Move configuration to command line only

This commit is contained in:
Ben
2019-12-02 23:11:46 +00:00
parent 3e7943c6db
commit ade40f914b
11 changed files with 139 additions and 264 deletions

View File

@@ -1,5 +1,4 @@
import inspect
import config
from houdini import handlers
from houdini import plugins
@@ -46,12 +45,12 @@ class _CommandGroup(_Command):
def command(name=None, **kwargs):
return _listener(_Command, name, string_delimiter=config.commands['StringDelimiters'],
return _listener(_Command, name, string_delimiter=['"', "'"],
string_separator=' ', **kwargs)
def group(name=None, **kwargs):
return _listener(_CommandGroup, name, string_delimiter=config.commands['StringDelimiters'],
return _listener(_CommandGroup, name, string_delimiter=['"', "'"],
string_separator=' ', **kwargs)
@@ -59,7 +58,6 @@ cooldown = handlers.cooldown
check = handlers.check
player_attribute = handlers.player_attribute
player_data_attribute = handlers.player_data_attribute
player_in_room = handlers.player_in_room
@@ -84,13 +82,14 @@ class CommandManager(_AbstractManager):
for name in command_object.alias:
if name in parent_commands and len(parent_commands[name]):
conflict_command = parent_commands[name][0]
if config.commands['ConflictMode'] == ConflictResolution.Exception:
conflict_resolution = self.server.config.command_conflict_mode
if conflict_resolution == ConflictResolution.Exception:
raise NameError(f'Command name conflict: \'{name}\' from plugin \'{module.__class__.__name__}\' '
f'conflicts with \'{conflict_command.name}\' from '
f'module \'{conflict_command.instance.__class__.__name__}\'')
elif config.commands['ConflictMode'] == ConflictResolution.Append:
elif conflict_resolution == ConflictResolution.Append:
parent_commands[name].append(command_object)
elif config.commands['ConflictMode'] == ConflictResolution.Silent:
elif conflict_resolution == ConflictResolution.Silent:
module.server.logger.warning(f'Command \'{name}\' from module \'{module.__class__.__name__}\' '
f'disabled due to conflict with '
f'\'{conflict_command.instance.__class__.__name__}\'')
@@ -102,25 +101,21 @@ def is_command(command_object):
return issubclass(type(command_object), _Command)
if type(config.commands['Prefix']) == str:
config.commands['Prefix'] = [config.commands['Prefix']]
def has_command_prefix(command_string):
for prefix in config.commands['Prefix']:
def has_command_prefix(pre, command_string):
for prefix in pre:
if command_string.startswith(prefix):
return True
return False
def get_command_prefix(command_string):
for prefix in config.commands['Prefix']:
def get_command_prefix(pre, command_string):
for prefix in pre:
if command_string.startswith(prefix):
return prefix
async def invoke_command_string(commands, p, command_string):
prefix = get_command_prefix(command_string)
prefix = get_command_prefix(p.server.config.command_prefix, command_string)
no_prefix = command_string[len(prefix):]
data = no_prefix.split(' ')

View File

@@ -1,5 +1,3 @@
import config
from houdini import handlers
from houdini.handlers import XMLPacket
from houdini.converters import VersionChkConverter
@@ -11,13 +9,13 @@ from houdini.data.buddy import BuddyList
@handlers.handler(XMLPacket('verChk'))
@handlers.allow_once
async def handle_version_check(p, version: VersionChkConverter):
if config.client['MultiClientSupport']:
if config.client['LegacyVersionChk'] == version:
if not p.server.config.single_client_mode:
if p.server.config.legacy_version == version:
p.client_type = ClientType.Legacy
elif config.client['VanillaVersionChk'] == version:
elif p.server.config.vanilla_version == version:
p.client_type = ClientType.Vanilla
elif config.client['DefaultVersionChk'] == version:
p.client_type = config.client['DefaultClientType']
elif p.server.config.default_version == version:
p.client_type = p.server.config.default_version
if p.client_type is None:
await p.send_xml({'body': {'action': 'apiKO', 'r': '0'}})
@@ -28,32 +26,31 @@ async def handle_version_check(p, version: VersionChkConverter):
@handlers.handler(XMLPacket('rndK'))
@handlers.allow_once
async def handle_random_key(p, data):
await p.send_xml({'body': {'action': 'rndK', 'r': '-1'}, 'k': config.client['AuthStaticKey']})
async def handle_random_key(p, _):
await p.send_xml({'body': {'action': 'rndK', 'r': '-1'}, 'k': p.server.config.auth_key})
async def get_server_presence(p, pid):
buddy_worlds = []
world_populations = []
for server_name, server_config in config.servers.items():
if server_config['World']:
server_population = await p.server.redis.hget('houdini.population', server_config['Id'])
server_population = (7 if int(server_population) == server_config['Capacity']
else int(server_population) // (server_config['Capacity'] // 6)) \
if server_population else 0
pops = await p.server.redis.hgetall('houdini.population')
for server_id, server_population in pops.items():
server_population = (7 if int(server_population) == p.server.config.capacity
else int(server_population) // (p.server.config.capacity // 6)) \
if server_population else 0
world_populations.append(f'{server_config["Id"]},{server_population}')
world_populations.append(f'{int(server_id)},{int(server_population)}')
server_key = f'houdini.players.{server_config["Id"]}'
if await p.server.redis.scard(server_key):
async with p.server.db.transaction():
buddies = BuddyList.select('buddy_id').where(BuddyList.penguin_id == pid).gino.iterate()
tr = p.server.redis.multi_exec()
async for buddy_id, in buddies:
tr.sismember(server_key, buddy_id)
online_buddies = await tr.execute()
if any(online_buddies):
buddy_worlds.append(server_config['Id'])
server_key = f'houdini.players.{int(server_id)}'
if await p.server.redis.scard(server_key):
async with p.server.db.transaction():
buddies = BuddyList.select('buddy_id').where(BuddyList.penguin_id == pid).gino.iterate()
tr = p.server.redis.multi_exec()
async for buddy_id, in buddies:
tr.sismember(server_key, buddy_id)
online_buddies = await tr.execute()
if any(online_buddies):
buddy_worlds.append(str(int(server_id)))
return '|'.join(world_populations), '|'.join(buddy_worlds)

View File

@@ -41,19 +41,19 @@ async def handle_login(p, credentials: Credentials):
if await p.server.redis.exists(flood_key):
tr = p.server.redis.multi_exec()
tr.incr(flood_key)
tr.expire(flood_key, p.server.server_config['LoginFailureTimer'])
tr.expire(flood_key, p.server.config.login_failure_timer)
failure_count, _ = await tr.execute()
if failure_count >= p.server.server_config['LoginFailureLimit']:
if failure_count >= p.server.config.login_failure_limit:
return await p.send_error_and_disconnect(150)
else:
await p.server.redis.setex(flood_key, p.server.server_config['LoginFailureTimer'], 1)
await p.server.redis.setex(flood_key, p.server.config.login_failure_timer, 1)
return await p.send_error_and_disconnect(101)
failure_count = await p.server.redis.get(flood_key)
if failure_count:
max_attempts_exceeded = int(failure_count) >= p.server.server_config['LoginFailureLimit']
max_attempts_exceeded = int(failure_count) >= p.server.config.login_failure_limit
if max_attempts_exceeded:
return await p.send_error_and_disconnect(150)
@@ -98,8 +98,8 @@ async def handle_login(p, credentials: Credentials):
confirmation_hash = Crypto.hash(os.urandom(24))
tr = p.server.redis.multi_exec()
tr.setex(f'{data.username}.lkey', p.server.server_config['KeyTTL'], login_key)
tr.setex(f'{data.username}.ckey', p.server.server_config['KeyTTL'], confirmation_hash)
tr.setex(f'{data.username}.lkey', p.server.config.auth_ttl, login_key)
tr.setex(f'{data.username}.ckey', p.server.config.auth_ttl, confirmation_hash)
await tr.execute()
world_populations, buddy_presence = await get_server_presence(p, data.id)

View File

@@ -1,5 +1,3 @@
import config
from houdini import handlers
from houdini.handlers import XMLPacket, login
from houdini.converters import WorldCredentials, Credentials
@@ -15,7 +13,7 @@ handle_random_key = login.handle_random_key
async def world_login(p, data):
if len(p.server.penguins_by_id) >= p.server.server_config['Capacity']:
if len(p.server.penguins_by_id) >= p.server.config.capacity:
return await p.send_error_and_disconnect(103)
if data is None:
@@ -53,7 +51,7 @@ async def handle_login(p, credentials: WorldCredentials):
return await p.close()
login_key = login_key.decode()
login_hash = Crypto.encrypt_password(login_key + config.client['AuthStaticKey']) + login_key
login_hash = Crypto.encrypt_password(login_key + p.server.config.auth_key) + login_key
if credentials.client_key != login_hash:
return await p.close()
@@ -73,11 +71,11 @@ async def handle_login(p, credentials: WorldCredentials):
async def handle_legacy_login(p, credentials: Credentials):
tr = p.server.redis.multi_exec()
tr.get(f'{credentials.username}.lkey')
tr.delete(f'{credentials.username}.lkey', '{credentials.username}.ckey')
tr.delete(f'{credentials.username}.lkey', f'{credentials.username}.ckey')
login_key, _ = await tr.execute()
login_key = login_key.decode()
login_hash = Crypto.encrypt_password(login_key + config.client['AuthStaticKey']) + login_key
login_hash = Crypto.encrypt_password(login_key + p.server.config.auth_key) + login_key
if login_key is None or login_hash != credentials.password:
return await p.close()

View File

@@ -15,7 +15,8 @@ async def handle_send_message(p, penguin_id: int, message: str):
await penguin.send_xt("mm", message, penguin_id)
return
if has_command_prefix(message):
tokens = message.lower()
if has_command_prefix(p.server.config.command_prefix, message):
await invoke_command_string(p.server.commands, p, message)
else:
await p.room.send_xt('sm', p.id, message)

View File

@@ -58,9 +58,9 @@ async def handle_join_server(p, penguin_id: int, login_key: str):
p.login_timestamp = datetime.now()
p.joined_world = True
server_key = f'houdini.players.{p.server.server_config["Id"]}'
await p.server.redis.sadd(server_key, p.data.id)
await p.server.redis.hincrby('houdini.population', p.server.server_config['Id'], 1)
server_key = f'houdini.players.{p.server.config.id}'
await p.server.redis.sadd(server_key, p.id)
await p.server.redis.hincrby('houdini.population', p.server.config.id, 1)
@handlers.handler(XTPacket('j', 'jr'))
@@ -127,6 +127,6 @@ async def handle_disconnect_room(p):
del p.server.penguins_by_id[p.id]
del p.server.penguins_by_username[p.username]
server_key = f'houdini.players.{p.server.server_config["Id"]}'
await p.server.redis.srem(server_key, p.data.id)
await p.server.redis.hincrby('houdini.population', p.server.server_config['Id'], -1)
server_key = f'houdini.players.{p.server.config.id}'
await p.server.redis.srem(server_key, p.id)
await p.server.redis.hincrby('houdini.population', p.server.config.id, -1)

View File

@@ -6,7 +6,6 @@ import copy
from houdini.spheniscidae import Spheniscidae
from houdini.penguin import Penguin
from houdini import PenguinStringCompiler
import config
import logging
from logging.handlers import RotatingFileHandler
@@ -47,22 +46,14 @@ from houdini.handlers.play.music import SoundStudio
class Houdini:
def __init__(self, server_name, **kwargs):
def __init__(self, config):
self.server = None
self.redis = None
self.config = None
self.cache = None
self.config = config
self.db = db
self.peers_by_ip = {}
self.server_name = server_name
self.database_config_override = kwargs.get('database')
self.redis_config_override = kwargs.get('redis')
self.commands_config_override = kwargs.get('commands')
self.client_config_override = kwargs.get('client')
self.server_config_override = kwargs.get('server')
self.server_config = None
self.logger = None
self.client_class = Spheniscidae
@@ -105,18 +96,14 @@ class Houdini:
self.music = None
async def start(self):
self.config = config
self.server_config = copy.deepcopy(self.config.servers[self.server_name])
self.server_config.update(self.server_config_override)
self.config.database.update(self.database_config_override)
self.config.redis.update(self.redis_config_override)
self.config.commands.update(self.commands_config_override)
self.config.client.update(self.client_config_override)
general_log_directory = os.path.dirname(self.server_config["Logging"]["General"])
errors_log_directory = os.path.dirname(self.server_config["Logging"]["Errors"])
async def start(self):
general_log_file = self.config.logging_general_path if self.config.logging_general_path \
else f'logs/{self.config.name.lower()}.log'
errors_log_file = self.config.logging_error_path if self.config.logging_error_path \
else f'logs/{self.config.name.lower()}-errors.log'
general_log_directory = os.path.dirname(general_log_file)
errors_log_directory = os.path.dirname(errors_log_file)
if not os.path.exists(general_log_directory):
os.mkdir(general_log_directory)
@@ -125,10 +112,10 @@ class Houdini:
os.mkdir(errors_log_directory)
self.logger = logging.getLogger('houdini')
universal_handler = RotatingFileHandler(self.server_config['Logging']['General'],
universal_handler = RotatingFileHandler(general_log_file,
maxBytes=2097152, backupCount=3, encoding='utf-8')
error_handler = logging.FileHandler(self.server_config['Logging']['Errors'])
error_handler = logging.FileHandler(errors_log_file)
console_handler = logging.StreamHandler(stream=sys.stdout)
log_formatter = logging.Formatter('%(asctime)s [%(levelname)-5.5s] %(message)s')
@@ -141,35 +128,34 @@ class Houdini:
self.logger.addHandler(console_handler)
self.logger.addHandler(error_handler)
level = logging.getLevelName(self.server_config['Logging']['Level'])
level = logging.getLevelName(self.config.logging_level)
self.logger.setLevel(level)
self.server = await asyncio.start_server(
self.client_connected, self.server_config['Address'],
self.server_config['Port']
self.client_connected, self.config.address,
self.config.port
)
await self.db.set_bind('postgresql://{}:{}@{}/{}'.format(
self.config.database['Username'], self.config.database['Password'],
self.config.database['Address'],
self.config.database['Name']))
self.config.database_username, self.config.database_password,
self.config.database_address,
self.config.database_name))
self.logger.info('Booting Houdini')
self.redis = await aioredis.create_redis_pool('redis://{}:{}'.format(
self.config.redis['Address'], self.config.redis['Port']),
self.config.redis_address, self.config.redis_port),
minsize=5, maxsize=10)
if self.server_config['World']:
await self.redis.delete(f'houdini.players.{self.server_config["Id"]}')
await self.redis.hdel(f'houdini.population', self.server_config["Id"])
if self.config.type == 'world':
await self.redis.delete(f'houdini.players.{self.config.id}')
await self.redis.hset(f'houdini.population', self.config.id, 0)
caches.set_config({
'default': {
'cache': SimpleMemoryCache,
'namespace': 'houdini',
'ttl': self.server_config['CacheExpiry']
}})
caches.set_config(dict(default=dict(
cache=SimpleMemoryCache,
namespace='houdini',
ttl=self.config.cache_expiry
)))
self.cache = caches.get('default')
self.client_class = Penguin
@@ -227,12 +213,13 @@ class Houdini:
self.logger.info(f'Loaded {len(self.characters)} characters')
self.permissions = await PermissionCollection.get_collection()
self.chat_filter_words = await ChatFilterRuleCollection.get_collection()
self.logger.info(f'Multi-client support is '
f'{"enabled" if self.config.client["MultiClientSupport"] else "disabled"}')
self.logger.info(f'Listening on {self.server_config["Address"]}:{self.server_config["Port"]}')
f'{"enabled" if not self.config.single_client_mode else "disabled"}')
self.logger.info(f'Listening on {self.config.address}:{self.config.port}')
if self.config.client['AuthStaticKey'] != 'houdini':
if self.config.auth_key != 'houdini':
self.logger.warning('The static key has been changed from the default, '
'this may cause authentication issues!')

View File

@@ -55,8 +55,8 @@ class Penguin(Spheniscidae, penguin.Penguin):
return self.server.penguin_string_compiler.compile(self)
@property
def nickname(self):
return self.data.safe_nickname(self.server.server_config['Language'])
def safe_name(self):
return self.safe_nickname(self.server.config.lang)
async def join_room(self, room):
await room.add_penguin(self)

View File

@@ -34,6 +34,8 @@ class Spheniscidae:
self.received_packets = set()
super().__init__()
@property
def is_vanilla_client(self):
return self.client_type == ClientType.Vanilla
@@ -51,7 +53,7 @@ class Spheniscidae:
async def send_policy_file(self):
await self.send_line(f'<cross-domain-policy><allow-access-from domain="*" to-ports="'
f'{self.server.server_config["Port"]}" /></cross-domain-policy>')
f'{self.server.config.port}" /></cross-domain-policy>')
await self.close()
async def send_xt(self, handler_id, *data):