|
|
|
|
import time, json, re
|
|
|
|
|
import flask
|
|
|
|
|
from flask import Flask
|
|
|
|
|
from flask_socketio import SocketIO, send, emit, join_room, leave_room, has_request_context, disconnect
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
## Notes from the python/flask port:
|
|
|
|
|
|
|
|
|
|
Still to do:
|
|
|
|
|
|
|
|
|
|
* NPC
|
|
|
|
|
* Language Filter
|
|
|
|
|
* Connection monitoring: PACKETS_PER_SECONDS
|
|
|
|
|
* serverMod.js!
|
|
|
|
|
|
|
|
|
|
## Notes from original/node:
|
|
|
|
|
|
|
|
|
|
The client and server version strings MUST be the same!
|
|
|
|
|
They can be used to force clients to hard refresh to load the latest client.
|
|
|
|
|
If the server gets updated it can be restarted, but if there are active clients (users' open browsers) they could be outdated and create issues.
|
|
|
|
|
If the VERSION vars are mismatched they will send all clients in an infinite refresh loop. Make sure you update sketch.js before restarting server.js
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
PACKETS_PER_SECONDS = 30
|
|
|
|
|
VERSION = "1.0"
|
|
|
|
|
with open("data.json") as datafile:
|
|
|
|
|
DATA = json.load(datafile)
|
|
|
|
|
|
|
|
|
|
# time before disconnecting (forgot the tab open?)
|
|
|
|
|
ACTIVITY_TIMEOUT = 10 * 60 * 1000
|
|
|
|
|
# should be the same as index maxlength="16"
|
|
|
|
|
MAX_NAME_LENGTH = 16
|
|
|
|
|
|
|
|
|
|
# cap the overall players
|
|
|
|
|
MAX_PLAYERS = -1
|
|
|
|
|
# refuse people when a room is full
|
|
|
|
|
MAX_PLAYERS_PER_ROOM = 200
|
|
|
|
|
|
|
|
|
|
# views since the server started counts relogs
|
|
|
|
|
visits = 0
|
|
|
|
|
admins = []
|
|
|
|
|
# We want the server to keep track of the whole game state
|
|
|
|
|
# in this case the game state are the attributes of each player
|
|
|
|
|
gameState = {
|
|
|
|
|
'players': {},
|
|
|
|
|
'NPCs': {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# save the server startup time and send it in case the clients need to syncronize something
|
|
|
|
|
START_TIME = int(time.time()*1000) # datetime.datetime.now()
|
|
|
|
|
|
|
|
|
|
# a collection of banned IPs
|
|
|
|
|
# not permanent, it lasts until the server restarts
|
|
|
|
|
banned = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = Flask(__name__, static_url_path='', static_folder='public')
|
|
|
|
|
app.add_url_rule("/", view_func=lambda **kw: app.send_static_file("index.html", **kw))
|
|
|
|
|
# app.config['SECRET_KEY'] = 'secret!'
|
|
|
|
|
socketio = SocketIO(app)
|
|
|
|
|
connections = 0
|
|
|
|
|
|
|
|
|
|
from interval_tasks import IntervalTaskMaster
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@socketio.on('connect')
|
|
|
|
|
def connect(sid=None, environ=None, auth=None):
|
|
|
|
|
global connections
|
|
|
|
|
connections += 1
|
|
|
|
|
print (f"A user connected ({connections} connections)")
|
|
|
|
|
emit('serverWelcome', (VERSION, DATA, START_TIME))
|
|
|
|
|
|
|
|
|
|
@socketio.on('disconnect')
|
|
|
|
|
def disconnect(auth=None):
|
|
|
|
|
global connections
|
|
|
|
|
connections -= 1
|
|
|
|
|
# print ("disconnect")
|
|
|
|
|
print(f'Client disconnected ({connections} connections)')
|
|
|
|
|
# when a client disconnects I have to delete its player object
|
|
|
|
|
# or I would end up with ghost players
|
|
|
|
|
try:
|
|
|
|
|
print(f"Player disconnected {flask.request.sid}")
|
|
|
|
|
playerObject = gameState['players'].get(flask.request.sid);
|
|
|
|
|
emit("playerLeft", { 'id': flask.request.sid, 'disconnect': True }, broadcast=True)
|
|
|
|
|
# check if there is a custom function in the MOD to call at this point
|
|
|
|
|
if playerObject is not None:
|
|
|
|
|
if playerObject['room']:
|
|
|
|
|
if hasattr(MOD, playerObject['room']+"Leave"):
|
|
|
|
|
mod_fn = getattr(MOD, playerObject['room']+"Leave")
|
|
|
|
|
mod_fn(playerObject, playerObject['room'])
|
|
|
|
|
|
|
|
|
|
# send the disconnect
|
|
|
|
|
# delete the player object
|
|
|
|
|
if flask.request.sid in gameState['players']:
|
|
|
|
|
del gameState['players'][flask.request.sid]
|
|
|
|
|
print(f"There are now {len(gameState['players'])} players on this server")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on disconnect, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('join')
|
|
|
|
|
def join (playerInfo):
|
|
|
|
|
global visits
|
|
|
|
|
# print ("join", playerInfo)
|
|
|
|
|
try:
|
|
|
|
|
# if running locally it's not gonna work
|
|
|
|
|
IP = ""
|
|
|
|
|
if (has_request_context() and flask.request.headers):
|
|
|
|
|
if (flask.request.headers.get("x-forwarded-for") is not None):
|
|
|
|
|
IP = flask.request.headers.get("x-forwarded-for").split(",")[0]
|
|
|
|
|
if (playerInfo['nickName'] == ""):
|
|
|
|
|
print(f"New user joined the server in lurking mode {flask.request.sid} {IP}")
|
|
|
|
|
else:
|
|
|
|
|
print(f"New user joined the game: {playerInfo['nickName']} avatar# {playerInfo['avatar']} colors# {playerInfo['colors']} {flask.request.sid}")
|
|
|
|
|
|
|
|
|
|
roomPlayers = 1
|
|
|
|
|
# from https://github.com/miguelgrinberg/python-socketio/blob/main/src/socketio/base_manager.py
|
|
|
|
|
# manager.rooms = {} # self.rooms[namespace][room][sio_sid] = eio_sid
|
|
|
|
|
# io.sockets.adapter (js) == socketio.server.manager (python)
|
|
|
|
|
# print ("rooms", socketio.server.manager.rooms)
|
|
|
|
|
myRoom = socketio.server.manager.rooms['/'].get(playerInfo['room'])
|
|
|
|
|
if myRoom is not None:
|
|
|
|
|
roomPlayers = len(myRoom.keys()) + 1
|
|
|
|
|
# roomPlayers = len(list(socketio.server.manager.get_participants('/', playerInfo['room'])))
|
|
|
|
|
print (f"There are now {roomPlayers} users in {playerInfo['room']}")
|
|
|
|
|
# serverPlayers = Object.keys(io.sockets.connected).length + 1
|
|
|
|
|
serverPlayers = connections + 1
|
|
|
|
|
isBanned = False
|
|
|
|
|
|
|
|
|
|
# prevent banned IPs from joining
|
|
|
|
|
if (IP != ""):
|
|
|
|
|
try:
|
|
|
|
|
index = banned.index(IP);
|
|
|
|
|
print(f"ATTENTION: banned {IP} is trying to log in again")
|
|
|
|
|
isBanned = True
|
|
|
|
|
emit("errorMessage", "You have been banned")
|
|
|
|
|
disconnect()
|
|
|
|
|
except ValueError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# prevent secret rooms to be joined through URL
|
|
|
|
|
if playerInfo['room'] in DATA['ROOMS']:
|
|
|
|
|
if DATA['ROOMS'][playerInfo['room']].get('secret'):
|
|
|
|
|
playerInfo['room'] = DATA['SETTINGS']['defaultRoom']
|
|
|
|
|
|
|
|
|
|
if isBanned:
|
|
|
|
|
pass
|
|
|
|
|
elif flask.request.sid in gameState['players']:
|
|
|
|
|
# prevent a hacked client from duplicating players
|
|
|
|
|
print(f"ATTENTION: there is already a player associated to the socket {flask.request.sid}")
|
|
|
|
|
elif ((serverPlayers > MAX_PLAYERS and MAX_PLAYERS != -1) or (roomPlayers > MAX_PLAYERS_PER_ROOM and MAX_PLAYERS_PER_ROOM != -1)):
|
|
|
|
|
# limit the number of players
|
|
|
|
|
print(f"ATTENTION: {playerInfo['room']} reached maximum capacity")
|
|
|
|
|
emit("errorMessage", "The server is full, please try again later.")
|
|
|
|
|
disconnect()
|
|
|
|
|
else:
|
|
|
|
|
# if client hacked truncate
|
|
|
|
|
playerInfo['nickName'] = playerInfo['nickName'][:MAX_NAME_LENGTH]
|
|
|
|
|
|
|
|
|
|
# the first validation was to give the player feedback, this one is for real
|
|
|
|
|
val = 1
|
|
|
|
|
|
|
|
|
|
# always validate lurkers, they can't do anything
|
|
|
|
|
if playerInfo['nickName'] != "":
|
|
|
|
|
val = validateName(playerInfo['nickName'])
|
|
|
|
|
|
|
|
|
|
if (val == 0 or val == 3):
|
|
|
|
|
print(f"ATTENTION: {flask.request.sid} tried to bypass username validation")
|
|
|
|
|
else:
|
|
|
|
|
#if there is an | strip the after so the password remains in the admin client
|
|
|
|
|
combo = playerInfo['nickName'].split("|")
|
|
|
|
|
playerInfo['nickName'] = combo[0]
|
|
|
|
|
|
|
|
|
|
if (val == 2):
|
|
|
|
|
print(f"{playerInfo['nickName']} joins as admin")
|
|
|
|
|
|
|
|
|
|
#the player objects on the client will keep track of the room
|
|
|
|
|
newPlayer = { 'id': flask.request.sid, 'nickName': filter.clean(playerInfo['nickName']), 'colors': playerInfo['colors'], 'room': playerInfo['room'], 'avatar': playerInfo['avatar'], 'x': playerInfo['x'], 'y': playerInfo['y']}
|
|
|
|
|
|
|
|
|
|
# save the same information in my game state
|
|
|
|
|
sid = flask.request.sid
|
|
|
|
|
gameState['players'][sid] = newPlayer
|
|
|
|
|
# set last message at the beginning of time, the SEVENTIES
|
|
|
|
|
gameState['players'][sid]['lastMessage'] = 0;
|
|
|
|
|
# is it admin?
|
|
|
|
|
gameState['players'][sid]['admin'] = (val == 2)
|
|
|
|
|
gameState['players'][sid]['spam'] = 0;
|
|
|
|
|
gameState['players'][sid]['lastActivity'] = int(time.time()*1000)
|
|
|
|
|
gameState['players'][sid]['muted'] = False
|
|
|
|
|
gameState['players'][sid]['IP'] = IP
|
|
|
|
|
gameState['players'][sid]['floodCount'] = 0
|
|
|
|
|
gameState['players'][sid]['room'] = playerInfo['room']
|
|
|
|
|
|
|
|
|
|
# send the user to the default room
|
|
|
|
|
join_room(playerInfo['room'])
|
|
|
|
|
|
|
|
|
|
newPlayer['new'] = True
|
|
|
|
|
|
|
|
|
|
# let's not count lurkers
|
|
|
|
|
if playerInfo['nickName'] != "":
|
|
|
|
|
visits += 1
|
|
|
|
|
|
|
|
|
|
#send all players information about the new player
|
|
|
|
|
#upon creation destination and position are the same
|
|
|
|
|
# print (f"playerJoined {newPlayer} {playerInfo['room']}")
|
|
|
|
|
emit("playerJoined", newPlayer, room=playerInfo['room'])
|
|
|
|
|
|
|
|
|
|
# check if there are NPCs in this room and make them send info to the player
|
|
|
|
|
for NPCId in gameState['NPCs']:
|
|
|
|
|
npc = gameState['NPCs'][NPCId]
|
|
|
|
|
if npc.room == playerInfo['room']:
|
|
|
|
|
npc.sendIntroTo(flask.request.sid)
|
|
|
|
|
#check if there is a custom function in the MOD to call at this point
|
|
|
|
|
if hasattr(MOD, playerInfo['room']+"Join"):
|
|
|
|
|
# call it!
|
|
|
|
|
mod_fn = getattr(MOD, playerInfo['room']+"Join")
|
|
|
|
|
mod_fn(newPlayer, playerInfo['room'])
|
|
|
|
|
|
|
|
|
|
print(f"There are now {len(gameState['players'])} players on this server. Total visits {visits}")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on join, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('intro')
|
|
|
|
|
def intro (newComer, obj):
|
|
|
|
|
""" when I receive an intro send it to the recipient
|
|
|
|
|
|
|
|
|
|
intro messages are sent by the client in reponse to a playerJoin message of a "new" player
|
|
|
|
|
obj is a "auto biography" from the sender to relay to newComer
|
|
|
|
|
containing info (like avatar) that the server merely relays
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
# print (f"intro {newComer} {obj}")
|
|
|
|
|
# verify the id to make sure a hacked client can"t fake players
|
|
|
|
|
if (obj is not None):
|
|
|
|
|
if obj['id'] == flask.request.sid:
|
|
|
|
|
emit("onIntro", obj, room=newComer)
|
|
|
|
|
if hasattr(MOD, obj['room'] + "Intro"):
|
|
|
|
|
mod_fn = getattr(MOD, obj['room'] + "Intro")
|
|
|
|
|
mod_fn(newComer, obj)
|
|
|
|
|
else:
|
|
|
|
|
print(f"ATTENTION: Illegitimate intro from {flask.request.sid}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('talk')
|
|
|
|
|
def talk (obj):
|
|
|
|
|
""" when I receive a talk send it to everybody in the room """
|
|
|
|
|
try:
|
|
|
|
|
mtime = int(time.time()*1000)
|
|
|
|
|
|
|
|
|
|
# block if spamming
|
|
|
|
|
if (mtime - gameState['players'][flask.request.sid]['lastMessage'] > DATA['SETTINGS']['ANTI_SPAM'] and not gameState['players'][flask.request.sid]['muted']):
|
|
|
|
|
|
|
|
|
|
# Admin commands can be typed as messages
|
|
|
|
|
# is this an admin
|
|
|
|
|
if (gameState['players'][flask.request.sid].get("admin") and obj['message'][0] == "/"):
|
|
|
|
|
print(f"Admin {gameState['players'][flask.request.sid]['nickName']} attempts command {obj['message']}")
|
|
|
|
|
adminCommand(socket, obj['message'])
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
# normal talk stuff
|
|
|
|
|
# aphostrophe
|
|
|
|
|
obj['message'] = obj['message'].replace("’", "'")
|
|
|
|
|
|
|
|
|
|
# replace unmapped characters
|
|
|
|
|
# obj.message = obj.message.replace(/[^A-Za-z0-9_!$%*()@./#&+-|]*$/g, "");
|
|
|
|
|
obj['message'] = re.sub(r'[^A-Za-z0-9_!$%*()@./#&+-|]*$', "", obj['message'])
|
|
|
|
|
|
|
|
|
|
# remove leading and trailing whitespaces
|
|
|
|
|
obj['message'] = obj['message'].strip()
|
|
|
|
|
|
|
|
|
|
# filter bad words
|
|
|
|
|
obj['message'] = filter.clean(obj['message'])
|
|
|
|
|
|
|
|
|
|
# advanced cleaning
|
|
|
|
|
# f u c k
|
|
|
|
|
test = re.sub('\s', "", obj['message'])
|
|
|
|
|
# fffffuuuuck
|
|
|
|
|
test2 = re.sub('(.)(?=.*\1)', "", obj['message'])
|
|
|
|
|
# f*u*c*k
|
|
|
|
|
test3 = re.sub('\W', "", obj['message'])
|
|
|
|
|
# spaces
|
|
|
|
|
test4 = re.sub('\s', "", obj['message']);
|
|
|
|
|
|
|
|
|
|
if (filter.isProfane(test) or filter.isProfane(test2) or filter.isProfane(test3) or test4 == ""):
|
|
|
|
|
print(f"{flask.request.sid} is problematic")
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
|
|
# check if there is a custom function in the MOD to call at this point
|
|
|
|
|
if hasattr(MOD, obj['room']+"TalkFilter"):
|
|
|
|
|
mod_fn = getattr(MOD, obj['room']+"TalkFilter")
|
|
|
|
|
obj.message = mod_fn(gameState['players'][flask.request.sid], obj['message'])
|
|
|
|
|
|
|
|
|
|
if obj['message'] is not None:
|
|
|
|
|
print("MOD: Warning - TalkFilter should return a message ")
|
|
|
|
|
obj['message'] = ""
|
|
|
|
|
|
|
|
|
|
if obj['message']:
|
|
|
|
|
emit("playerTalked", { 'id': flask.request.sid, 'color': obj['color'], 'message': obj['message'], 'x': obj['x'], 'y': obj['y'] }, room=obj['room'])
|
|
|
|
|
|
|
|
|
|
# update the last message time
|
|
|
|
|
# if (gameState['players'][flask.request.sid]:
|
|
|
|
|
gameState['players'][flask.request.sid]['lastMessage'] = mtime
|
|
|
|
|
gameState['players'][flask.request.sid]['lastActivity'] = mtime
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on talk, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('changeRoom')
|
|
|
|
|
def changeRoom (obj):
|
|
|
|
|
""" when I receive a move sent it to everybody"""
|
|
|
|
|
try:
|
|
|
|
|
roomPlayers = 1
|
|
|
|
|
|
|
|
|
|
# myRoom = io.sockets.adapter.rooms[obj.to];
|
|
|
|
|
# if myRoom is not None:
|
|
|
|
|
# roomPlayers = myRoom.length + 1;
|
|
|
|
|
|
|
|
|
|
myRoom = socketio.server.manager.rooms['/'].get(obj['to'])
|
|
|
|
|
if myRoom is not None:
|
|
|
|
|
roomPlayers = len(myRoom.keys()) + 1
|
|
|
|
|
|
|
|
|
|
if roomPlayers > MAX_PLAYERS_PER_ROOM and MAX_PLAYERS_PER_ROOM != -1:
|
|
|
|
|
# limit the number of players
|
|
|
|
|
print(f"ATTENTION: {obj['to']} reached maximum capacity")
|
|
|
|
|
# keep the player in game, send a message
|
|
|
|
|
emit("godMessage", "The room looks full")
|
|
|
|
|
else:
|
|
|
|
|
print(f"Player {flask.request.sid} moved from {obj['from']} to {obj['to']}")
|
|
|
|
|
|
|
|
|
|
leave_room(obj['from'])
|
|
|
|
|
join_room(obj['to'])
|
|
|
|
|
|
|
|
|
|
# broadcast the change to everybody in the current room
|
|
|
|
|
# from the client perspective leaving the room is the same as disconnecting
|
|
|
|
|
emit("playerLeft", { 'id': flask.request.sid, 'room': obj['from'], 'disconnect': False }, room=obj['from'])
|
|
|
|
|
|
|
|
|
|
# same for joining, sending everybody in the room the player state
|
|
|
|
|
playerObject = gameState['players'][flask.request.sid]
|
|
|
|
|
playerObject['room'] = obj['to']
|
|
|
|
|
playerObject['x'] = playerObject['destinationX'] = obj['x']
|
|
|
|
|
playerObject['y'] = playerObject['destinationY'] = obj['y']
|
|
|
|
|
playerObject['new'] = False
|
|
|
|
|
|
|
|
|
|
# check if there is a custom function in the MOD to call at this point
|
|
|
|
|
if hasattr(MOD, obj['from']+"Leave"):
|
|
|
|
|
mod_fn = getattr(MOD, obj['from']+"Leave")
|
|
|
|
|
mod_fn(playerObject, obj['from'])
|
|
|
|
|
|
|
|
|
|
emit("playerJoined", playerObject, room=obj['to'])
|
|
|
|
|
|
|
|
|
|
# check if there is a custom function in the MOD to call at this point
|
|
|
|
|
if hasattr(MOD, obj['to']+"Join"):
|
|
|
|
|
mod_fn = getattr(MOD, obj['to']+"Join")
|
|
|
|
|
mod_fn(playerObject, obj['to'])
|
|
|
|
|
|
|
|
|
|
# check if there are NPCs in this room and make them send info to the player
|
|
|
|
|
for NPCId in gameState['NPCs']:
|
|
|
|
|
npc = gameState['NPCs'][NPCId]
|
|
|
|
|
if (npc.room == obj['to']):
|
|
|
|
|
npc.sendIntroTo(flask.request.sid)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on changeRoom, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# when I receive a move sent it to everybody
|
|
|
|
|
@socketio.on('move')
|
|
|
|
|
def move(obj):
|
|
|
|
|
try:
|
|
|
|
|
gameState['players'][flask.request.sid]['lastActivity'] = int(time.time()*1000)
|
|
|
|
|
|
|
|
|
|
# broadcast the movement to everybody
|
|
|
|
|
# print (f"emit playerMoved to everybody in {obj['room']}")
|
|
|
|
|
emit("playerMoved", { 'id': flask.request.sid, 'x': obj['x'], 'y': obj['y'], 'destinationX': obj['destinationX'], 'destinationY': obj['destinationY'] }, room=obj['room'])
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on move, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
# when I receive a user name validate it
|
|
|
|
|
@socketio.on('sendName')
|
|
|
|
|
def sendName(nn):
|
|
|
|
|
try:
|
|
|
|
|
res = validateName(nn)
|
|
|
|
|
# send the code 0 no - 1 ok - 2 admin
|
|
|
|
|
emit("nameValidation", res)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on sendName, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('emote')
|
|
|
|
|
def emote(obj):
|
|
|
|
|
"""when a character emote animation changes"""
|
|
|
|
|
try:
|
|
|
|
|
emit("playerEmoted", (flask.request.sid, obj['em']), room=obj['room'])
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on emote, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('focus')
|
|
|
|
|
def focus(obj):
|
|
|
|
|
"""user afk """
|
|
|
|
|
try:
|
|
|
|
|
# print(f"{flask.request.sid} back from AFK")
|
|
|
|
|
emit("playerFocused", flask.request.sid, room=obj['room']);
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on focus, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('blur')
|
|
|
|
|
def blur(obj):
|
|
|
|
|
try:
|
|
|
|
|
# print(f"{flask.request.sid} is AFK")
|
|
|
|
|
emit("playerBlurred", flask.request.sid, room=obj['room'])
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"Error on blur, object malformed from {flask.request.sid}?")
|
|
|
|
|
print(f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
@socketio.on('action')
|
|
|
|
|
def action(aId):
|
|
|
|
|
""" generic action listener, looks for a function with that id in the mod """
|
|
|
|
|
if hasattr(MOD, "on"+aId):
|
|
|
|
|
mod_fn = getattr(MOD, "on"+aId)
|
|
|
|
|
# print(f"on {aId} exists in the mod, call it")
|
|
|
|
|
mod_fn(flask.request.sid);
|
|
|
|
|
|
|
|
|
|
def validateName(nn):
|
|
|
|
|
admin = False
|
|
|
|
|
duplicate = False
|
|
|
|
|
reserved = False
|
|
|
|
|
|
|
|
|
|
#check if the nickname is a name + password combo
|
|
|
|
|
combo = nn.split("|")
|
|
|
|
|
|
|
|
|
|
#it may be
|
|
|
|
|
if len(combo) > 1:
|
|
|
|
|
n = combo[0]
|
|
|
|
|
p = combo[1]
|
|
|
|
|
for admin in admins:
|
|
|
|
|
if admin.upper() == nn.upper():
|
|
|
|
|
#it is an admin name! check if the password is correct, case insensitive
|
|
|
|
|
envCombo = admin.split("|")
|
|
|
|
|
if (p == envCombo[1]):
|
|
|
|
|
admin = True
|
|
|
|
|
# if there is an | just strip the after
|
|
|
|
|
nn = n
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if not admin check if the nickname is reserved (case insensitive)
|
|
|
|
|
if not admin:
|
|
|
|
|
for admin in admins:
|
|
|
|
|
combo = admin.split("|");
|
|
|
|
|
if combo[0].upper() == nn.upper():
|
|
|
|
|
#it is! kill it. Yes, it should be done at login and communicated
|
|
|
|
|
#but hey I don't have to be nice to users who steal my name
|
|
|
|
|
reserved = True
|
|
|
|
|
|
|
|
|
|
id = idByName(nn)
|
|
|
|
|
if (id is not None):
|
|
|
|
|
duplicate = True
|
|
|
|
|
print(f"There is already a player named {nn}")
|
|
|
|
|
|
|
|
|
|
#i hate this double negative logic but I hate learning regex more
|
|
|
|
|
res = re.match(r'^([a-zA-Z0-9 !@#$%&*(),._-]+)$', nn)
|
|
|
|
|
|
|
|
|
|
if (res is None):
|
|
|
|
|
return 3
|
|
|
|
|
elif duplicate or reserved:
|
|
|
|
|
return 0
|
|
|
|
|
elif admin:
|
|
|
|
|
print(f"{nn} logging as admin")
|
|
|
|
|
return 2
|
|
|
|
|
else:
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def adminCommand(adminSocket, str):
|
|
|
|
|
try:
|
|
|
|
|
# remove /
|
|
|
|
|
str = str[1:]
|
|
|
|
|
cmd = str.split(" ")
|
|
|
|
|
if cmd[0] == "kick":
|
|
|
|
|
sid = socketIDByName(cmd[1])
|
|
|
|
|
if sid:
|
|
|
|
|
# shadow disconnect
|
|
|
|
|
disconnect(sid)
|
|
|
|
|
else:
|
|
|
|
|
# popup to admin
|
|
|
|
|
emit("popup", f"I can't find a user named {cmd[1]}")
|
|
|
|
|
|
|
|
|
|
elif cmd[0] == "mute":
|
|
|
|
|
s = idByName(cmd[1])
|
|
|
|
|
if s:
|
|
|
|
|
gameState['players'][s]['muted'] = True
|
|
|
|
|
else:
|
|
|
|
|
# popup to admin
|
|
|
|
|
emit("popup", f"I can't find a user named {cmd[1]}")
|
|
|
|
|
|
|
|
|
|
elif cmd[0] == "unmute":
|
|
|
|
|
s = idByName(cmd[1]);
|
|
|
|
|
if s:
|
|
|
|
|
gameState['players'][s]['muted'] = False
|
|
|
|
|
else:
|
|
|
|
|
# popup to admin
|
|
|
|
|
emit("popup", f"I can't find a user named {cmd[1]}")
|
|
|
|
|
|
|
|
|
|
# trigger a direct popup
|
|
|
|
|
elif cmd[0] == "popup":
|
|
|
|
|
s = socketIDByName(cmd[1])
|
|
|
|
|
if s:
|
|
|
|
|
msg = cmd[2:].join(" ");
|
|
|
|
|
emit("popup", msg, room=s)
|
|
|
|
|
else:
|
|
|
|
|
# popup to admin
|
|
|
|
|
emit("popup", f"I can't find a user named {cmd[1]}")
|
|
|
|
|
|
|
|
|
|
# send fullscreen message to everybody
|
|
|
|
|
elif cmd[0] == "god":
|
|
|
|
|
msg = cmd[1:].join(" ");
|
|
|
|
|
emit("godMessage", msg, broadcast=True)
|
|
|
|
|
|
|
|
|
|
# disconnect all sockets
|
|
|
|
|
elif cmd[0] == "nuke":
|
|
|
|
|
emit("errorMessage", "Server Restarted\nPlease Refresh", broadcast=True)
|
|
|
|
|
for sid in gameState['players']:
|
|
|
|
|
disconnect(sid)
|
|
|
|
|
|
|
|
|
|
# add to the list of banned IPs
|
|
|
|
|
elif cmd[0] == "ban":
|
|
|
|
|
IP = IPByName(cmd[1])
|
|
|
|
|
s = socketByName(cmd[1])
|
|
|
|
|
if IP:
|
|
|
|
|
banned.append(IP)
|
|
|
|
|
if s:
|
|
|
|
|
s.emit("errorMessage", "You have been banned")
|
|
|
|
|
s.disconnect()
|
|
|
|
|
else:
|
|
|
|
|
# popup to admin
|
|
|
|
|
emit("popup", f"I can't find a user named {cmd[1]}")
|
|
|
|
|
|
|
|
|
|
elif cmd[0] == "unban":
|
|
|
|
|
# releases the ban
|
|
|
|
|
banned = []
|
|
|
|
|
|
|
|
|
|
# forces a hard refresh - all players disconnect
|
|
|
|
|
# used to load a new version of the client
|
|
|
|
|
elif cmd[0] == "refresh":
|
|
|
|
|
io.sockets.emit("refresh");
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print ("Error admin command")
|
|
|
|
|
print (f"Exception: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# admin functions, the admin exists in the client frontend so they don't have access to ip and id of other users
|
|
|
|
|
def socketIDByName(nick):
|
|
|
|
|
s = None
|
|
|
|
|
for sid in gameState['players']:
|
|
|
|
|
if gameState['players'][sid]['nickName'].upper() == nick.upper():
|
|
|
|
|
s = sid
|
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
|
def idByName(nick):
|
|
|
|
|
i = None
|
|
|
|
|
for sid in gameState['players']:
|
|
|
|
|
if gameState['players'][sid]['nickName'].upper() == nick.upper():
|
|
|
|
|
i = sid
|
|
|
|
|
return i
|
|
|
|
|
|
|
|
|
|
def IPByName(nick):
|
|
|
|
|
IP = ""
|
|
|
|
|
for sid in gameState['players']:
|
|
|
|
|
if gameState['players'][sid]['nickName'].upper() == nick.upper():
|
|
|
|
|
IP = gameState['players'][sid]['IP']
|
|
|
|
|
return IP
|
|
|
|
|
|
|
|
|
|
class BadWordsFilter (object):
|
|
|
|
|
def __init__(self, initial_data=None):
|
|
|
|
|
self.words=[]
|
|
|
|
|
def clean (self, text):
|
|
|
|
|
return text
|
|
|
|
|
def isProfane(self, text):
|
|
|
|
|
return False
|
|
|
|
|
def addWords(self, *words):
|
|
|
|
|
self.words.extend(words)
|
|
|
|
|
|
|
|
|
|
filter = BadWordsFilter()
|
|
|
|
|
taskmaster = IntervalTaskMaster(socketio)
|
|
|
|
|
|
|
|
|
|
# modding
|
|
|
|
|
MOD = {}
|
|
|
|
|
try:
|
|
|
|
|
import serverMod as MOD
|
|
|
|
|
MOD.initMod(socketio, gameState, DATA, taskmaster)
|
|
|
|
|
except ImportError as e:
|
|
|
|
|
print (f"Error importing serverMod: {e}")
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
taskmaster.start()
|
|
|
|
|
socketio.run(app)
|