From 374e939ac467cf98bd785442f28a4ec802f27dc6 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 31 Jul 2018 00:44:41 +0200 Subject: Added the tsuserver3 files necessary to support this custom client. --- server/__init__.py | 0 server/aoprotocol.py | 641 ++++++++++++++++++++++++++++++++ server/area_manager.py | 210 +++++++++++ server/ban_manager.py | 54 +++ server/client_manager.py | 380 +++++++++++++++++++ server/commands.py | 848 +++++++++++++++++++++++++++++++++++++++++++ server/constants.py | 11 + server/districtclient.py | 79 ++++ server/evidence.py | 91 +++++ server/exceptions.py | 32 ++ server/fantacrypt.py | 45 +++ server/logger.py | 64 ++++ server/masterserverclient.py | 89 +++++ server/tsuserver.py | 263 ++++++++++++++ server/websocket.py | 212 +++++++++++ 15 files changed, 3019 insertions(+) create mode 100644 server/__init__.py create mode 100644 server/aoprotocol.py create mode 100644 server/area_manager.py create mode 100644 server/ban_manager.py create mode 100644 server/client_manager.py create mode 100644 server/commands.py create mode 100644 server/constants.py create mode 100644 server/districtclient.py create mode 100644 server/evidence.py create mode 100644 server/exceptions.py create mode 100644 server/fantacrypt.py create mode 100644 server/logger.py create mode 100644 server/masterserverclient.py create mode 100644 server/tsuserver.py create mode 100644 server/websocket.py (limited to 'server') diff --git a/server/__init__.py b/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/aoprotocol.py b/server/aoprotocol.py new file mode 100644 index 0000000..e0c35e8 --- /dev/null +++ b/server/aoprotocol.py @@ -0,0 +1,641 @@ +# 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 + + +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: + self.client.disconnect() + return + # 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: + return + + 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.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') + + 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 + 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): + msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color = args + showname = self.client.get_char_name() + 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): + msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname = args + if len(showname) > 0 and not self.client.area.showname_changes_allowed == "true": + 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 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 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: + 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'): + return + msg = text[:256] + 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() + 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) + 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) + + 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 + if self.client.name.startswith(self.server.config['hostname']) or self.client.name.startswith('G'): + self.client.send_host_message('That name is reserved!') + return + if args[1].startswith('/'): + spl = args[1][1:].split(' ', 1) + cmd = spl[0] + 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.disemvowel: + args[1] = self.client.disemvowel_message(args[1]) + self.client.area.send_command('CT', self.client.name, args[1]) + logger.log_server( + '[OOC][{}][{}][{}]{}'.format(self.client.area.id, self.client.get_char_name(), self.client.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 not self.validate_net_cmd(args, self.ArgType.STR, self.ArgType.INT): + 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]) + 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) + 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 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): + return + if args[0] == 'testimony1': + sign = 'WT' + elif args[0] == 'testimony2': + sign = 'CE' + 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]) + 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) + + 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 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) + 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, + 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())) + 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, + self.client.area.id, args[0]), 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(), args[0])) + + 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 new file mode 100644 index 0000000..6b6c939 --- /dev/null +++ b/server/area_manager.py @@ -0,0 +1,210 @@ +# 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 + + +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): + 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.evi_list = EvidenceList() + self.is_recording = False + self.recorded_messages = [] + self.evidence_mod = evidence_mod + self.locking_allowed = locking_allowed + self.owned = False + 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 = False + + def new_client(self, client): + self.clients.add(client) + + 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() + + def unlock(self): + self.is_locked = False + self.invite_list = {} + self.send_host_message('This area is open 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_host_message(self, msg): + self.send_command('CT', self.server.config['hostname'], msg) + + 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 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 can_send_message(self, client): + if self.is_locked and not client.is_mod and not client.ipid in self.invite_list: + 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 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', 'building-open', 'building-full', 'casing-open', 'casing-full', 'recess') + if value.lower() not in allowed_values: + raise AreaError('Invalid status. Possible values: {}'.format(', '.join(allowed_values))) + self.status = value.upper() + + 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 = 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 __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 + 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'])) + 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.') diff --git a/server/ban_manager.py b/server/ban_manager.py new file mode 100644 index 0000000..24518b2 --- /dev/null +++ b/server/ban_manager.py @@ -0,0 +1,54 @@ +# 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) diff --git a/server/client_manager.py b/server/client_manager.py new file mode 100644 index 0000000..6857269 --- /dev/null +++ b/server/client_manager.py @@ -0,0 +1,380 @@ +# 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.is_cm = False + self.evi_list = [] + self.disemvowel = False + 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 + + #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) + + 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 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) + logger.log_server('[{}]Changed character from {} to {}.' + .format(self.area.id, old_char, self.get_char_name()), self) + + def change_music_cd(self): + if self.is_mod or self.is_cm: + 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.is_cm: + 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 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!") + 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.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 ===' + 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 self.area == area: + msg += ' [*]' + self.send_host_message(msg) + + def get_area_info(self, area_id, mods): + info = '' + try: + area = self.server.area_manager.get_area_by_id(area_id) + except AreaError: + raise + info += '= Area {}: {} =='.format(area.id, area.name) + sorted_clients = [] + for client in area.clients: + if (not mods) or client.is_mod: + sorted_clients.append(client) + 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()) + 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: + cnt += len(self.server.area_manager.areas[i].clients) + info += '\r\n{}'.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) + 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): + 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('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.send_command('DONE') + + def char_select(self): + self.char_id = -1 + self.send_done() + + 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'): + raise ClientError('Invalid position. Possible values: def, pro, hld, hlp, jud, wit.') + 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 disemvowel_message(self, message): + message = re.sub("[aeiou]", "", message, flags=re.IGNORECASE) + return re.sub(r"\s+", " ", message) + + 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): + 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 new file mode 100644 index 0000000..efcfe38 --- /dev/null +++ b/server/commands.py @@ -0,0 +1,848 @@ +# 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 hashlib +import string +from server.constants import TargetType + +from server import logger +from server.exceptions import ClientError, ServerError, ArgumentError, AreaError + + +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.id, 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('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) + +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_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.id, client.get_char_name(), roll, val[0])) + +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 /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.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)) + 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])) + +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, + client.area.current_music_player)) + +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.id, client.get_char_name(), flip)) + +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.is_cm 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.id, client.get_char_name(), pos, len(targets))) + +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) + 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: + targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) + if targets: + for c in targets: + 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) + +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)) + + +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.id, 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") + +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.IPID, 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") + +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) + +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.id, 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.id, 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.id, 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)) + logger.log_server('[{}][{}][ANNOUNCEMENT]{}.'.format(client.area.id, 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.id, 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.id, client.get_char_name(), client.area.doc)) + 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)) + + +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.id, client.get_char_name(), client.area.doc)) + 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.id, client.get_char_name(), client.area.status)) + 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: + c.send_host_message('PM from {} in {} ({}): {}'.format(client.name, 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.') + 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 client.area.owned == False: + client.area.owned = True + client.is_cm = True + 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())) + +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.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 + return + else: + raise ClientError('Only CM can lock the area.') + +def ooc_cmd_area_unlock(client, arg): + if not client.area.is_locked: + raise ClientError('Area is already unlocked.') + if not client.is_cm: + 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 not client.area.is_locked: + raise ClientError('Area isn\'t locked.') + if not client.is_cm 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.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)) + except: + raise ClientError('You must specify a target. Use /invite ') + +def ooc_cmd_uninvite(client, arg): + if not client.is_cm 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: + 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.invite_list.pop(c.ipid) + 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 not client.area.is_locked and not client.is_mod: + 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.invite_list.pop(c.ipid) + 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_mute .') + targets = client.server.client_manager.get_targets(client, TargetType.ID, arg, False) + if not targets: + raise ArgumentError('Target not found. Use /ooc_mute .') + 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) + 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 /disemvowel .') + if targets: + for c in targets: + logger.log_server('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_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.') + 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.') + 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.') + 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.') + 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.is_cm 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 = 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 new file mode 100644 index 0000000..fa07e8e --- /dev/null +++ b/server/constants.py @@ -0,0 +1,11 @@ +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 new file mode 100644 index 0000000..adc29ec --- /dev/null +++ b/server/districtclient.py @@ -0,0 +1,79 @@ +# 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, + 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 new file mode 100644 index 0000000..ddd9ba3 --- /dev/null +++ b/server/evidence.py @@ -0,0 +1,91 @@ +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'], 'hlp':['hlp', 'pro'], 'hld':['hld', 'def'], 'jud':['jud'], 'all':['hlp', 'hld', 'wit', 'jud', 'pro', 'def', ''], 'pos':[]} + + def login(self, client): + if client.area.evidence_mod == 'FFA': + pass + if client.area.evidence_mod == 'Mods': + if not client.is_cm: + return False + if client.area.evidence_mod == 'CM': + if not client.is_cm and not client.is_mod: + return False + if client.area.evidence_mod == 'HiddenCM': + if not client.is_cm 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 new file mode 100644 index 0000000..d3503e9 --- /dev/null +++ b/server/exceptions.py @@ -0,0 +1,32 @@ +# 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 new file mode 100644 index 0000000..e31548e --- /dev/null +++ b/server/fantacrypt.py @@ -0,0 +1,45 @@ +# 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 new file mode 100644 index 0000000..675a359 --- /dev/null +++ b/server/logger.py @@ -0,0 +1,64 @@ +# 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') + + 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) + + +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 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) diff --git a/server/masterserverclient.py b/server/masterserverclient.py new file mode 100644 index 0000000..49af043 --- /dev/null +++ b/server/masterserverclient.py @@ -0,0 +1,89 @@ +# 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 new file mode 100644 index 0000000..14ad60b --- /dev/null +++ b/server/tsuserver.py @@ -0,0 +1,263 @@ +# 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.id, 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 broadcast_need(self, client, msg): + char_name = client.get_char_name() + area_name = client.area.name + area_id = client.area.id + 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) + if self.config['use_district']: + self.district_client.send_raw_message('NEED#{}#{}#{}#{}'.format(char_name, area_name, area_id, msg)) + + 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 new file mode 100644 index 0000000..d77f678 --- /dev/null +++ b/server/websocket.py @@ -0,0 +1,212 @@ +# 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 + + if payload_length == 126: + payload_length = struct.unpack(">H", data[2:4])[0] + elif payload_length == 127: + payload_length = struct.unpack(">Q", data[2:10])[0] + + masks = data[2:6] + decoded = "" + for char in data[6:payload_length + 6]: + 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) \ No newline at end of file -- cgit From 651585f1912ee30d8940243186dbacac518458f8 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 31 Jul 2018 03:24:44 +0200 Subject: Fixed a bug where shownames would always be forbidden. --- server/aoprotocol.py | 3 +-- server/area_manager.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index e0c35e8..d21a6a5 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -347,12 +347,11 @@ class AOProtocol(asyncio.Protocol): self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR): msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname = args - if len(showname) > 0 and not self.client.area.showname_changes_allowed == "true": + 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 diff --git a/server/area_manager.py b/server/area_manager.py index 6b6c939..3ed543d 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -49,6 +49,8 @@ class AreaManager: 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.owned = False self.cards = dict() -- cgit From 8e3922489095d83532b1c5273137c57a3a3ff3a8 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 3 Aug 2018 19:52:32 +0200 Subject: Showname fix, case insensitive commands. - Fixed a problem wherein if you had 'Custom shownames' on, your client would display the foldernames of characters if their user hadn't given temselves a custom showname, instead of the character's showname. --- server/aoprotocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index d21a6a5..1711eba 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -340,7 +340,7 @@ class AOProtocol(asyncio.Protocol): self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.INT): msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color = args - showname = self.client.get_char_name() + showname = "" 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, @@ -442,7 +442,7 @@ class AOProtocol(asyncio.Protocol): 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] -- cgit From f9fd9a789af5587de40ee131ce709791ca81253b Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 9 Aug 2018 16:36:56 +0200 Subject: Limit mod call reason limit to 100 on serverside, too. --- server/aoprotocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 1711eba..9b8822b 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -603,7 +603,7 @@ class AOProtocol(asyncio.Protocol): 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, - self.client.area.id, args[0]), pred=lambda c: c.is_mod) + self.client.area.id, args[0][:100]), 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(), args[0])) -- cgit From 0280f42f6ea2443757f0aa483322d60a5b2c0b6f Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 9 Aug 2018 22:19:39 +0200 Subject: PMs now show ID (and IPID if you're a mod). --- server/commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index efcfe38..b8a21b3 100644 --- a/server/commands.py +++ b/server/commands.py @@ -466,7 +466,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): -- cgit From 84da730bcef71aeb0d0944261a44dc289949a74d Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 10 Aug 2018 00:09:41 +0200 Subject: Music changing now shows your custom showname, if set. --- server/aoprotocol.py | 11 ++++++++--- server/area_manager.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 9b8822b..d26afc9 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -478,7 +478,7 @@ 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 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 @@ -487,8 +487,13 @@ 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) + if len(args) > 2: + showname = args[2] + 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.id, self.client.get_char_name(), name), self.client) except ServerError: diff --git a/server/area_manager.py b/server/area_manager.py index 3ed543d..99b4efd 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -116,6 +116,14 @@ 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): @@ -159,6 +167,10 @@ class AreaManager: self.current_music_player = client.get_char_name() 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 = name + def get_evidence_list(self, client): client.evi_list, evi_list = self.evi_list.create_evi_list(client) return evi_list -- cgit From bf971f69d8a4317d511334727455f368906b9a21 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 10 Aug 2018 00:30:05 +0200 Subject: Fixed a bug where showname change rules got ignored when changing songs. --- server/aoprotocol.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index d26afc9..877b617 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -489,6 +489,9 @@ class AOProtocol(asyncio.Protocol): name, length = self.server.get_song_data(args[0]) 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: -- cgit From 5c7b233f8c5266ec04830ac64a534765d01fc495 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 10 Aug 2018 22:17:19 +0200 Subject: Area statuses updated. --- server/area_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 99b4efd..3ee2b27 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -150,9 +150,11 @@ 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() def change_doc(self, doc='No document.'): -- cgit From 1add0108e8f592e471905d8391f1b8f8b5daea0c Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 11 Aug 2018 00:31:42 +0200 Subject: Added the ability to un-CM self. --- server/commands.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index b8a21b3..701a82f 100644 --- a/server/commands.py +++ b/server/commands.py @@ -538,6 +538,16 @@ def ooc_cmd_cm(client, arg): 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())) + +def ooc_cmd_uncm(client, arg): + if client.is_cm: + client.is_cm = False + client.area.owned = False + if client.area.is_locked: + client.area.unlock() + client.area.send_host_message('{} is no longer CM in this area.'.format(client.get_char_name())) + else: + raise ClientError('You cannot give up being the CM when you are not one') def ooc_cmd_unmod(client, arg): client.is_mod = False -- cgit From c22606b5a70d8afa845e6f274521a89cd240a18e Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 11 Aug 2018 01:02:07 +0200 Subject: /currentmusic now tells you the IPID of the last music changer if you're a mod. --- server/area_manager.py | 3 +++ server/commands.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 3ee2b27..374c529 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -44,6 +44,7 @@ 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 = [] @@ -167,10 +168,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): diff --git a/server/commands.py b/server/commands.py index 701a82f..14ae147 100644 --- a/server/commands.py +++ b/server/commands.py @@ -152,7 +152,11 @@ def ooc_cmd_currentmusic(client, arg): 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_coinflip(client, arg): -- cgit From 3759131a8f0c0d65e14fdda9748de300dc23670d Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 12 Aug 2018 00:12:09 +0200 Subject: Area numbers replaced by area abbreviations. --- server/aoprotocol.py | 10 ++++------ server/area_manager.py | 12 ++++++++++++ server/client_manager.py | 4 ++-- server/commands.py | 2 +- server/tsuserver.py | 4 ++-- 5 files changed, 21 insertions(+), 11 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 877b617..9bddb0a 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -603,15 +603,13 @@ class AOProtocol(asyncio.Protocol): 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, - self.client.area.id), pred=lambda c: c.is_mod) + 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.get_ip(), self.client.area.id, self.client.get_char_name())) 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, - self.client.area.id, args[0][:100]), pred=lambda c: c.is_mod) + 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.get_ip(), self.client.area.id, self.client.get_char_name(), args[0])) diff --git a/server/area_manager.py b/server/area_manager.py index 374c529..36ade64 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -187,6 +187,18 @@ class AreaManager: """ for client in self.clients: client.send_command('LE', *self.get_evidence_list(client)) + + def get_abbreviation(self): + if self.name.lower().startswith("courtroom"): + return "CR" + self.name.split()[-1] + elif self.name.lower().startswith("area"): + return "A" + self.name.split()[-1] + elif len(self.name.split()) > 1: + return "".join(item[0].upper() for item in self.name.split()) + elif len(self.name) > 3: + return self.name[:3].upper() + else: + return self.name.upper() def __init__(self, server): diff --git a/server/client_manager.py b/server/client_manager.py index 6857269..0030173 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -203,7 +203,7 @@ class ClientManager: 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]) + msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(area.get_abbreviation(), area.name, len(area.clients), area.status, owner, lock[area.is_locked]) if self.area == area: msg += ' [*]' self.send_host_message(msg) @@ -214,7 +214,7 @@ class ClientManager: 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) sorted_clients = [] for client in area.clients: if (not mods) or client.is_mod: diff --git a/server/commands.py b/server/commands.py index 14ae147..f58dbea 100644 --- a/server/commands.py +++ b/server/commands.py @@ -593,7 +593,7 @@ def ooc_cmd_invite(client, arg): c = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0] client.area.invite_list[c.ipid] = 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 ') diff --git a/server/tsuserver.py b/server/tsuserver.py index 14ad60b..9438a35 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -232,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.get_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) @@ -243,7 +243,7 @@ class TsuServer3: 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.get_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) -- cgit From d444eb6dceb47123940baed5a255572d24567dc8 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 12 Aug 2018 00:32:43 +0200 Subject: Modchat for mods to chat secretly across areas. Called with `/m [message]`. --- server/commands.py | 8 ++++++++ server/tsuserver.py | 8 ++++++++ 2 files changed, 16 insertions(+) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index f58dbea..b8748ed 100644 --- a/server/commands.py +++ b/server/commands.py @@ -343,6 +343,14 @@ def ooc_cmd_gm(client, arg): client.server.broadcast_global(client, arg, True) logger.log_server('[{}][{}][GLOBAL-MOD]{}.'.format(client.area.id, 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.id, 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.') diff --git a/server/tsuserver.py b/server/tsuserver.py index 9438a35..a7aed5d 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -240,6 +240,14 @@ 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.get_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 -- cgit From 00b5af9b60aaeeb26511f29a6f839210ee9c8234 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 12 Aug 2018 02:17:07 +0200 Subject: Fixed the `/invite` and `/uninvite` commands so they work as they claim they do. - You can now invite and uninvite by IDs. --- server/client_manager.py | 2 +- server/commands.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/server/client_manager.py b/server/client_manager.py index 0030173..e127880 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -168,7 +168,7 @@ 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: + if area.is_locked and not self.is_mod and not self.id 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!") old_area = self.area diff --git a/server/commands.py b/server/commands.py index b8748ed..853a37d 100644 --- a/server/commands.py +++ b/server/commands.py @@ -577,7 +577,7 @@ def ooc_cmd_area_lock(client, arg): 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 + client.area.invite_list[i.id] = None return else: raise ClientError('Only CM can lock the area.') @@ -599,7 +599,7 @@ def ooc_cmd_invite(client, arg): 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 {}.'.format(client.area.name)) except: @@ -620,7 +620,7 @@ def ooc_cmd_uninvite(client, arg): 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) + client.area.invite_list.pop(c.id) except AreaError: raise except ClientError: @@ -653,7 +653,7 @@ def ooc_cmd_area_kick(client, arg): 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) + client.area.invite_list.pop(c.id) except AreaError: raise except ClientError: -- cgit From 37c0a709488d6d8b3d8191f321098ea996d9cf70 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 13 Aug 2018 08:30:29 +0200 Subject: `/rolla_set`'s display fixed. --- server/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index 853a37d..eb42470 100644 --- a/server/commands.py +++ b/server/commands.py @@ -825,7 +825,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: -- cgit From 3712526ff0e4a715ea9548f331edfc43d1502eb9 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 13 Aug 2018 14:39:09 +0200 Subject: Added a HDID-based banning system. --- server/aoprotocol.py | 5 ++++- server/ban_manager.py | 31 +++++++++++++++++++++++++++++++ server/commands.py | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 9bddb0a..75ee824 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -65,7 +65,7 @@ class AOProtocol(asyncio.Protocol): buf = data - if not self.client.is_checked and self.server.ban_manager.is_banned(self.client.ipid): + if not self.client.is_checked and (self.server.ban_manager.is_banned(self.client.ipid) or self.server.ban_manager.is_hdid_banned(self.client.hdid)): self.client.transport.close() else: self.client.is_checked = True @@ -165,6 +165,9 @@ class AOProtocol(asyncio.Protocol): if not self.validate_net_cmd(args, self.ArgType.STR, needs_auth=False): return self.client.hdid = args[0] + if self.server.ban_manager.is_hdid_banned(self.client.hdid): + self.client.disconnect() + return 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]: diff --git a/server/ban_manager.py b/server/ban_manager.py index 24518b2..b4f97b7 100644 --- a/server/ban_manager.py +++ b/server/ban_manager.py @@ -25,6 +25,9 @@ class BanManager: self.bans = [] self.load_banlist() + self.hdid_bans = [] + self.load_hdid_banlist() + def load_banlist(self): try: with open('storage/banlist.json', 'r') as banlist_file: @@ -52,3 +55,31 @@ class BanManager: def is_banned(self, ipid): return (ipid in self.bans) + + def load_hdid_banlist(self): + try: + with open('storage/banlist_hdid.json', 'r') as banlist_file: + self.hdid_bans = json.load(banlist_file) + except FileNotFoundError: + return + + def write_hdid_banlist(self): + with open('storage/banlist_hdid.json', 'w') as banlist_file: + json.dump(self.hdid_bans, banlist_file) + + def add_hdid_ban(self, hdid): + if hdid not in self.hdid_bans: + self.hdid_bans.append(hdid) + else: + raise ServerError('This HDID is already banned.') + self.write_hdid_banlist() + + def remove_hdid_ban(self, hdid): + if hdid in self.hdid_bans: + self.hdid_bans.remove(hdid) + else: + raise ServerError('This HDID is not banned.') + self.write_hdid_banlist() + + def is_hdid_banned(self, hdid): + return (hdid in self.hdid_bans) \ No newline at end of file diff --git a/server/commands.py b/server/commands.py index eb42470..c1143d2 100644 --- a/server/commands.py +++ b/server/commands.py @@ -275,10 +275,39 @@ def ooc_cmd_unban(client, arg): try: client.server.ban_manager.remove_ban(int(arg.strip())) except: - raise ClientError('You must specify \'hdid\'') + raise ClientError('You must specify ipid') + logger.log_server('Unbanned {}.'.format(arg), client) + client.send_host_message('Unbanned {}'.format(arg)) + +def ooc_cmd_ban_hdid(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + try: + hdid = int(arg.strip()) + except: + raise ClientError('You must specify hdid') + try: + client.server.ban_manager.add_hdid_ban(hdid) + except ServerError: + raise + if hdid != None: + targets = client.server.client_manager.get_targets(client, TargetType.HDID, hdid, False) + if targets: + for c in targets: + c.disconnect() + client.send_host_message('{} clients was kicked.'.format(len(targets))) + client.send_host_message('{} was banned.'.format(hdid)) + logger.log_server('Banned {}.'.format(hdid), client) + +def ooc_cmd_unban_hdid(client, arg): + if not client.is_mod: + raise ClientError('You must be authorized to do that.') + try: + client.server.ban_manager.remove_hdid_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)) - def ooc_cmd_play(client, arg): if not client.is_mod: -- cgit From 8c859398f1b1308633584103c334bb103bf6cf2c Mon Sep 17 00:00:00 2001 From: Cerapter Date: Wed, 15 Aug 2018 19:50:41 +0200 Subject: Blocking shouts now blocks the testimony buttons too. --- server/aoprotocol.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 75ee824..912c0e7 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -513,6 +513,9 @@ 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 -- cgit From 9ce1d3fa40b48fb55ea94f7d833ee029e7d23a7b Mon Sep 17 00:00:00 2001 From: Cerapter Date: Wed, 15 Aug 2018 23:30:46 +0200 Subject: Jukebox + Area abbreviation finetuning. - An area can now have a custom `abbreviation: XXX` set in `areas.yaml`. - Areas can have jukebox mode on by `jukebox: true` in `areas.yaml`. - When this mode is on, music changing is actually voting for the next music. - If no music is playing, or there is only your vote in there, it behaves as normal music changing. - In case of multiple votes, your vote gets added to a list, and may have a chance of being picked. - Check this list with `/jukebox`. - If not your music is picked, your voting power increases, making your music being picked next more likely. - If yours is picked, your voting power is reset to 0. - No matter how many people select the same song, if the song gets picked, all of them will have their voting power reset to 0. - Leaving an area, or picking a not-really-a-song (like 'PRELUDE', which is a category marker, basically), will remove your vote. - If there are no votes left (because every left, for example), the jukebox stops playing songs. - Mods can force a skip by `/jukebox_skip`. - Mods can also toggle an area's jukebox with `/jukebox_toggle`. - Mods can also still play songs with `/play`, though they might get cucked by the Jukebox. --- server/aoprotocol.py | 27 +++++++----- server/area_manager.py | 106 ++++++++++++++++++++++++++++++++++++++++------- server/client_manager.py | 8 +++- server/commands.py | 70 +++++++++++++++++++++++++++++++ server/tsuserver.py | 6 +-- 5 files changed, 188 insertions(+), 29 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 912c0e7..b36aa61 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -490,18 +490,23 @@ class AOProtocol(asyncio.Protocol): return try: name, length = self.server.get_song_data(args[0]) - 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) + + if self.client.area.jukebox: + self.client.area.add_jukebox_vote(self.client, name, length) + logger.log_server('[{}][{}]Added a jukebox vote for {}.'.format(self.client.area.id, self.client.get_char_name(), name), self.client) 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.id, self.client.get_char_name(), name), self.client) + 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.id, self.client.get_char_name(), name), self.client) except ServerError: return except ClientError as ex: diff --git a/server/area_manager.py b/server/area_manager.py index 36ade64..d0ff1cb 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -26,7 +26,7 @@ from server.evidence import EvidenceList 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): + 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 = ''): self.iniswap_allowed = iniswap_allowed self.clients = set() self.invite_list = {} @@ -52,6 +52,7 @@ class AreaManager: self.locking_allowed = locking_allowed self.showname_changes_allowed = showname_changes_allowed self.shouts_allowed = shouts_allowed + self.abbreviation = abbreviation self.owned = False self.cards = dict() @@ -64,6 +65,9 @@ class AreaManager: self.is_locked = False + self.jukebox = jukebox + self.jukebox_votes = [] + def new_client(self, client): self.clients.add(client) @@ -109,6 +113,70 @@ 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): + 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)) + 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): + 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 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 + + self.send_command('MC', vote_picked.name, vote_picked.client.char_id) + + 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) @@ -188,18 +256,12 @@ class AreaManager: for client in self.clients: client.send_command('LE', *self.get_evidence_list(client)) - def get_abbreviation(self): - if self.name.lower().startswith("courtroom"): - return "CR" + self.name.split()[-1] - elif self.name.lower().startswith("area"): - return "A" + self.name.split()[-1] - elif len(self.name.split()) > 1: - return "".join(item[0].upper() for item in self.name.split()) - elif len(self.name) > 3: - return self.name[:3].upper() - else: - return self.name.upper() - + class JukeboxVote: + def __init__(self, client, name, length): + self.client = client + self.name = name + self.length = length + self.chance = 1 def __init__(self, server): self.server = server @@ -221,8 +283,12 @@ class AreaManager: item['showname_changes_allowed'] = False if 'shouts_allowed' not in item: item['shouts_allowed'] = True + if 'jukebox' not in item: + item['jukebox'] = 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'])) + 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'])) self.cur_id += 1 def default_area(self): @@ -239,3 +305,15 @@ 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() diff --git a/server/client_manager.py b/server/client_manager.py index e127880..62e141d 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -106,6 +106,8 @@ class ClientManager: return True def disconnect(self): + if self.area.jukebox: + self.area.remove_jukebox_vote(self, True) self.transport.close() def change_character(self, char_id, force=False): @@ -171,6 +173,10 @@ class ClientManager: if area.is_locked and not self.is_mod and not self.id 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 self.area.jukebox: + self.area.remove_jukebox_vote(self, True) + old_area = self.area if not area.is_char_available(self.char_id): try: @@ -203,7 +209,7 @@ class ClientManager: 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(area.get_abbreviation(), area.name, len(area.clients), area.status, owner, lock[area.is_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) diff --git a/server/commands.py b/server/commands.py index c1143d2..d952d15 100644 --- a/server/commands.py +++ b/server/commands.py @@ -159,6 +159,76 @@ def ooc_cmd_currentmusic(client, arg): 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: + 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.send_host_message('A mod has set the jukebox to {}.'.format(client.area.jukebox)) + +def ooc_cmd_jukebox_skip(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 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('A mod has forced a skip, restarting the only jukebox song.') + else: + client.area.send_host_message('A mod has forced a skip to the next jukebox song.') + logger.log_server('[{}][{}]Skipped the current jukebox song.'.format(client.area.id, client.get_char_name())) + +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.') diff --git a/server/tsuserver.py b/server/tsuserver.py index a7aed5d..97b4b90 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -232,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.get_abbreviation(), 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) @@ -242,7 +242,7 @@ class TsuServer3: def send_modchat(self, client, msg): name = client.name - ooc_name = '{}[{}][{}]'.format('M', client.area.get_abbreviation(), 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( @@ -251,7 +251,7 @@ class TsuServer3: def broadcast_need(self, client, msg): char_name = client.get_char_name() area_name = client.area.name - area_id = client.area.get_abbreviation() + 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) -- cgit From 956c3b50d6c813abc149b80c5abb03d6712d1e95 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 16 Aug 2018 00:40:42 +0200 Subject: Added support for the jukebox to use the shownames of its users. --- server/aoprotocol.py | 8 +++++++- server/area_manager.py | 12 ++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index b36aa61..9c7c9ca 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -492,7 +492,13 @@ class AOProtocol(asyncio.Protocol): name, length = self.server.get_song_data(args[0]) if self.client.area.jukebox: - self.client.area.add_jukebox_vote(self.client, name, length) + showname = '' + if len(args) > 2: + if len(args[2]) > 0 and not self.client.area.showname_changes_allowed: + self.client.send_host_message("Showname changes are forbidden in this area!") + return + showname = args[2] + self.client.area.add_jukebox_vote(self.client, name, length, showname) logger.log_server('[{}][{}]Added a jukebox vote for {}.'.format(self.client.area.id, self.client.get_char_name(), name), self.client) else: if len(args) > 2: diff --git a/server/area_manager.py b/server/area_manager.py index d0ff1cb..e6b34a0 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -114,12 +114,12 @@ class AreaManager: return False return True - def add_jukebox_vote(self, client, music_name, length=-1): + def add_jukebox_vote(self, client, music_name, length=-1, showname=''): 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)) + 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() @@ -160,7 +160,10 @@ class AreaManager: self.current_music = '' return - self.send_command('MC', vote_picked.name, 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) self.current_music_player = 'The Jukebox' self.current_music_player_ipid = 'has no IPID' @@ -257,11 +260,12 @@ class AreaManager: client.send_command('LE', *self.get_evidence_list(client)) class JukeboxVote: - def __init__(self, client, name, length): + 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 -- cgit From 0368e7dc459b3057f8c7d0e6e329de0d3cd7c424 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 16 Aug 2018 20:04:19 +0200 Subject: Guilty / Not Guilty buttons for the judge position. --- server/aoprotocol.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 9c7c9ca..211bcff 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -533,18 +533,23 @@ class AOProtocol(asyncio.Protocol): 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 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) -- cgit From aee3099d9b1ce0fb1e2521612d8f4eb07b323e4a Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 16 Aug 2018 22:57:41 +0200 Subject: Added the command `/allow_blankposting` for CMs and mods to control blankposting. --- server/aoprotocol.py | 3 +++ server/area_manager.py | 2 +- server/commands.py | 11 ++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 211bcff..11d2fbd 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -358,6 +358,9 @@ class AOProtocol(asyncio.Protocol): 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 not self.client.area.blankposting_allowed and text == ' ': + self.client.send_host_message("Blankposting is forbidden in this area!") + return if msg_type not in ('chat', '0', '1'): return if anim_type not in (0, 1, 2, 5, 6): diff --git a/server/area_manager.py b/server/area_manager.py index e6b34a0..583e149 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -64,7 +64,7 @@ class AreaManager: """ self.is_locked = False - + self.blankposting_allowed = True self.jukebox = jukebox self.jukebox_votes = [] diff --git a/server/commands.py b/server/commands.py index d952d15..d1f145d 100644 --- a/server/commands.py +++ b/server/commands.py @@ -89,7 +89,16 @@ 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.is_cm: + raise ClientError('You must be authorized to do that.') + client.area.blankposting_allowed = not client.area.blankposting_allowed + answer = {True: 'allowed', False: 'forbidden'} + if client.is_cm: + client.area.send_host_message('The CM has set blankposting in the area to {}.'.format(answer[client.area.blankposting_allowed])) + else: + client.area.send_host_message('A mod has set blankposting in the area to {}.'.format(answer[client.area.blankposting_allowed])) + return def ooc_cmd_roll(client, arg): roll_max = 11037 -- cgit From 265b853337e6368afc2816a78a64a4d128f756e4 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 17 Aug 2018 02:12:29 +0200 Subject: Removed HDID ban due to it not working. --- server/aoprotocol.py | 5 +---- server/ban_manager.py | 33 +-------------------------------- server/commands.py | 32 +------------------------------- 3 files changed, 3 insertions(+), 67 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 11d2fbd..5bc94bb 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -65,7 +65,7 @@ class AOProtocol(asyncio.Protocol): buf = data - if not self.client.is_checked and (self.server.ban_manager.is_banned(self.client.ipid) or self.server.ban_manager.is_hdid_banned(self.client.hdid)): + 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 @@ -165,9 +165,6 @@ class AOProtocol(asyncio.Protocol): if not self.validate_net_cmd(args, self.ArgType.STR, needs_auth=False): return self.client.hdid = args[0] - if self.server.ban_manager.is_hdid_banned(self.client.hdid): - self.client.disconnect() - return 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]: diff --git a/server/ban_manager.py b/server/ban_manager.py index b4f97b7..20c186f 100644 --- a/server/ban_manager.py +++ b/server/ban_manager.py @@ -25,9 +25,6 @@ class BanManager: self.bans = [] self.load_banlist() - self.hdid_bans = [] - self.load_hdid_banlist() - def load_banlist(self): try: with open('storage/banlist.json', 'r') as banlist_file: @@ -54,32 +51,4 @@ class BanManager: self.write_banlist() def is_banned(self, ipid): - return (ipid in self.bans) - - def load_hdid_banlist(self): - try: - with open('storage/banlist_hdid.json', 'r') as banlist_file: - self.hdid_bans = json.load(banlist_file) - except FileNotFoundError: - return - - def write_hdid_banlist(self): - with open('storage/banlist_hdid.json', 'w') as banlist_file: - json.dump(self.hdid_bans, banlist_file) - - def add_hdid_ban(self, hdid): - if hdid not in self.hdid_bans: - self.hdid_bans.append(hdid) - else: - raise ServerError('This HDID is already banned.') - self.write_hdid_banlist() - - def remove_hdid_ban(self, hdid): - if hdid in self.hdid_bans: - self.hdid_bans.remove(hdid) - else: - raise ServerError('This HDID is not banned.') - self.write_hdid_banlist() - - def is_hdid_banned(self, hdid): - return (hdid in self.hdid_bans) \ No newline at end of file + return (ipid in self.bans) \ No newline at end of file diff --git a/server/commands.py b/server/commands.py index d1f145d..a3c3ce8 100644 --- a/server/commands.py +++ b/server/commands.py @@ -357,37 +357,7 @@ def ooc_cmd_unban(client, arg): raise ClientError('You must specify ipid') logger.log_server('Unbanned {}.'.format(arg), client) client.send_host_message('Unbanned {}'.format(arg)) - -def ooc_cmd_ban_hdid(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - try: - hdid = int(arg.strip()) - except: - raise ClientError('You must specify hdid') - try: - client.server.ban_manager.add_hdid_ban(hdid) - except ServerError: - raise - if hdid != None: - targets = client.server.client_manager.get_targets(client, TargetType.HDID, hdid, False) - if targets: - for c in targets: - c.disconnect() - client.send_host_message('{} clients was kicked.'.format(len(targets))) - client.send_host_message('{} was banned.'.format(hdid)) - logger.log_server('Banned {}.'.format(hdid), client) - -def ooc_cmd_unban_hdid(client, arg): - if not client.is_mod: - raise ClientError('You must be authorized to do that.') - try: - client.server.ban_manager.remove_hdid_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)) - + def ooc_cmd_play(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') -- cgit From 457a5e39fcb0f34477c7ac22c5ed8919f6522eec Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 19 Aug 2018 08:03:39 +0200 Subject: Jukebox fixes: check if jukebox exists + blockDJ removes vote. --- server/area_manager.py | 6 ++++++ server/commands.py | 1 + 2 files changed, 7 insertions(+) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 583e149..90229d0 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -115,6 +115,8 @@ class AreaManager: 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: @@ -125,6 +127,8 @@ class AreaManager: 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) @@ -132,6 +136,8 @@ class AreaManager: 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: diff --git a/server/commands.py b/server/commands.py index a3c3ce8..5dfe030 100644 --- a/server/commands.py +++ b/server/commands.py @@ -812,6 +812,7 @@ 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.') + 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): -- cgit From 9ce2ec9de23c12729896c494ff6c42689664d08d Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 21 Aug 2018 14:53:22 +0200 Subject: Added the command list link to `/help`. --- server/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index 5dfe030..16a4a3b 100644 --- a/server/commands.py +++ b/server/commands.py @@ -310,8 +310,8 @@ def ooc_cmd_forcepos(client, arg): 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): -- cgit From 4ee565591f4e4bd278336ca182c1b516236829cf Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 24 Aug 2018 12:58:59 +0200 Subject: Jukebox and area locking bugfixes. --- server/area_manager.py | 2 +- server/client_manager.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 90229d0..372195b 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -205,7 +205,7 @@ class AreaManager: 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.is_locked and not client.is_mod and not client.id in self.invite_list: 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 diff --git a/server/client_manager.py b/server/client_manager.py index 62e141d..709e0d8 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -106,8 +106,6 @@ class ClientManager: return True def disconnect(self): - if self.area.jukebox: - self.area.remove_jukebox_vote(self, True) self.transport.close() def change_character(self, char_id, force=False): @@ -337,6 +335,8 @@ class ClientManager: def remove_client(self, client): + if client.area.jukebox: + client.area.remove_jukebox_vote(client, True) heappush(self.cur_id, client.id) self.clients.remove(client) -- cgit From 6d278330a2299fb00937622ad40725f23898794c Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 24 Aug 2018 18:48:13 +0200 Subject: `/getarea` now shows the CM, `/jukebox_toggle` and `/jukebox_skip` are now CM commands as well. --- server/client_manager.py | 5 ++++- server/commands.py | 20 +++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'server') diff --git a/server/client_manager.py b/server/client_manager.py index 709e0d8..c5e0b10 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -225,7 +225,10 @@ class ClientManager: sorted_clients.append(client) 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.is_cm: + info +='[CM]' + info += '[{}] {}'.format(c.id, c.get_char_name()) if self.is_mod: info += ' ({})'.format(c.ipid) info += ': {}'.format(c.name) diff --git a/server/commands.py b/server/commands.py index 16a4a3b..6d34338 100644 --- a/server/commands.py +++ b/server/commands.py @@ -169,15 +169,20 @@ def ooc_cmd_currentmusic(client, arg): client.area.current_music_player)) def ooc_cmd_jukebox_toggle(client, arg): - if not client.is_mod: + if not client.is_mod and not client.is_cm: 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.send_host_message('A mod has set the jukebox to {}.'.format(client.area.jukebox)) + changer = 'Unknown' + if client.is_cm: + changer = 'The CM' + elif client.is_mod: + changer = 'A mod' + client.area.send_host_message('{} has set the jukebox to {}.'.format(changer, client.area.jukebox)) def ooc_cmd_jukebox_skip(client, arg): - if not client.is_mod: + if not client.is_mod and not client.is_cm: raise ClientError('You must be authorized to do that.') if len(arg) != 0: raise ArgumentError('This command has no arguments.') @@ -186,10 +191,15 @@ def ooc_cmd_jukebox_skip(client, arg): if len(client.area.jukebox_votes) == 0: raise ClientError('There is no song playing right now, skipping is pointless.') client.area.start_jukebox() + changer = 'Unknown' + if client.is_cm: + changer = 'The CM' + elif client.is_mod: + changer = 'A mod' if len(client.area.jukebox_votes) == 1: - client.area.send_host_message('A mod has forced a skip, restarting the only jukebox song.') + client.area.send_host_message('{} has forced a skip, restarting the only jukebox song.'.format(changer)) else: - client.area.send_host_message('A mod has forced a skip to the next jukebox song.') + client.area.send_host_message('{} has forced a skip to the next jukebox song.'.format(changer)) logger.log_server('[{}][{}]Skipped the current jukebox song.'.format(client.area.id, client.get_char_name())) def ooc_cmd_jukebox(client, arg): -- cgit From 34da56eea66b015c9ed3debc694821748e7e3ec9 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 24 Aug 2018 19:14:22 +0200 Subject: Blankposting is allowed again if an area loses its CM somehow. --- server/area_manager.py | 1 + server/commands.py | 1 + 2 files changed, 2 insertions(+) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 372195b..15dddd6 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -81,6 +81,7 @@ class AreaManager: def unlock(self): self.is_locked = False + self.blankposting_allowed = True self.invite_list = {} self.send_host_message('This area is open now.') diff --git a/server/commands.py b/server/commands.py index 6d34338..7c212ba 100644 --- a/server/commands.py +++ b/server/commands.py @@ -643,6 +643,7 @@ def ooc_cmd_uncm(client, arg): if client.is_cm: client.is_cm = False client.area.owned = False + client.area.blankposting_allowed = True if client.area.is_locked: client.area.unlock() client.area.send_host_message('{} is no longer CM in this area.'.format(client.get_char_name())) -- cgit From 0fb3b7edbfdc712710347aba05ce51316660a891 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 27 Aug 2018 16:04:53 +0200 Subject: Jukebox fixes: clear on toggle, don't show 'has played' with one person. --- server/area_manager.py | 11 ++++++++--- server/commands.py | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 15dddd6..07c6073 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -67,6 +67,7 @@ class AreaManager: self.blankposting_allowed = True self.jukebox = jukebox self.jukebox_votes = [] + self.jukebox_prev_char_id = -1 def new_client(self, client): self.clients.add(client) @@ -167,10 +168,14 @@ class AreaManager: self.current_music = '' return - if vote_picked.showname == '': - self.send_command('MC', vote_picked.name, vote_picked.client.char_id) + if vote_picked.char_id != self.jukebox_prev_char_id or len(self.jukebox_votes) > 1: + self.jukebox_prev_char_id = vote_picked.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, vote_picked.client.char_id, vote_picked.showname) + self.send_command('MC', vote_picked.name, -1) self.current_music_player = 'The Jukebox' self.current_music_player_ipid = 'has no IPID' diff --git a/server/commands.py b/server/commands.py index 7c212ba..bf1cc7e 100644 --- a/server/commands.py +++ b/server/commands.py @@ -174,6 +174,7 @@ def ooc_cmd_jukebox_toggle(client, arg): if len(arg) != 0: raise ArgumentError('This command has no arguments.') client.area.jukebox = not client.area.jukebox + client.area.jukebox_votes = [] changer = 'Unknown' if client.is_cm: changer = 'The CM' -- cgit From 7aac266b9bf0eb622e23c84306dbe095ef99871c Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 27 Aug 2018 16:09:11 +0200 Subject: Additional jukebox check just in case someone is alone, but is choosing different songs. --- server/area_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 07c6073..e1b7e55 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -168,7 +168,7 @@ class AreaManager: self.current_music = '' return - if vote_picked.char_id != self.jukebox_prev_char_id or len(self.jukebox_votes) > 1: + if vote_picked.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.char_id if vote_picked.showname == '': self.send_command('MC', vote_picked.name, vote_picked.client.char_id) -- cgit From 46e64d6077c6a9aa10f8331f43b7d8e34ba8d82f Mon Sep 17 00:00:00 2001 From: Cerapter Date: Wed, 29 Aug 2018 00:40:43 +0200 Subject: `/ban`, `/kick`, `/mute`, `/unmute`, `/unban` now allow for multiple people. Their IPIDs should be appended after one another with a space inbetween, so: `/ban 45123 42130 39212` for example. Further, they now all lead through the user as to what they're doing. --- server/commands.py | 127 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 43 deletions(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index bf1cc7e..4c79c29 100644 --- a/server/commands.py +++ b/server/commands.py @@ -330,44 +330,61 @@ def ooc_cmd_kick(client, arg): 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: + 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)) targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, 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() - 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.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) 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 ipid') - 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) + client.send_host_message('Unbanned {}'.format(raw_ipid)) def ooc_cmd_play(client, arg): if not client.is_mod: @@ -382,25 +399,49 @@ 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: + try: + ipid = int(raw_ipid) + except: + raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) + try: + clients = client.server.client_manager.get_targets(client, TargetType.IPID, int(ipid), False) + msg = 'Muted ' + str(ipid) + ' clients' + for c in clients: + c.is_muted = True + msg += ' ' + c.get_char_name() + ' [' + c.id + '],' + msg = msg[:-1] + msg += '.' + client.send_host_message('{}'.format(msg)) + except: + client.send_host_message("No targets found. Use /mute for mute.") 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.IPID, 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: + try: + ipid = int(raw_ipid) + except: + raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) + try: + clients = client.server.client_manager.get_targets(client, TargetType.IPID, int(ipid), False) + msg = 'Unmuted ' + str(ipid) + ' clients' + for c in clients: + c.is_muted = True + msg += ' ' + c.get_char_name() + ' [' + c.id + '],' + msg = msg[:-1] + msg += '.' + client.send_host_message('{}'.format(msg)) + except: + client.send_host_message("No targets found. Use /unmute for unmute.") def ooc_cmd_login(client, arg): if len(arg) == 0: -- cgit From 57fd4b9b9dcf81fb2d607ef11495acf11906fe01 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Wed, 29 Aug 2018 16:13:11 +0200 Subject: Fixed jukebox not emptying on toggle, and mod actions on multiple IPIDs. --- server/area_manager.py | 4 ++-- server/commands.py | 64 +++++++++++++++++++++++++------------------------- 2 files changed, 34 insertions(+), 34 deletions(-) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index e1b7e55..ad36362 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -168,8 +168,8 @@ class AreaManager: self.current_music = '' return - if vote_picked.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.char_id + 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: diff --git a/server/commands.py b/server/commands.py index 4c79c29..f951ca6 100644 --- a/server/commands.py +++ b/server/commands.py @@ -329,9 +329,9 @@ 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 .') + raise ArgumentError('You must specify a target. Use /kick ...') args = list(arg.split(' ')) - client.send_host_message('Attempting to ban {} IPIDs.'.format(len(args))) + client.send_host_message('Attempting to kick {} IPIDs.'.format(len(args))) for raw_ipid in args: try: ipid = int(raw_ipid) @@ -350,7 +350,7 @@ 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 .') + 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: @@ -375,7 +375,7 @@ 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 .') + 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: @@ -403,21 +403,21 @@ def ooc_cmd_mute(client, arg): args = list(arg.split(' ')) client.send_host_message('Attempting to mute {} IPIDs.'.format(len(args))) for raw_ipid in args: - try: + if raw_ipid.isdigit(): ipid = int(raw_ipid) - except: - raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) - try: - clients = client.server.client_manager.get_targets(client, TargetType.IPID, int(ipid), False) - msg = 'Muted ' + str(ipid) + ' clients' - for c in clients: - c.is_muted = True - msg += ' ' + c.get_char_name() + ' [' + c.id + '],' - msg = msg[:-1] - msg += '.' - client.send_host_message('{}'.format(msg)) - except: - client.send_host_message("No targets found. Use /mute for mute.") + clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) + if (clients): + msg = 'Muted ' + str(ipid) + ' clients' + for c in clients: + c.is_muted = True + 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: @@ -427,21 +427,21 @@ def ooc_cmd_unmute(client, arg): args = list(arg.split(' ')) client.send_host_message('Attempting to unmute {} IPIDs.'.format(len(args))) for raw_ipid in args: - try: + if raw_ipid.isdigit(): ipid = int(raw_ipid) - except: - raise ClientError('{} does not look like a valid IPID.'.format(raw_ipid)) - try: - clients = client.server.client_manager.get_targets(client, TargetType.IPID, int(ipid), False) - msg = 'Unmuted ' + str(ipid) + ' clients' - for c in clients: - c.is_muted = True - msg += ' ' + c.get_char_name() + ' [' + c.id + '],' - msg = msg[:-1] - msg += '.' - client.send_host_message('{}'.format(msg)) - except: - client.send_host_message("No targets found. Use /unmute for unmute.") + clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) + if (clients): + msg = 'Unmuted ' + str(ipid) + ' clients' + for c in clients: + c.is_muted = False + 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: -- cgit From c3e29d685079dd1090c305b84172983ed347a63c Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 30 Aug 2018 13:32:09 +0200 Subject: Stopped people from using Unicode format characters to pretend to be the server. --- server/aoprotocol.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 5bc94bb..8313a36 100644 --- a/server/aoprotocol.py +++ b/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): @@ -440,6 +441,10 @@ class AOProtocol(asyncio.Protocol): 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'): self.client.send_host_message('That name is reserved!') return -- cgit From dffd48711a1bd68421832867d69f589da64e972b Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 1 Sep 2018 21:54:36 +0200 Subject: Character selection enhancements. - Changing areas or switching characters updates the character availability list for everyone. - Taken and passworded on by default. --- server/client_manager.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/server/client_manager.py b/server/client_manager.py index c5e0b10..8323299 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -122,6 +122,7 @@ 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) @@ -193,6 +194,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) @@ -276,11 +278,7 @@ 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) @@ -292,6 +290,13 @@ class ClientManager: self.char_id = -1 self.send_done() + def get_available_char_list(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 + return char_list + def auth_mod(self, password): if self.is_mod: raise ClientError('Already logged in.') -- cgit From 69c58694ed033ed7a5fac44e2c701341210a9498 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 1 Sep 2018 22:06:56 +0200 Subject: `/getarea` expanded to show info from `/area`. --- server/client_manager.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/client_manager.py b/server/client_manager.py index 8323299..292af2a 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -207,7 +207,7 @@ class ClientManager: 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()) + owner = 'CM: {}'.format(client.get_char_name()) break msg += '\r\nArea {}: {} (users: {}) [{}][{}]{}'.format(area.abbreviation, area.name, len(area.clients), area.status, owner, lock[area.is_locked]) if self.area == area: @@ -221,6 +221,16 @@ class ClientManager: except AreaError: raise info += '=== {} ==='.format(area.name) + info += '\r\n' + + lock = {True: '[LOCKED]', False: ''} + owner = 'FREE' + if area.owned: + for client in [x for x in area.clients if x.is_cm]: + owner = 'CM: {}'.format(client.get_char_name()) + break + info += '[{}]: [{} users][{}][{}]{}'.format(area.abbreviation, len(area.clients), area.status, owner, lock[area.is_locked]) + sorted_clients = [] for client in area.clients: if (not mods) or client.is_mod: -- cgit From 7d207208dc94a47bdce9fd1ff682372eeb914b32 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 1 Sep 2018 22:27:05 +0200 Subject: Unmute fix, `/getarea` prettying. --- server/client_manager.py | 7 +------ server/commands.py | 6 +++--- 2 files changed, 4 insertions(+), 9 deletions(-) (limited to 'server') diff --git a/server/client_manager.py b/server/client_manager.py index 292af2a..7395217 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -224,12 +224,7 @@ class ClientManager: info += '\r\n' lock = {True: '[LOCKED]', False: ''} - owner = 'FREE' - if area.owned: - for client in [x for x in area.clients if x.is_cm]: - owner = 'CM: {}'.format(client.get_char_name()) - break - info += '[{}]: [{} users][{}][{}]{}'.format(area.abbreviation, len(area.clients), area.status, owner, lock[area.is_locked]) + info += '[{}]: [{} users][{}]{}'.format(area.abbreviation, len(area.clients), area.status, lock[area.is_locked]) sorted_clients = [] for client in area.clients: diff --git a/server/commands.py b/server/commands.py index f951ca6..a0dd295 100644 --- a/server/commands.py +++ b/server/commands.py @@ -809,10 +809,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))) -- cgit From a21dd24380a128bfdfa8ebca95d891aca94e1ef4 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 2 Sep 2018 00:56:42 +0200 Subject: Logging update. --- server/aoprotocol.py | 16 ++++++------- server/client_manager.py | 2 +- server/commands.py | 60 +++++++++++++++++++++++++++++++++--------------- server/logger.py | 18 +++++++++++++-- 4 files changed, 66 insertions(+), 30 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 8313a36..757eaa6 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -413,7 +413,7 @@ class AOProtocol(asyncio.Protocol): 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) 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) @@ -467,7 +467,7 @@ class AOProtocol(asyncio.Protocol): args[1] = self.client.disemvowel_message(args[1]) self.client.area.send_command('CT', 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): @@ -504,7 +504,7 @@ class AOProtocol(asyncio.Protocol): return showname = args[2] self.client.area.add_jukebox_vote(self.client, name, length, showname) - logger.log_server('[{}][{}]Added a jukebox vote for {}.'.format(self.client.area.id, self.client.get_char_name(), name), self.client) + 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] @@ -517,7 +517,7 @@ class AOProtocol(asyncio.Protocol): 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) + .format(self.client.area.abbreviation, self.client.get_char_name(), name), self.client) except ServerError: return except ClientError as ex: @@ -556,7 +556,7 @@ class AOProtocol(asyncio.Protocol): 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. @@ -573,7 +573,7 @@ class AOProtocol(asyncio.Protocol): 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 @@ -633,12 +633,12 @@ class AOProtocol(asyncio.Protocol): 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.get_ip(), self.client.area.id, self.client.get_char_name())) + 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.get_ip(), self.client.area.id, self.client.get_char_name(), args[0])) + 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/server/client_manager.py b/server/client_manager.py index 7395217..37d6f5b 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -124,7 +124,7 @@ class ClientManager: 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: diff --git a/server/commands.py b/server/commands.py index a0dd295..fab23d1 100644 --- a/server/commands.py +++ b/server/commands.py @@ -47,7 +47,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: @@ -59,7 +59,7 @@ def ooc_cmd_bglock(client,arg): 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) + 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: @@ -125,7 +125,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 @@ -154,7 +154,7 @@ def ooc_cmd_rollp(client, arg): 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)) 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 /roll and got {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), hashlib.sha1((str(roll) + SALT).encode('utf-8')).hexdigest() + '|' + SALT, val[0]), client) def ooc_cmd_currentmusic(client, arg): if len(arg) != 0: @@ -201,7 +201,7 @@ def ooc_cmd_jukebox_skip(client, arg): client.area.send_host_message('{} has forced a skip, restarting the only jukebox song.'.format(changer)) else: client.area.send_host_message('{} has forced a skip to the next jukebox song.'.format(changer)) - logger.log_server('[{}][{}]Skipped the current jukebox song.'.format(client.area.id, client.get_char_name())) + 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: @@ -256,7 +256,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: @@ -316,7 +316,7 @@ 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: @@ -340,7 +340,8 @@ def ooc_cmd_kick(client, arg): targets = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) if targets: for c in targets: - logger.log_server('Kicked {}.'.format(c.ipid), client) + 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.disconnect() else: @@ -370,6 +371,7 @@ def ooc_cmd_ban(client, arg): 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: @@ -384,6 +386,7 @@ def ooc_cmd_unban(client, arg): 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): @@ -393,7 +396,7 @@ 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: @@ -410,6 +413,8 @@ def ooc_cmd_mute(client, arg): msg = 'Muted ' + str(ipid) + ' 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 += '.' @@ -434,6 +439,8 @@ def ooc_cmd_unmute(client, arg): msg = 'Unmuted ' + str(ipid) + ' 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 += '.' @@ -454,6 +461,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: @@ -461,7 +469,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: @@ -471,7 +479,8 @@ 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: @@ -479,7 +488,8 @@ def ooc_cmd_m(client, arg): 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.id, client.get_char_name(), arg), client) + 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: @@ -488,7 +498,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: @@ -497,7 +508,8 @@ def ooc_cmd_announce(client, arg): 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) + 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: @@ -515,7 +527,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: @@ -530,11 +542,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): @@ -542,7 +554,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() @@ -554,7 +566,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 @@ -829,6 +841,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: @@ -846,6 +859,7 @@ def ooc_cmd_undisemvowel(client, arg): 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: @@ -865,6 +879,8 @@ 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())) @@ -882,6 +898,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): @@ -898,6 +916,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): @@ -914,6 +934,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): diff --git a/server/logger.py b/server/logger.py index 675a359..fb1b8b3 100644 --- a/server/logger.py +++ b/server/logger.py @@ -24,6 +24,7 @@ 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) @@ -44,6 +45,14 @@ def setup_logger(debug): 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 @@ -55,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) -- cgit From 34d6f6fa544ca90140e138c557fa651c74d76d4a Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 2 Sep 2018 10:49:55 +0200 Subject: Stopped people from pretending to say a modchat message. --- server/aoprotocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 757eaa6..5dbb192 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -445,7 +445,7 @@ class AOProtocol(asyncio.Protocol): 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'): + 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('/'): -- cgit From c8142f3f53ba926b4e5ab729ed1ca8c47998d4df Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 2 Sep 2018 22:51:20 +0200 Subject: Curse added: `/shake id`, `/unshake id`. Randomises word order in IC and OOC chat. --- server/aoprotocol.py | 4 ++++ server/client_manager.py | 8 ++++++++ server/commands.py | 38 +++++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 5dbb192..8516c9f 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -403,6 +403,8 @@ class AOProtocol(asyncio.Protocol): if pos not in ('def', 'pro', 'hld', 'hlp', 'jud', 'wit'): 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 @@ -463,6 +465,8 @@ 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]) diff --git a/server/client_manager.py b/server/client_manager.py index 37d6f5b..b11937c 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -47,6 +47,7 @@ class ClientManager: self.is_cm = False self.evi_list = [] self.disemvowel = False + self.shaken = False self.muted_global = False self.muted_adverts = False self.is_muted = False @@ -334,6 +335,13 @@ class ClientManager: 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() diff --git a/server/commands.py b/server/commands.py index fab23d1..c1d5ba8 100644 --- a/server/commands.py +++ b/server/commands.py @@ -855,7 +855,7 @@ 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) @@ -865,6 +865,42 @@ def ooc_cmd_undisemvowel(client, arg): 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_blockdj(client, arg): if not client.is_mod: raise ClientError('You must be authorized to do that.') -- cgit From 21f489a26194befb9398a4b53f1f06ffdccb75fd Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 2 Sep 2018 22:57:54 +0200 Subject: Fixed `/mods` showing all the areas that don't have mods. --- server/client_manager.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/client_manager.py b/server/client_manager.py index b11937c..29caff0 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -216,7 +216,7 @@ class ClientManager: 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: @@ -231,6 +231,8 @@ class ClientManager: for client in area.clients: if (not mods) or client.is_mod: sorted_clients.append(client) + 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' @@ -253,7 +255,7 @@ class ClientManager: for i in range(len(self.server.area_manager.areas)): if len(self.server.area_manager.areas[i].clients) > 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: -- cgit From 00bfa025a20025d06ac43eaf036ad76ac373b21b Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 2 Sep 2018 23:00:53 +0200 Subject: Mate `/mute` and `/unmute` clearer. --- server/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index c1d5ba8..0bd9c55 100644 --- a/server/commands.py +++ b/server/commands.py @@ -410,7 +410,7 @@ def ooc_cmd_mute(client, arg): ipid = int(raw_ipid) clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) if (clients): - msg = 'Muted ' + str(ipid) + ' 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) @@ -436,7 +436,7 @@ def ooc_cmd_unmute(client, arg): ipid = int(raw_ipid) clients = client.server.client_manager.get_targets(client, TargetType.IPID, ipid, False) if (clients): - msg = 'Unmuted ' + str(ipid) + ' 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) -- cgit From 739142f8ddd194b7f4ed42fe813979655d04262a Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 3 Sep 2018 03:52:16 +0200 Subject: Dual characters on screen Part I The basics have been laid out. - Communication about the second character established. - Pairing sytem made. - Placement system of the second character implemented. Needs: - More testing. - A workable UI. - Fix for zooms. --- server/aoprotocol.py | 35 ++++++++++++++++++++++++++++++++--- server/client_manager.py | 6 ++++++ 2 files changed, 38 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 8516c9f..800ae6a 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -342,12 +342,14 @@ class AOProtocol(asyncio.Protocol): self.ArgType.INT, self.ArgType.INT, self.ArgType.INT): 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 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): - msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname = args + self.ArgType.INT, self.ArgType.INT, self.ArgType.INT, self.ArgType.STR_OR_EMPTY, self.ArgType.INT, self.ArgType.INT): + msg_type, pre, folder, anim, text, pos, sfx, anim_type, cid, sfx_delay, button, evidence, flip, ding, color, showname, charid_pair, offset_pair = 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 @@ -412,8 +414,35 @@ 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 + self.client.last_sprite = anim + self.client.flip = flip + other_offset = 0 + other_emote = '' + other_flip = 0 + + 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 + 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) + sfx_delay, button, self.client.evi_list[evidence], flip, ding, color, showname, + charid_pair, other_emote, offset_pair, other_offset, other_flip) 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) diff --git a/server/client_manager.py b/server/client_manager.py index 29caff0..ec9a26a 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -57,6 +57,12 @@ 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 #flood-guard stuff self.mus_counter = 0 -- cgit From 22e0cb8e1a97e57a6235c23afc6508f5e441aefc Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 3 Sep 2018 12:55:57 +0200 Subject: Dual characters on screen Part 2. - UI option to change pairup. - Fixed a bug on the prosecution side. - Dual characters now allow for iniswapped characters. - Zooming now doesn't stay on the field, instead, the game defaults to the last emote. --- server/aoprotocol.py | 8 ++++++-- server/client_manager.py | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 800ae6a..31b45d9 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -420,11 +420,14 @@ class AOProtocol(asyncio.Protocol): # and an other offset. self.client.charid_pair = charid_pair self.client.offset_pair = offset_pair - self.client.last_sprite = anim + 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: @@ -434,6 +437,7 @@ class AOProtocol(asyncio.Protocol): other_offset = target.offset_pair other_emote = target.last_sprite other_flip = target.flip + other_folder = target.claimed_folder break if not confirmed: @@ -442,7 +446,7 @@ class AOProtocol(asyncio.Protocol): 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_emote, offset_pair, other_offset, other_flip) + charid_pair, other_folder, other_emote, offset_pair, other_offset, other_flip) 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) diff --git a/server/client_manager.py b/server/client_manager.py index ec9a26a..bb2bcd9 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -63,6 +63,7 @@ class ClientManager: self.offset_pair = 0 self.last_sprite = '' self.flip = 0 + self.claimed_folder = '' #flood-guard stuff self.mus_counter = 0 -- cgit From becf58dd4f9432364a64dc7af006b3938245127b Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 3 Sep 2018 15:55:34 +0200 Subject: Area list added. - Accessible with the ingame A/M button, or by `/switch_am`. - The music list now only lists music. - The area list lists the areas. - It describes general area properties (playercount, status, CM, locked). - Automatically updates as these change. - Clicking on an area behaves the same way as clicking on an area in the music list previously did. --- server/area_manager.py | 32 ++++++++++++++++++++++++++++++++ server/client_manager.py | 6 ++++++ server/commands.py | 3 +++ server/tsuserver.py | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index ad36362..6e024f6 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -71,12 +71,14 @@ class AreaManager: 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 + self.server.area_manager.send_arup_cms() if self.is_locked: self.unlock() @@ -84,6 +86,7 @@ class AreaManager: self.is_locked = False self.blankposting_allowed = True self.invite_list = {} + self.server.area_manager.send_arup_lock() self.send_host_message('This area is open now.') def is_char_available(self, char_id): @@ -240,6 +243,7 @@ class AreaManager: 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 @@ -333,3 +337,31 @@ class AreaManager: return name[:3].upper() else: return name.upper() + + 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' + for client in area.clients: + if client.is_cm: + cm = client.get_char_name() + 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) + self.server.send_arup(lock_list) diff --git a/server/client_manager.py b/server/client_manager.py index bb2bcd9..b1f8785 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -299,6 +299,12 @@ class ClientManager: 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): diff --git a/server/commands.py b/server/commands.py index 0bd9c55..134b685 100644 --- a/server/commands.py +++ b/server/commands.py @@ -691,6 +691,7 @@ def ooc_cmd_cm(client, arg): client.is_cm = True 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())) def ooc_cmd_uncm(client, arg): @@ -700,6 +701,7 @@ def ooc_cmd_uncm(client, arg): client.area.blankposting_allowed = True if client.area.is_locked: client.area.unlock() + client.server.area_manager.send_arup_cms() client.area.send_host_message('{} is no longer CM in this area.'.format(client.get_char_name())) else: raise ClientError('You cannot give up being the CM when you are not one') @@ -718,6 +720,7 @@ def ooc_cmd_area_lock(client, arg): client.send_host_message('Area is already locked.') if client.is_cm: client.area.is_locked = True + client.server.area_manager.send_arup_lock() client.area.send_host_message('Area is locked.') for i in client.area.clients: client.area.invite_list[i.id] = None diff --git a/server/tsuserver.py b/server/tsuserver.py index 97b4b90..8f5bf85 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -258,6 +258,40 @@ class TsuServer3: 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] in (0, 3): + for part_arg in args[1:]: + try: + sanitised = int(part_arg) + except: + return + elif args[0] in (1, 2): + 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') -- cgit From c395b9132eeccb10652f749866f40685ab7a14f5 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 3 Sep 2018 19:15:59 +0200 Subject: `/charcurse`, `/uncharcurse` and `/charids` commands. Curses a player to only be able to use the given characters. --- server/aoprotocol.py | 3 +++ server/client_manager.py | 10 +++++++- server/commands.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 31b45d9..3887678 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -358,6 +358,9 @@ class AOProtocol(asyncio.Protocol): 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 and text == ' ': self.client.send_host_message("Blankposting is forbidden in this area!") return diff --git a/server/client_manager.py b/server/client_manager.py index b1f8785..2310298 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -48,6 +48,7 @@ class ClientManager: self.evi_list = [] self.disemvowel = False self.shaken = False + self.charcurse = [] self.muted_global = False self.muted_adverts = False self.is_muted = False @@ -119,6 +120,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: @@ -312,7 +317,10 @@ class ClientManager: self.send_done() def get_available_char_list(self): - avail_char_ids = set(range(len(self.server.char_list))) - set([x.char_id for x in self.area.clients]) + 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 diff --git a/server/commands.py b/server/commands.py index 134b685..8c223eb 100644 --- a/server/commands.py +++ b/server/commands.py @@ -904,6 +904,73 @@ def ooc_cmd_unshake(client, arg): 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.') + try: + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg[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' + args = arg[1:].split() + for raw_cid in args: + 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).') + try: + targets = client.server.client_manager.get_targets(client, TargetType.ID, int(arg[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.') -- cgit From 2fe5d440f419b3bfad71345350458181c9164033 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 3 Sep 2018 21:51:58 +0200 Subject: Validation nightmare. --- server/aoprotocol.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 3887678..e9131a5 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -335,20 +335,35 @@ class AOProtocol(asyncio.Protocol): return if not self.client.area.can_send_message(self.client): return + 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 + 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 + 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.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 = 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!") -- cgit From 956a10c3791ed507b54259cd866929ccd47f20e6 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 4 Sep 2018 17:37:37 +0200 Subject: Fixed an argument reading error regarding `/charcurse` + `/randomchar` support for it. --- server/commands.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index 8c223eb..6f2beb0 100644 --- a/server/commands.py +++ b/server/commands.py @@ -654,10 +654,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: @@ -911,16 +914,16 @@ def ooc_cmd_charcurse(client, arg): 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(arg[0]), False) + 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' - args = arg[1:].split() - for raw_cid in args: + for raw_cid in args[1:]: try: cid = int(raw_cid) c.charcurse.append(cid) @@ -944,8 +947,9 @@ def ooc_cmd_uncharcurse(client, arg): 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(arg[0]), False) + 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: -- cgit From 4f30afa51d84be5ea16911b7e4fbd545a87e24b3 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 4 Sep 2018 21:19:10 +0200 Subject: The server now announces what features it has. I'm not sure why I did this. --- server/aoprotocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index e9131a5..21b0daa 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -213,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', 'cc_customshownames', 'characterpairs', 'arup') def net_cmd_ch(self, _): """ Periodically checks the connection. -- cgit From d0a6e081de9df220a1575bcd81056a04d79e9b42 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 4 Sep 2018 21:57:20 +0200 Subject: Blankpost filter is now more agressive + check for typing ' /' in OOC. --- server/aoprotocol.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 21b0daa..4e725d0 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -376,9 +376,16 @@ class AOProtocol(asyncio.Protocol): 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 and text == ' ': - self.client.send_host_message("Blankposting is forbidden in this area!") - 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(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 msg_type not in ('chat', '0', '1'): return if anim_type not in (0, 1, 2, 5, 6): @@ -501,6 +508,9 @@ class AOProtocol(asyncio.Protocol): 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() -- cgit From adfe21afd6e5f4efc9c60b45590f4c086b9e0e52 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 4 Sep 2018 22:45:07 +0200 Subject: Added `jur` and `sea` positions. --- server/aoprotocol.py | 2 +- server/client_manager.py | 4 ++-- server/evidence.py | 11 ++++++++++- 3 files changed, 13 insertions(+), 4 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 4e725d0..d7a2c6c 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -427,7 +427,7 @@ 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: diff --git a/server/client_manager.py b/server/client_manager.py index 2310298..5e6825b 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -345,8 +345,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): diff --git a/server/evidence.py b/server/evidence.py index ddd9ba3..efa2e25 100644 --- a/server/evidence.py +++ b/server/evidence.py @@ -24,7 +24,16 @@ 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': -- cgit From 93cd2ad3747ff609e0aa2175a2622afe9ef6b56d Mon Sep 17 00:00:00 2001 From: Cerapter Date: Wed, 5 Sep 2018 17:21:27 +0200 Subject: Non-interrupting pres. --- server/aoprotocol.py | 26 +++++++++++++++++++++++--- server/area_manager.py | 7 +++++-- 2 files changed, 28 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index d7a2c6c..d8d91d2 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -346,6 +346,7 @@ class AOProtocol(asyncio.Protocol): showname = "" charid_pair = -1 offset_pair = 0 + nonint_pre = '' 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, @@ -355,6 +356,7 @@ class AOProtocol(asyncio.Protocol): 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 = '' 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 @@ -363,8 +365,19 @@ class AOProtocol(asyncio.Protocol): 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.4.0 validation monstrosity. + # 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 = '' + 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 @@ -383,7 +396,7 @@ class AOProtocol(asyncio.Protocol): 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(text.replace(' ', '')) < 3 and text != '<' and text != '>': + 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 msg_type not in ('chat', '0', '1'): @@ -405,6 +418,13 @@ class AOProtocol(asyncio.Protocol): if len(showname) > 15: self.client.send_host_message("Your IC showname is way too long!") return + 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: @@ -471,7 +491,7 @@ class AOProtocol(asyncio.Protocol): 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) + 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) diff --git a/server/area_manager.py b/server/area_manager.py index 6e024f6..23c4339 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -26,7 +26,7 @@ from server.evidence import EvidenceList 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 = ''): + 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 = {} @@ -65,6 +65,7 @@ class AreaManager: self.is_locked = False 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 @@ -305,10 +306,12 @@ class AreaManager: 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'])) + 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): -- cgit From d0503eeb6ee783e571b7369edbdcc6710bee7ee7 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 6 Sep 2018 20:41:49 +0200 Subject: `/allow_blankposting` now catches ~~ too + CMs can use redtext now as well. --- server/aoprotocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index d8d91d2..44b4612 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -396,7 +396,7 @@ class AOProtocol(asyncio.Protocol): 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 != '>': + 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 msg_type not in ('chat', '0', '1'): @@ -435,7 +435,7 @@ class AOProtocol(asyncio.Protocol): button = 0 # Turn off the ding. ding = 0 - if color == 2 and not self.client.is_mod: + if color == 2 and not (self.client.is_mod or self.client.is_cm): color = 0 if color == 6: text = re.sub(r'[^\x00-\x7F]+',' ', text) #remove all unicode to prevent redtext abuse -- cgit From a08b254077288232058af09de9bac86f6a6865de Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 6 Sep 2018 22:29:23 +0200 Subject: Added the ability for mods and CMs to force non-interrupting pres in areas. --- server/commands.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index 6f2beb0..125ca80 100644 --- a/server/commands.py +++ b/server/commands.py @@ -100,6 +100,17 @@ def ooc_cmd_allow_blankposting(client, arg): client.area.send_host_message('A mod has set blankposting in the area to {}.'.format(answer[client.area.blankposting_allowed])) return +def ooc_cmd_force_nonint_pres(client, arg): + if not client.is_mod and not client.is_cm: + 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'} + if client.is_cm: + client.area.send_host_message('The CM has set pres in the area to be {}.'.format(answer[client.area.non_int_pres_only])) + else: + client.area.send_host_message('A mod has set pres in the area to be {}.'.format(answer[client.area.non_int_pres_only])) + return + def ooc_cmd_roll(client, arg): roll_max = 11037 if len(arg) != 0: -- cgit From 8006d40d1495a848b07a59bf514100648c82459c Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 15 Sep 2018 01:16:28 +0200 Subject: Fixed bugs regarding noninterrupting pres. - They are now actually non-interrupting when an interjection is played. - Realisation now happens at the start of the message if the pre is non-interrupting. --- server/aoprotocol.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 44b4612..4712656 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -418,6 +418,12 @@ class AOProtocol(asyncio.Protocol): if len(showname) > 15: self.client.send_host_message("Your IC showname is way too long!") return + if nonint_pre != '': + 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 -- cgit From 86f91ba3e862b683becbc2d35539bc06a636c925 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 15 Sep 2018 01:56:22 +0200 Subject: Public-facing area commands now announce who used them. --- server/commands.py | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index 125ca80..925eac3 100644 --- a/server/commands.py +++ b/server/commands.py @@ -58,7 +58,7 @@ 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)) + 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): @@ -94,10 +94,7 @@ def ooc_cmd_allow_blankposting(client, arg): raise ClientError('You must be authorized to do that.') client.area.blankposting_allowed = not client.area.blankposting_allowed answer = {True: 'allowed', False: 'forbidden'} - if client.is_cm: - client.area.send_host_message('The CM has set blankposting in the area to {}.'.format(answer[client.area.blankposting_allowed])) - else: - client.area.send_host_message('A mod has set blankposting in the area to {}.'.format(answer[client.area.blankposting_allowed])) + 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): @@ -105,10 +102,7 @@ def ooc_cmd_force_nonint_pres(client, arg): 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'} - if client.is_cm: - client.area.send_host_message('The CM has set pres in the area to be {}.'.format(answer[client.area.non_int_pres_only])) - else: - client.area.send_host_message('A mod has set pres in the area to be {}.'.format(answer[client.area.non_int_pres_only])) + 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): @@ -186,12 +180,7 @@ def ooc_cmd_jukebox_toggle(client, arg): raise ArgumentError('This command has no arguments.') client.area.jukebox = not client.area.jukebox client.area.jukebox_votes = [] - changer = 'Unknown' - if client.is_cm: - changer = 'The CM' - elif client.is_mod: - changer = 'A mod' - client.area.send_host_message('{} has set the jukebox to {}.'.format(changer, client.area.jukebox)) + 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.is_cm: @@ -203,15 +192,10 @@ def ooc_cmd_jukebox_skip(client, arg): if len(client.area.jukebox_votes) == 0: raise ClientError('There is no song playing right now, skipping is pointless.') client.area.start_jukebox() - changer = 'Unknown' - if client.is_cm: - changer = 'The CM' - elif client.is_mod: - changer = 'A mod' if len(client.area.jukebox_votes) == 1: - client.area.send_host_message('{} has forced a skip, restarting the only jukebox song.'.format(changer)) + 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(changer)) + 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): -- cgit From fcd8f5b5abb2329aded120007319d581908c8a69 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 15 Sep 2018 02:33:18 +0200 Subject: Areas can now be spectatable, too. - Makes it so that people can join, but can't type IC unless invited. - The CM can set it with `/area_spectate`. --- server/area_manager.py | 30 +++++++++++++++++++++++++----- server/client_manager.py | 9 +++++---- server/commands.py | 24 ++++++++++++++++-------- server/tsuserver.py | 6 +++--- 4 files changed, 49 insertions(+), 20 deletions(-) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 23c4339..d8e60d0 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -22,6 +22,7 @@ import yaml from server.exceptions import AreaError from server.evidence import EvidenceList +from enum import Enum class AreaManager: @@ -63,13 +64,18 @@ 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 + 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() @@ -80,15 +86,29 @@ class AreaManager: client.is_cm = False self.owned = False self.server.area_manager.send_arup_cms() - if self.is_locked: + if self.is_locked != self.Locked.FREE: self.unlock() 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 + 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 + 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] @@ -215,7 +235,7 @@ class AreaManager: def can_send_message(self, client): - if self.is_locked and not client.is_mod and not client.id in self.invite_list: + if self.is_locked != self.Locked.FREE and not client.is_mod and not client.id in self.invite_list: 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 @@ -366,5 +386,5 @@ class AreaManager: def send_arup_lock(self): lock_list = [3] for area in self.areas: - lock_list.append(area.is_locked) + lock_list.append(area.is_locked.name) self.server.send_arup(lock_list) diff --git a/server/client_manager.py b/server/client_manager.py index 5e6825b..617bb69 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -182,9 +182,10 @@ 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.id in area.invite_list: - #self.send_host_message('This area is locked - you will be unable to send messages ICly.') + 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) @@ -215,13 +216,13 @@ 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 = 'CM: {}'.format(client.get_char_name()) break + 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 += ' [*]' @@ -236,7 +237,7 @@ class ClientManager: info += '=== {} ==='.format(area.name) info += '\r\n' - lock = {True: '[LOCKED]', False: ''} + 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 = [] diff --git a/server/commands.py b/server/commands.py index 925eac3..c84d8b7 100644 --- a/server/commands.py +++ b/server/commands.py @@ -697,7 +697,7 @@ def ooc_cmd_uncm(client, arg): client.is_cm = False client.area.owned = False client.area.blankposting_allowed = True - if client.area.is_locked: + if client.area.is_locked != client.area.Locked.FREE: client.area.unlock() client.server.area_manager.send_arup_cms() client.area.send_host_message('{} is no longer CM in this area.'.format(client.get_char_name())) @@ -714,20 +714,28 @@ 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.server.area_manager.send_arup_lock() - client.area.send_host_message('Area is locked.') - for i in client.area.clients: - client.area.invite_list[i.id] = None + 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.is_cm: + 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: raise ClientError('Only CM can unlock area.') diff --git a/server/tsuserver.py b/server/tsuserver.py index 8f5bf85..7ef4f5e 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -268,7 +268,7 @@ class TsuServer3: CM: ARUP#2#####... Lockedness: - ARUP#3#####... + ARUP#3#####... """ if len(args) < 2: @@ -277,13 +277,13 @@ class TsuServer3: if args[0] not in (0,1,2,3): return - if args[0] in (0, 3): + if args[0] == 0: for part_arg in args[1:]: try: sanitised = int(part_arg) except: return - elif args[0] in (1, 2): + elif args[0] in (1, 2, 3): for part_arg in args[1:]: try: sanitised = str(part_arg) -- cgit From d54064d892c26a26641e6ed67412b33d7db3f6c2 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 15 Sep 2018 03:33:10 +0200 Subject: Server messages are now coloured differently. --- server/area_manager.py | 2 +- server/client_manager.py | 4 ++-- server/commands.py | 2 +- server/districtclient.py | 2 +- server/tsuserver.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index d8e60d0..942ad63 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -124,7 +124,7 @@ class AreaManager: 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') def set_next_msg_delay(self, msg_length): delay = min(3000, 100 + 60 * msg_length) diff --git a/server/client_manager.py b/server/client_manager.py index 617bb69..e7655bd 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -94,7 +94,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'])) @@ -272,7 +272,7 @@ class ClientManager: 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) diff --git a/server/commands.py b/server/commands.py index c84d8b7..a6e2999 100644 --- a/server/commands.py +++ b/server/commands.py @@ -502,7 +502,7 @@ 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)) + '=== 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) diff --git a/server/districtclient.py b/server/districtclient.py index adc29ec..c766ba5 100644 --- a/server/districtclient.py +++ b/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/server/tsuserver.py b/server/tsuserver.py index 7ef4f5e..5af8161 100644 --- a/server/tsuserver.py +++ b/server/tsuserver.py @@ -253,8 +253,8 @@ class TsuServer3: 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), 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)) -- cgit From 0032c36822e149705efa975cd658b1661be2904d Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 15 Sep 2018 03:40:23 +0200 Subject: Auto-reset an area's status to idle if it empties out. --- server/area_manager.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'server') diff --git a/server/area_manager.py b/server/area_manager.py index 942ad63..328a231 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -82,6 +82,8 @@ class AreaManager: def remove_client(self, client): self.clients.remove(client) + if len(self.clients) == 0: + self.change_status('IDLE') if client.is_cm: client.is_cm = False self.owned = False -- cgit From f3e9d691afbf70ec992fe4726865c9b9e83fac1b Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 15 Sep 2018 15:00:41 +0200 Subject: Forbade spectators from interacting IC. --- server/aoprotocol.py | 9 +++++++++ server/area_manager.py | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 4712656..f07571d 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -577,6 +577,9 @@ class AOProtocol(asyncio.Protocol): if not self.client.is_dj: self.client.send_host_message('You were blockdj\'d by a moderator.') return + if 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: @@ -629,6 +632,9 @@ class AOProtocol(asyncio.Protocol): 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': @@ -658,6 +664,9 @@ 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: diff --git a/server/area_manager.py b/server/area_manager.py index 328a231..68eea42 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -237,11 +237,14 @@ class AreaManager: def can_send_message(self, client): - if self.is_locked != self.Locked.FREE and not client.is_mod and not client.id 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.') -- cgit From 54dc437f5ddab385d1f4cb9d8f1052b924620da4 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sat, 15 Sep 2018 18:39:37 +0200 Subject: Changed how `/rollp` works, now announces the results to CM too. --- server/commands.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index a6e2999..57b0c3f 100644 --- a/server/commands.py +++ b/server/commands.py @@ -140,13 +140,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 = '' @@ -156,10 +156,15 @@ 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)) + + for c in client.area.clients: + if c.is_cm: + c.send_host_message('{} secretly rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) + else: + c.send_host_message('{} rolled in secret.'.format(client.get_char_name())) + logger.log_server( - '[{}][{}]Used /roll and got {} out of {}.'.format(client.area.abbreviation, client.get_char_name(), hashlib.sha1((str(roll) + SALT).encode('utf-8')).hexdigest() + '|' + SALT, val[0]), client) + '[{}][{}]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: -- cgit From 851d1de1be6db6bcddfcc6a501e9eadc49b4f548 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Sun, 16 Sep 2018 20:50:22 +0200 Subject: Websocket update. --- server/aoprotocol.py | 5 ++--- server/websocket.py | 9 ++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index f07571d..2adfc91 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -87,8 +87,7 @@ class AOProtocol(asyncio.Protocol): self.client.disconnect() for msg in self.get_messages(): if len(msg) < 2: - self.client.disconnect() - return + continue # general netcode structure is not great if msg[0] in ('#', '3', '4'): if msg[0] == '#': @@ -100,7 +99,7 @@ class AOProtocol(asyncio.Protocol): cmd, *args = msg.split('#') self.net_cmd_dispatcher[cmd](self, args) except KeyError: - return + logger.log_debug('[INC][UNK]{}'.format(msg), self.client) def connection_made(self, transport): """ Called upon a new client connecting diff --git a/server/websocket.py b/server/websocket.py index d77f678..ba4258f 100644 --- a/server/websocket.py +++ b/server/websocket.py @@ -109,14 +109,17 @@ class WebSocket: 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[2:6] + masks = data[mask_offset:mask_offset + 4] decoded = "" - for char in data[6:payload_length + 6]: + for char in data[mask_offset + 4:payload_length + mask_offset + 4]: char ^= masks[len(decoded) % 4] decoded += chr(char) @@ -209,4 +212,4 @@ class WebSocket: return response_key.decode('ASCII') def finish(self): - self.protocol.connection_lost(self) \ No newline at end of file + self.protocol.connection_lost(self) -- cgit From ce05b587481acea35751b12091e01b5de4bcba87 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Mon, 17 Sep 2018 20:53:07 +0200 Subject: Send the banned package to banned users. --- server/aoprotocol.py | 1 + 1 file changed, 1 insertion(+) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 2adfc91..bea9e35 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -172,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) -- cgit From 0156849cc28c2cf1f32847beef6a0b27db6b5747 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 18 Sep 2018 19:51:20 +0200 Subject: BEGINNINGS! of the multi-CM system. - Allows people to become CMs of multiple areas. - Allows people to appoint CMs besides themselves using `/cm id]`. - CMs can now freely leave the areas they CM in without losing CM status. - CMs that own an area, but aren't there, still show up in them when using `/getarea` with the `[RCM]` = Remote Case Manager tag. - CMs get all IC and OOC messages from their areas. - CMs can use `/s` to send a message to all the areas they own, or `/a [area_id]` to send a message only to a specific area. This is usable both IC and OOC. --- server/aoprotocol.py | 36 ++++++++++++++- server/area_manager.py | 37 +++++++++++---- server/client_manager.py | 30 ++++++++---- server/commands.py | 116 +++++++++++++++++++++++++++++++++++------------ server/evidence.py | 6 +-- 5 files changed, 172 insertions(+), 53 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index bea9e35..bd022a4 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -335,6 +335,8 @@ class AOProtocol(asyncio.Protocol): 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, @@ -399,6 +401,28 @@ class AOProtocol(asyncio.Protocol): 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): @@ -441,7 +465,7 @@ class AOProtocol(asyncio.Protocol): button = 0 # Turn off the ding. ding = 0 - if color == 2 and not (self.client.is_mod or self.client.is_cm): + 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 @@ -498,6 +522,15 @@ class AOProtocol(asyncio.Protocol): 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) @@ -557,6 +590,7 @@ class AOProtocol(asyncio.Protocol): 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) diff --git a/server/area_manager.py b/server/area_manager.py index 68eea42..cfb2be0 100644 --- a/server/area_manager.py +++ b/server/area_manager.py @@ -54,7 +54,6 @@ class AreaManager: self.showname_changes_allowed = showname_changes_allowed self.shouts_allowed = shouts_allowed self.abbreviation = abbreviation - self.owned = False self.cards = dict() """ @@ -71,6 +70,8 @@ class AreaManager: self.jukebox_votes = [] self.jukebox_prev_char_id = -1 + self.owners = [] + class Locked(Enum): FREE = 1, SPECTATABLE = 2, @@ -84,12 +85,6 @@ class AreaManager: self.clients.remove(client) if len(self.clients) == 0: self.change_status('IDLE') - if client.is_cm: - client.is_cm = False - self.owned = False - self.server.area_manager.send_arup_cms() - if self.is_locked != self.Locked.FREE: - self.unlock() def unlock(self): self.is_locked = self.Locked.FREE @@ -102,6 +97,8 @@ class AreaManager: 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.') @@ -109,6 +106,8 @@ class AreaManager: 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.') @@ -124,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, '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) @@ -300,6 +305,14 @@ 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): @@ -365,6 +378,11 @@ class AreaManager: 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] @@ -382,9 +400,8 @@ class AreaManager: cms_list = [2] for area in self.areas: cm = 'FREE' - for client in area.clients: - if client.is_cm: - cm = client.get_char_name() + if len(area.owners) > 0: + cm = area.get_cms() cms_list.append(cm) self.server.send_arup(cms_list) diff --git a/server/client_manager.py b/server/client_manager.py index e7655bd..f5ef4ef 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -44,7 +44,6 @@ 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 @@ -140,7 +139,7 @@ class ClientManager: .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']: @@ -157,7 +156,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']: @@ -218,10 +217,8 @@ class ClientManager: msg = '=== Areas ===' 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 = 'CM: {}'.format(client.get_char_name()) - break + 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: @@ -244,13 +241,19 @@ class ClientManager: 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.is_cm: - info +='[CM]' + 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) @@ -266,7 +269,7 @@ 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 += '{}'.format(self.get_area_info(i, mods)) info = 'Current online: {}'.format(cnt) + info @@ -382,6 +385,13 @@ 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/server/commands.py b/server/commands.py index 57b0c3f..992d0c7 100644 --- a/server/commands.py +++ b/server/commands.py @@ -23,6 +23,34 @@ 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) def ooc_cmd_switch(client, arg): if len(arg) == 0: @@ -90,7 +118,7 @@ def ooc_cmd_allow_iniswap(client, arg): return def ooc_cmd_allow_blankposting(client, arg): - if not client.is_mod and not client.is_cm: + 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'} @@ -98,7 +126,7 @@ def ooc_cmd_allow_blankposting(client, arg): return def ooc_cmd_force_nonint_pres(client, arg): - if not client.is_mod and not client.is_cm: + 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'} @@ -179,7 +207,7 @@ def ooc_cmd_currentmusic(client, arg): client.area.current_music_player)) def ooc_cmd_jukebox_toggle(client, arg): - if not client.is_mod and not client.is_cm: + 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.') @@ -188,7 +216,7 @@ def ooc_cmd_jukebox_toggle(client, arg): 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.is_cm: + 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.') @@ -276,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() @@ -689,25 +717,55 @@ 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.server.area_manager.send_arup_cms() - client.area.send_host_message('{} is CM in this area now.'.format(client.get_char_name())) + 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.is_cm: - client.is_cm = False - client.area.owned = False - client.area.blankposting_allowed = True - if client.area.is_locked != client.area.Locked.FREE: - client.area.unlock() - client.server.area_manager.send_arup_cms() - client.area.send_host_message('{} is no longer CM in this area.'.format(client.get_char_name())) + 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 cannot give up being the CM when you are not one') + raise ClientError('You must be authorized to do that.') def ooc_cmd_unmod(client, arg): client.is_mod = False @@ -721,7 +779,7 @@ def ooc_cmd_area_lock(client, arg): return if client.area.is_locked == client.area.Locked.LOCKED: client.send_host_message('Area is already locked.') - if client.is_cm: + if client in client.area.owners: client.area.lock() return else: @@ -733,7 +791,7 @@ def ooc_cmd_area_spectate(client, arg): return if client.area.is_locked == client.area.Locked.SPECTATABLE: client.send_host_message('Area is already spectatable.') - if client.is_cm: + if client in client.area.owners: client.area.spectator() return else: @@ -742,7 +800,7 @@ def ooc_cmd_area_spectate(client, arg): 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.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.') @@ -750,9 +808,9 @@ 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 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.') try: c = client.server.client_manager.get_targets(client, TargetType.ID, int(arg), False)[0] @@ -763,9 +821,9 @@ def ooc_cmd_invite(client, arg): raise ClientError('You must specify a target. Use /invite ') def ooc_cmd_uninvite(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.') - 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 ') @@ -776,7 +834,7 @@ 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: + if client.area.is_locked != client.area.Locked.FREE: client.area.invite_list.pop(c.id) except AreaError: raise @@ -788,7 +846,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 #]') @@ -809,7 +867,7 @@ 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: + if client.area.is_locked != client.area.Locked.FREE: client.area.invite_list.pop(c.id) except AreaError: raise @@ -1070,7 +1128,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.') diff --git a/server/evidence.py b/server/evidence.py index efa2e25..b34172a 100644 --- a/server/evidence.py +++ b/server/evidence.py @@ -39,13 +39,13 @@ class EvidenceList: 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 -- cgit From c68c9daf27294698c6b4ae249f1ba7d817f10f89 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 18 Sep 2018 21:17:57 +0200 Subject: CMs now get `/rollp` results + KK and KB package. --- server/commands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index 992d0c7..5f0128a 100644 --- a/server/commands.py +++ b/server/commands.py @@ -185,11 +185,9 @@ def ooc_cmd_rollp(client, arg): roll = '(' + roll + ')' client.send_host_message('{} rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) - for c in client.area.clients: - if c.is_cm: - c.send_host_message('{} secretly rolled {} out of {}.'.format(client.get_char_name(), roll, val[0])) - else: - c.send_host_message('{} rolled in secret.'.format(client.get_char_name())) + 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) @@ -371,6 +369,7 @@ def ooc_cmd_kick(client, arg): 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)) @@ -395,6 +394,7 @@ def ooc_cmd_ban(client, arg): 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)) -- cgit From ee5b4b92de11dafbabb2f9b7c695d174e2a13694 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 18 Sep 2018 21:58:39 +0200 Subject: Fixed a typo. --- server/aoprotocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index bd022a4..1c6146e 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -611,7 +611,7 @@ class AOProtocol(asyncio.Protocol): if not self.client.is_dj: self.client.send_host_message('You were blockdj\'d by a moderator.') return - if area.cannot_ic_interact(self.client): + 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): -- cgit From 34465189a34975f6eed125bce4c4faee501c57d5 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 18 Sep 2018 22:21:40 +0200 Subject: Fixed OOC messages they sent not showing for the CM in other areas. --- server/commands.py | 1 + 1 file changed, 1 insertion(+) (limited to 'server') diff --git a/server/commands.py b/server/commands.py index 5f0128a..aae12de 100644 --- a/server/commands.py +++ b/server/commands.py @@ -51,6 +51,7 @@ def message_areas_cm(client, areas, message): 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: -- cgit From b73036724aea682c3860a04968e71f911a08ea18 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 20 Sep 2018 16:41:32 +0200 Subject: Rolled all the special IC stuff into one FL packet piece. So now, a standard CCCC server uses three bonus packets: `modcall_reason`, `cccc_ic_support` and `arup`. --- server/aoprotocol.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 1c6146e..18f688b 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -213,7 +213,7 @@ class AOProtocol(asyncio.Protocol): self.client.is_ao2 = True - self.client.send_command('FL', 'yellowtext', 'customobjections', 'flipping', 'fastloading', 'noencryption', 'deskmod', 'evidence', 'modcall_reason', 'cc_customshownames', 'characterpairs', 'arup') + self.client.send_command('FL', 'yellowtext', 'customobjections', 'flipping', 'fastloading', 'noencryption', 'deskmod', 'evidence', 'modcall_reason', 'cccc_ic_support', 'arup') def net_cmd_ch(self, _): """ Periodically checks the connection. @@ -348,7 +348,7 @@ class AOProtocol(asyncio.Protocol): showname = "" charid_pair = -1 offset_pair = 0 - nonint_pre = '' + 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, @@ -358,7 +358,7 @@ class AOProtocol(asyncio.Protocol): 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 = '' + 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 @@ -369,7 +369,7 @@ class AOProtocol(asyncio.Protocol): 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 = '' + 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 @@ -442,7 +442,7 @@ class AOProtocol(asyncio.Protocol): if len(showname) > 15: self.client.send_host_message("Your IC showname is way too long!") return - if nonint_pre != '': + if nonint_pre == 1: if button in (1, 2, 3, 4, 23): if anim_type == 1 or anim_type == 2: anim_type = 0 -- cgit From 1ddfdb34b1e158f4de790b63385b4e9cbebdbefe Mon Sep 17 00:00:00 2001 From: Cerapter Date: Thu, 20 Sep 2018 18:41:40 +0200 Subject: Fixed a small bug regarding music changes while shownamed. --- server/aoprotocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 18f688b..1bf9001 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -627,10 +627,10 @@ class AOProtocol(asyncio.Protocol): if self.client.area.jukebox: showname = '' if len(args) > 2: - if len(args[2]) > 0 and not self.client.area.showname_changes_allowed: + 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 - showname = args[2] 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: -- cgit From de8badc9a6e74ca29cbc04ab5438d6eed2eb8984 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 23 Oct 2018 16:15:15 +0200 Subject: Support for case alerts serverside. - Users can use an ingame button to alert people of cases. --- server/aoprotocol.py | 2 +- server/client_manager.py | 15 +++++++++++++++ server/commands.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 1bf9001..0af8f67 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -213,7 +213,7 @@ class AOProtocol(asyncio.Protocol): self.client.is_ao2 = True - self.client.send_command('FL', 'yellowtext', 'customobjections', 'flipping', 'fastloading', 'noencryption', 'deskmod', 'evidence', 'modcall_reason', 'cccc_ic_support', 'arup') + 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. diff --git a/server/client_manager.py b/server/client_manager.py index f5ef4ef..4a5c1ef 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -65,6 +65,15 @@ class ClientManager: 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.case_call_time = 0 + #flood-guard stuff self.mus_counter = 0 self.mus_mute_time = 0 @@ -359,6 +368,12 @@ 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) diff --git a/server/commands.py b/server/commands.py index aae12de..49fd979 100644 --- a/server/commands.py +++ b/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 @@ -767,6 +768,53 @@ def ooc_cmd_uncm(client, arg): 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" + +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": + 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") + + msg = msg + ', '.join(lookingfor) + '.\r\n==================' + + client.server.send_all_cmd_pred('CASEA', msg, args[1], args[2], args[3], args[4], '1') + + client.set_case_call_delay() + + logger.log_server('[{}][{}][CASE_ANNOUNCEMENT]{}, DEF: {}, PRO: {}, JUD: {}, JUR: {}.'.format(client.area.abbreviation, client.get_char_name(), args[0], args[1], args[2], args[3], args[4]), 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 -- cgit From 962289793d97357b69e228a0b52737681d2ea0b0 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Tue, 23 Oct 2018 16:34:39 +0200 Subject: Added support for the stenographer role in case alerts. --- server/client_manager.py | 1 + server/commands.py | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'server') diff --git a/server/client_manager.py b/server/client_manager.py index 4a5c1ef..432c39d 100644 --- a/server/client_manager.py +++ b/server/client_manager.py @@ -72,6 +72,7 @@ class ClientManager: self.casing_pro = False self.casing_jud = False self.casing_jur = False + self.casing_steno = False self.case_call_time = 0 #flood-guard stuff diff --git a/server/commands.py b/server/commands.py index 49fd979..d02eff2 100644 --- a/server/commands.py +++ b/server/commands.py @@ -780,6 +780,7 @@ def ooc_cmd_setcase(client, arg): 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: @@ -791,7 +792,7 @@ def ooc_cmd_anncase(client, arg): 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": + 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]) @@ -805,14 +806,16 @@ def ooc_cmd_anncase(client, arg): 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], '1') + 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: {}.'.format(client.area.abbreviation, client.get_char_name(), args[0], args[1], args[2], args[3], args[4]), client) + 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!') -- cgit From 1f8b4944ca3a4a95b1f73c9275facee12f637c14 Mon Sep 17 00:00:00 2001 From: Cerapter Date: Fri, 2 Nov 2018 15:28:31 +0100 Subject: A safety measure regarding the IC commands `/a` and `/s`. Made it so they actually require the command plus a space, so that things like `/area` in IC don't get caught. --- server/aoprotocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/aoprotocol.py b/server/aoprotocol.py index 0af8f67..2cf6fb4 100644 --- a/server/aoprotocol.py +++ b/server/aoprotocol.py @@ -401,7 +401,7 @@ class AOProtocol(asyncio.Protocol): 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'): + if text.startswith('/a '): part = text.split(' ') try: aid = int(part[1]) @@ -414,7 +414,7 @@ class AOProtocol(asyncio.Protocol): except ValueError: self.client.send_host_message("That does not look like a valid area ID!") return - elif text.startswith('/s'): + elif text.startswith('/s '): part = text.split(' ') for a in self.server.area_manager.areas: if self.client in a.owners: -- cgit