.HIDE. General refactoring and documentation
This commit is contained in:
parent
abad698fe3
commit
a2ef03c887
2
common
2
common
|
@ -1 +1 @@
|
|||
Subproject commit cd9f453e2199c112bb3b8fb2e34d3019b1dae3b1
|
||||
Subproject commit 0c8c4b9e9883c399708c887498e58217d8702a9b
|
|
@ -1,4 +1,3 @@
|
|||
""" Contains functions used to read specific client packets from byte stream """
|
||||
from constants import dataTypes
|
||||
from helpers import packetHelper
|
||||
from constants import slotStatuses
|
||||
|
@ -100,7 +99,7 @@ def matchSettings(stream):
|
|||
start += 2
|
||||
for i in range(0,16):
|
||||
s = data[0]["slot{}Status".format(str(i))]
|
||||
if s != slotStatuses.free and s != slotStatuses.locked:
|
||||
if s != slotStatuses.FREE and s != slotStatuses.LOCKED:
|
||||
start += 4
|
||||
|
||||
# Other settings
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
"""Bancho exceptions"""
|
||||
# TODO: Prints in exceptions
|
||||
class loginFailedException(Exception):
|
||||
pass
|
||||
|
||||
|
|
|
@ -19,12 +19,12 @@ from objects import glob
|
|||
Commands callbacks
|
||||
|
||||
Must have fro, chan and messages as arguments
|
||||
fro -- name of who triggered the command
|
||||
chan -- channel where the message was sent
|
||||
message -- list containing arguments passed from the message
|
||||
[0] = first argument
|
||||
[1] = second argument
|
||||
. . .
|
||||
:param fro: username of who triggered the command
|
||||
:param chan: channel"(or username, if PM) where the message was sent
|
||||
:param message: list containing arguments passed from the message
|
||||
[0] = first argument
|
||||
[1] = second argument
|
||||
. . .
|
||||
|
||||
return the message or **False** if there's no response by the bot
|
||||
TODO: Change False to None, because False doesn't make any sense
|
||||
|
@ -35,6 +35,7 @@ def instantRestart(fro, chan, message):
|
|||
return False
|
||||
|
||||
def faq(fro, chan, message):
|
||||
# TODO: Unhardcode this
|
||||
if message[0] == "rules":
|
||||
return "Please make sure to check (Ripple's rules)[http://ripple.moe/?p=23]."
|
||||
elif message[0] == "swearing":
|
||||
|
@ -160,11 +161,11 @@ def silence(fro, chan, message):
|
|||
if unit == 's':
|
||||
silenceTime = int(amount)
|
||||
elif unit == 'm':
|
||||
silenceTime = int(amount)*60
|
||||
silenceTime = int(amount) * 60
|
||||
elif unit == 'h':
|
||||
silenceTime = int(amount)*3600
|
||||
silenceTime = int(amount) * 3600
|
||||
elif unit == 'd':
|
||||
silenceTime = int(amount)*86400
|
||||
silenceTime = int(amount) * 86400
|
||||
else:
|
||||
return "Invalid time unit (s/m/h/d)."
|
||||
|
||||
|
@ -707,11 +708,6 @@ callback: function to call when the command is triggered. Optional.
|
|||
response: text to return when the command is triggered. Optional.
|
||||
syntax: command syntax. Arguments must be separated by spaces (eg: <arg1> <arg2>)
|
||||
privileges: privileges needed to execute the command. Optional.
|
||||
|
||||
NOTES:
|
||||
- You CAN'T use both rank and minRank at the same time.
|
||||
- If both rank and minrank are **not** present, everyone will be able to run that command.
|
||||
- You MUST set trigger and callback/response, or the command won't work.
|
||||
"""
|
||||
commands = [
|
||||
{
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
normal = 0
|
||||
freeMod = 1
|
||||
NORMAL = 0
|
||||
FREE_MOD = 1
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
score = 0
|
||||
accuracy = 1
|
||||
combo = 2
|
||||
SCORE = 0
|
||||
ACCURACY = 1
|
||||
COMBO = 2
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
headToHead = 0
|
||||
tagCoop = 1
|
||||
teamVs = 2
|
||||
tagTeamVs = 3
|
||||
HEAD_TO_HEAD = 0
|
||||
TAG_COOP = 1
|
||||
TEAM_VS = 2
|
||||
TAG_TEAM_VS = 3
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
noTeam = 0
|
||||
blue = 1
|
||||
red = 2
|
||||
NO_TEAM = 0
|
||||
BLUE = 1
|
||||
RED = 2
|
||||
|
|
|
@ -159,7 +159,7 @@ def channelInfo(chan):
|
|||
return packetHelper.buildPacket(packetIDs.server_channelInfo, [
|
||||
[chan, dataTypes.STRING],
|
||||
[channel.description, dataTypes.STRING],
|
||||
[channel.getConnectedUsersCount(), dataTypes.UINT16]
|
||||
[len(channel.connectedUsers), dataTypes.UINT16]
|
||||
])
|
||||
|
||||
def channelInfoEnd():
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
free = 1
|
||||
locked = 2
|
||||
notReady = 4
|
||||
ready = 8
|
||||
noMap = 16
|
||||
playing = 32
|
||||
occupied = 124
|
||||
playingQuit = 128
|
||||
FREE = 1
|
||||
LOCKED = 2
|
||||
NOT_READY = 4
|
||||
READY = 8
|
||||
NO_MAP = 16
|
||||
PLAYING = 32
|
||||
OCCUPIED = 124
|
||||
PLAYING_QUIT = 128
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
"""Bancho user ranks"""
|
||||
NORMAL = 0
|
||||
PLAYER = 1
|
||||
BAT = 2
|
||||
|
|
|
@ -17,9 +17,8 @@ def handle(userToken, packetData):
|
|||
return
|
||||
|
||||
# Send restricted message if needed
|
||||
if not userToken.restricted:
|
||||
if userUtils.isRestricted(userID):
|
||||
userToken.setRestricted()
|
||||
if userToken.restricted:
|
||||
userToken.checkRestricted(True)
|
||||
|
||||
# Change action packet
|
||||
packetData = clientPackets.userActionChange(packetData)
|
||||
|
|
|
@ -18,7 +18,7 @@ def handle(userToken, packetData):
|
|||
match = glob.matches.matches[matchID]
|
||||
|
||||
# Set slot or match mods according to modType
|
||||
if match.matchModMode == matchModModes.freeMod:
|
||||
if match.matchModMode == matchModModes.FREE_MOD:
|
||||
# Freemod
|
||||
# Host can set global DT/HT
|
||||
if userID == match.hostUserID:
|
||||
|
|
|
@ -81,11 +81,11 @@ def handle(userToken, packetData):
|
|||
# Reset ready if needed
|
||||
if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
|
||||
for i in range(0,16):
|
||||
if match.slots[i].status == slotStatuses.ready:
|
||||
match.slots[i].status = slotStatuses.notReady
|
||||
if match.slots[i].status == slotStatuses.READY:
|
||||
match.slots[i].status = slotStatuses.NOT_READY
|
||||
|
||||
# Reset mods if needed
|
||||
if match.matchModMode == matchModModes.normal:
|
||||
if match.matchModMode == matchModModes.NORMAL:
|
||||
# Reset slot mods if not freeMods
|
||||
for i in range(0,16):
|
||||
match.slots[i].mods = 0
|
||||
|
@ -94,21 +94,21 @@ def handle(userToken, packetData):
|
|||
match.mods = 0
|
||||
|
||||
# Set/reset teams
|
||||
if match.matchTeamType == matchTeamTypes.teamVs or match.matchTeamType == matchTeamTypes.tagTeamVs:
|
||||
if match.matchTeamType == matchTeamTypes.TEAM_VS or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
||||
# Set teams
|
||||
c=0
|
||||
for i in range(0,16):
|
||||
if match.slots[i].team == matchTeams.noTeam:
|
||||
match.slots[i].team = matchTeams.red if c % 2 == 0 else matchTeams.blue
|
||||
if match.slots[i].team == matchTeams.NO_TEAM:
|
||||
match.slots[i].team = matchTeams.RED if c % 2 == 0 else matchTeams.BLUE
|
||||
c+=1
|
||||
else:
|
||||
# Reset teams
|
||||
for i in range(0,16):
|
||||
match.slots[i].team = matchTeams.noTeam
|
||||
match.slots[i].team = matchTeams.NO_TEAM
|
||||
|
||||
# Force no freemods if tag coop
|
||||
if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs:
|
||||
match.matchModMode = matchModModes.normal
|
||||
if match.matchTeamType == matchTeamTypes.TAG_COOP or match.matchTeamType == matchTeamTypes.TAG_TEAM_VS:
|
||||
match.matchModMode = matchModModes.NORMAL
|
||||
|
||||
# Send updated settings
|
||||
match.sendUpdates()
|
||||
|
|
|
@ -192,19 +192,20 @@ def handle(tornadoRequest):
|
|||
# Get location and country from ip.zxq.co or database
|
||||
if glob.localize:
|
||||
# Get location and country from IP
|
||||
location = locationHelper.getLocation(requestIP)
|
||||
latitude, longitude = locationHelper.getLocation(requestIP)
|
||||
countryLetters = locationHelper.getCountry(requestIP)
|
||||
country = countryHelper.getCountryID(countryLetters)
|
||||
else:
|
||||
# Set location to 0,0 and get country from db
|
||||
log.warning("Location skipped")
|
||||
location = [0,0]
|
||||
latitude = 0
|
||||
longitude = 0
|
||||
countryLetters = "XX"
|
||||
country = countryHelper.getCountryID(userUtils.getCountry(userID))
|
||||
|
||||
# Set location and country
|
||||
responseToken.setLocation(location)
|
||||
responseToken.setCountry(country)
|
||||
responseToken.setLocation(latitude, longitude)
|
||||
responseToken.country = country
|
||||
|
||||
# Set country in db if user has no country (first bancho login)
|
||||
if userUtils.getCountry(userID) == "XX":
|
||||
|
|
|
@ -11,7 +11,7 @@ def handle(userToken, packetData):
|
|||
packetData = clientPackets.setAwayMessage(packetData)
|
||||
|
||||
# Set token away message
|
||||
userToken.setAwayMessage(packetData["awayMessage"])
|
||||
userToken.awayMessage = packetData["awayMessage"]
|
||||
|
||||
# Send private message from fokabot
|
||||
if packetData["awayMessage"] == "":
|
||||
|
|
|
@ -34,7 +34,5 @@ class handler(requestsManager.asyncRequestHandler):
|
|||
data["status"] = statusCode
|
||||
|
||||
# Send response
|
||||
#self.clear()
|
||||
self.write(json.dumps(data))
|
||||
self.set_status(statusCode)
|
||||
#self.finish(json.dumps(data))
|
||||
self.set_status(statusCode)
|
|
@ -44,7 +44,5 @@ class handler(requestsManager.asyncRequestHandler):
|
|||
data["status"] = statusCode
|
||||
|
||||
# Send response
|
||||
#self.clear()
|
||||
self.write(json.dumps(data))
|
||||
self.set_status(statusCode)
|
||||
#self.finish(json.dumps(data))
|
||||
|
|
|
@ -20,7 +20,5 @@ class handler(requestsManager.asyncRequestHandler):
|
|||
data["status"] = statusCode
|
||||
|
||||
# Send response
|
||||
#self.clear()
|
||||
self.write(json.dumps(data))
|
||||
self.set_status(statusCode)
|
||||
#self.finish(json.dumps(data))
|
||||
|
|
|
@ -20,7 +20,5 @@ class handler(requestsManager.asyncRequestHandler):
|
|||
data["status"] = statusCode
|
||||
|
||||
# Send response
|
||||
#self.clear()
|
||||
self.write(json.dumps(data))
|
||||
self.set_status(statusCode)
|
||||
#self.finish(json.dumps(data))
|
||||
|
|
|
@ -115,8 +115,6 @@ class handler(SentryMixin, requestsManager.asyncRequestHandler):
|
|||
return wrapper
|
||||
|
||||
eventHandler = {
|
||||
# TODO: Rename packets and events
|
||||
# TODO: Host check for multi
|
||||
packetIDs.client_changeAction: handleEvent(changeActionEvent),
|
||||
packetIDs.client_logout: handleEvent(logoutEvent),
|
||||
packetIDs.client_friendAdd: handleEvent(friendAddEvent),
|
||||
|
|
|
@ -12,14 +12,11 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
|
|||
"""
|
||||
Join a channel
|
||||
|
||||
userID -- user ID of the user that joins the channel. Optional.
|
||||
token can be used instead.
|
||||
token -- user token object of user that joins the channel. Optional.
|
||||
userID can be used instead.
|
||||
channel -- name of channe
|
||||
toIRC -- if True, send this channel join event to IRC. Must be true if joining from bancho.
|
||||
Optional. Defaukt: True
|
||||
return -- returns 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
:param userID: user ID of the user that joins the channel. Optional. token can be used instead.
|
||||
:param token: user token object of user that joins the channel. Optional. userID can be used instead.
|
||||
:param channel: channel name
|
||||
:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Default: True
|
||||
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
"""
|
||||
try:
|
||||
# Get token if not defined
|
||||
|
@ -77,15 +74,12 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
|
|||
"""
|
||||
Part a channel
|
||||
|
||||
userID -- user ID of the user that parts the channel. Optional.
|
||||
token can be used instead.
|
||||
token -- user token object of user that parts the channel. Optional.
|
||||
userID can be used instead.
|
||||
channel -- name of channel
|
||||
toIRC -- if True, send this channel join event to IRC. Must be true if joining from bancho.
|
||||
Optional. Defaukt: True
|
||||
kick -- if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False
|
||||
return -- returns 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
:param userID: user ID of the user that parts the channel. Optional. token can be used instead.
|
||||
:param token: user token object of user that parts the channel. Optional. userID can be used instead.
|
||||
:param channel: channel name
|
||||
:param toIRC: if True, send this channel join event to IRC. Must be true if joining from bancho. Optional. Default: True
|
||||
:param kick: if True, channel tab will be closed on client. Used when leaving lobby. Optional. Default: False
|
||||
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
"""
|
||||
try:
|
||||
# Get token if not defined
|
||||
|
@ -151,15 +145,12 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
|||
"""
|
||||
Send a message to osu!bancho and IRC server
|
||||
|
||||
fro -- sender username. Optional.
|
||||
You can use token instead of this if you wish.
|
||||
to -- receiver channel (if starts with #) or username
|
||||
message -- text of the message
|
||||
token -- sender token object.
|
||||
You can use this instead of fro if you are sending messages from bancho.
|
||||
Optional.
|
||||
toIRC -- if True, send the message to IRC. If False, send it to Bancho only.
|
||||
Optional. Default: True
|
||||
:param fro: sender username. Optional. token can be used instead
|
||||
:param to: receiver channel (if starts with #) or username
|
||||
:param message: text of the message
|
||||
:param token: sender token object. Optional. fro can be used instead
|
||||
:param toIRC: if True, send the message to IRC. If False, send it to Bancho only. Default: True
|
||||
:return: 0 if joined or other IRC code in case of error. Needed only on IRC-side
|
||||
"""
|
||||
try:
|
||||
tokenString = ""
|
||||
|
@ -231,7 +222,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
|||
raise exceptions.channelNoPermissionsException
|
||||
|
||||
# Everything seems fine, build recipients list and send packet
|
||||
recipients = glob.channels.channels[to].getConnectedUsers()[:]
|
||||
recipients = glob.channels.channels[to].connectedUsers[:]
|
||||
for key, value in glob.tokens.tokens.items():
|
||||
# Skip our client and irc clients
|
||||
if key == tokenString or value.irc == True:
|
||||
|
@ -312,6 +303,12 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
|
|||
|
||||
""" IRC-Bancho Connect/Disconnect/Join/Part interfaces"""
|
||||
def fixUsernameForBancho(username):
|
||||
"""
|
||||
Convert username from IRC format (without spaces) to Bancho format (with spaces)
|
||||
|
||||
:param username: username to convert
|
||||
:return: converted username
|
||||
"""
|
||||
# If there are no spaces or underscores in the name
|
||||
# return it
|
||||
if " " not in username and "_" not in username:
|
||||
|
@ -326,9 +323,22 @@ def fixUsernameForBancho(username):
|
|||
return username.replace("_", " ")
|
||||
|
||||
def fixUsernameForIRC(username):
|
||||
"""
|
||||
Convert an username from Bancho format to IRC format (underscores instead of spaces)
|
||||
|
||||
:param username: username to convert
|
||||
:return: converted username
|
||||
"""
|
||||
return username.replace(" ", "_")
|
||||
|
||||
def IRCConnect(username):
|
||||
"""
|
||||
Handle IRC login bancho-side.
|
||||
Add token and broadcast login packet.
|
||||
|
||||
:param username: username
|
||||
:return:
|
||||
"""
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
|
@ -339,6 +349,13 @@ def IRCConnect(username):
|
|||
log.info("{} logged in from IRC".format(username))
|
||||
|
||||
def IRCDisconnect(username):
|
||||
"""
|
||||
Handle IRC logout bancho-side.
|
||||
Remove token and broadcast logout packet.
|
||||
|
||||
:param username: username
|
||||
:return:
|
||||
"""
|
||||
token = glob.tokens.getTokenFromUsername(username)
|
||||
if token is None:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
|
@ -347,6 +364,13 @@ def IRCDisconnect(username):
|
|||
log.info("{} disconnected from IRC".format(username))
|
||||
|
||||
def IRCJoinChannel(username, channel):
|
||||
"""
|
||||
Handle IRC channel join bancho-side.
|
||||
|
||||
:param username: username
|
||||
:param channel: channel name
|
||||
:return: IRC return code
|
||||
"""
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
|
@ -357,6 +381,13 @@ def IRCJoinChannel(username, channel):
|
|||
return joinChannel(userID, channel)
|
||||
|
||||
def IRCPartChannel(username, channel):
|
||||
"""
|
||||
Handle IRC channel part bancho-side.
|
||||
|
||||
:param username: username
|
||||
:param channel: channel name
|
||||
:return: IRC return code
|
||||
"""
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
|
@ -364,9 +395,16 @@ def IRCPartChannel(username, channel):
|
|||
return partChannel(userID, channel)
|
||||
|
||||
def IRCAway(username, message):
|
||||
"""
|
||||
Handle IRC away command bancho-side.
|
||||
|
||||
:param username:
|
||||
:param message: away message
|
||||
:return: IRC return code
|
||||
"""
|
||||
userID = userUtils.getID(username)
|
||||
if not userID:
|
||||
log.warning("{} doesn't exist".format(username))
|
||||
return
|
||||
glob.tokens.getTokenFromUserID(userID).setAwayMessage(message)
|
||||
glob.tokens.getTokenFromUserID(userID).awayMessage = message
|
||||
return 305 if message == "" else 306
|
|
@ -5,9 +5,9 @@ class config:
|
|||
# Check if config.ini exists and load/generate it
|
||||
def __init__(self, file):
|
||||
"""
|
||||
Initialize a config object
|
||||
Initialize a config file object
|
||||
|
||||
file -- filename
|
||||
:param file: file name
|
||||
"""
|
||||
self.config = configparser.ConfigParser()
|
||||
self.default = True
|
||||
|
@ -25,9 +25,9 @@ class config:
|
|||
# Check if config.ini has all needed the keys
|
||||
def checkConfig(self):
|
||||
"""
|
||||
Check if this config has the required keys
|
||||
Check is the config file has all required keys
|
||||
|
||||
return -- True if valid, False if not
|
||||
:return: True if valid, False if not valid
|
||||
"""
|
||||
try:
|
||||
# Try to get all the required keys
|
||||
|
@ -79,7 +79,9 @@ class config:
|
|||
|
||||
def generateDefaultConfig(self):
|
||||
"""
|
||||
Open and set default keys for that config file
|
||||
Write a default config file to disk
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Open config.ini in write mode
|
||||
f = open(self.fileName, "w")
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
from common.constants import bcolors
|
||||
from objects import glob
|
||||
|
||||
def printServerStartHeader(asciiArt):
|
||||
def printServerStartHeader(asciiArt=True):
|
||||
"""
|
||||
Print server start header with optional ascii art
|
||||
Print server start message
|
||||
|
||||
asciiArt -- if True, will print ascii art too
|
||||
:param asciiArt: print BanchoBoat ascii art. Default: True
|
||||
:return:
|
||||
"""
|
||||
if asciiArt:
|
||||
print("{} _ __".format(bcolors.GREEN))
|
||||
|
@ -32,35 +33,43 @@ def printServerStartHeader(asciiArt):
|
|||
|
||||
def printNoNl(string):
|
||||
"""
|
||||
Print string without new line at the end
|
||||
Print a string without \n at the end
|
||||
|
||||
string -- string to print
|
||||
:param string: string to print
|
||||
:return:
|
||||
"""
|
||||
print(string, end="")
|
||||
|
||||
def printColored(string, color):
|
||||
"""
|
||||
Print colored string
|
||||
Print a colored string
|
||||
|
||||
string -- string to print
|
||||
color -- see bcolors.py
|
||||
:param string: string to print
|
||||
:param color: ANSI color code
|
||||
:return:
|
||||
"""
|
||||
print("{}{}{}".format(color, string, bcolors.ENDC))
|
||||
|
||||
def printError():
|
||||
"""
|
||||
Print error text FOR LOADING
|
||||
Print a red "Error"
|
||||
|
||||
:return:
|
||||
"""
|
||||
printColored("Error", bcolors.RED)
|
||||
|
||||
def printDone():
|
||||
"""
|
||||
Print error text FOR LOADING
|
||||
Print a green "Done"
|
||||
|
||||
:return:
|
||||
"""
|
||||
printColored("Done", bcolors.GREEN)
|
||||
|
||||
def printWarning():
|
||||
"""
|
||||
Print error text FOR LOADING
|
||||
Print a yellow "Warning"
|
||||
|
||||
:return:
|
||||
"""
|
||||
printColored("Warning", bcolors.YELLOW)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""Contains all country codes with their osu numeric code"""
|
||||
|
||||
# TODO: Update countries list
|
||||
countryCodes = {
|
||||
"LV": 132,
|
||||
"AD": 3,
|
||||
|
@ -255,12 +254,11 @@ countryCodes = {
|
|||
|
||||
def getCountryID(code):
|
||||
"""
|
||||
Get country ID for osu client
|
||||
Get osu country ID from country letters
|
||||
|
||||
code -- country name abbreviation (eg: US)
|
||||
return -- country code int
|
||||
:param code: country letters (eg: US)
|
||||
:return: country osu code
|
||||
"""
|
||||
|
||||
if code in countryCodes:
|
||||
return countryCodes[code]
|
||||
else:
|
||||
|
@ -270,10 +268,9 @@ def getCountryLetters(code):
|
|||
"""
|
||||
Get country letters from osu country ID
|
||||
|
||||
code -- country code int
|
||||
return -- country name (2 letters) (XX if code not found)
|
||||
:param code: osu country ID
|
||||
:return: country letters (XX if not found)
|
||||
"""
|
||||
|
||||
for key, value in countryCodes.items():
|
||||
if value == code:
|
||||
return key
|
||||
|
|
|
@ -7,10 +7,10 @@ from objects import glob
|
|||
|
||||
def getCountry(ip):
|
||||
"""
|
||||
Get country from IP address
|
||||
Get country from IP address using geoip api
|
||||
|
||||
ip -- IP Address
|
||||
return -- Country code (2 letters)
|
||||
:param ip: IP address
|
||||
:return: country code. XX if invalid.
|
||||
"""
|
||||
try:
|
||||
# Try to get country from Pikolo Aul's Go-Sanic ip API
|
||||
|
@ -22,15 +22,15 @@ def getCountry(ip):
|
|||
|
||||
def getLocation(ip):
|
||||
"""
|
||||
Get latitude and longitude from IP address
|
||||
Get latitude and longitude from IP address using geoip api
|
||||
|
||||
ip -- IP address
|
||||
return -- [latitude, longitude]
|
||||
:param ip: IP address
|
||||
:return: (latitude, longitude)
|
||||
"""
|
||||
try:
|
||||
# Try to get position from Pikolo Aul's Go-Sanic ip API
|
||||
result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["loc"].split(",")
|
||||
return [float(result[0]), float(result[1])]
|
||||
return (float(result[0]), float(result[1]))
|
||||
except:
|
||||
log.error("Error in get position")
|
||||
return [0,0]
|
||||
return (0, 0)
|
||||
|
|
|
@ -3,10 +3,10 @@ from constants import dataTypes
|
|||
|
||||
def uleb128Encode(num):
|
||||
"""
|
||||
Encode int -> uleb128
|
||||
Encode an int to uleb128
|
||||
|
||||
num -- int to encode
|
||||
return -- bytearray with encoded number
|
||||
:param num: int to encode
|
||||
:return: bytearray with encoded number
|
||||
"""
|
||||
arr = bytearray()
|
||||
length = 0
|
||||
|
@ -25,10 +25,10 @@ def uleb128Encode(num):
|
|||
|
||||
def uleb128Decode(num):
|
||||
"""
|
||||
Decode uleb128 -> int
|
||||
Decode a uleb128 to int
|
||||
|
||||
num -- encoded uleb128
|
||||
return -- list. [total, length]
|
||||
:param num: encoded uleb128 int
|
||||
:return: (total, length)
|
||||
"""
|
||||
shift = 0
|
||||
arr = [0,0] #total, length
|
||||
|
@ -45,14 +45,12 @@ def uleb128Decode(num):
|
|||
|
||||
def unpackData(data, dataType):
|
||||
"""
|
||||
Unpacks data according to dataType
|
||||
Unpacks a single section of a packet.
|
||||
|
||||
data -- bytes array to unpack
|
||||
dataType -- data type. See dataTypes.py
|
||||
|
||||
return -- unpacked bytes
|
||||
:param data: bytes to unpack
|
||||
:param dataType: data type
|
||||
:return: unpacked bytes
|
||||
"""
|
||||
|
||||
# Get right pack Type
|
||||
if dataType == dataTypes.UINT16:
|
||||
unpackType = "<H"
|
||||
|
@ -78,14 +76,12 @@ def unpackData(data, dataType):
|
|||
|
||||
def packData(__data, dataType):
|
||||
"""
|
||||
Packs data according to dataType
|
||||
Packs a single section of a packet.
|
||||
|
||||
data -- bytes to pack
|
||||
dataType -- data type. See dataTypes.py
|
||||
|
||||
return -- packed bytes
|
||||
:param __data: data to pack
|
||||
:param dataType: data type
|
||||
:return: packed bytes
|
||||
"""
|
||||
|
||||
data = bytes() # data to return
|
||||
pack = True # if True, use pack. False only with strings
|
||||
|
||||
|
@ -140,12 +136,11 @@ def packData(__data, dataType):
|
|||
|
||||
def buildPacket(__packet, __packetData=None):
|
||||
"""
|
||||
Build a packet
|
||||
Builds a packet
|
||||
|
||||
packet -- packet id (int)
|
||||
packetData -- list [[data, dataType], [data, dataType], ...]
|
||||
|
||||
return -- packet bytes
|
||||
:param __packet: packet ID
|
||||
:param __packetData: packet structure [[data, dataType], [data, dataType], ...]
|
||||
:return: packet bytes
|
||||
"""
|
||||
# Set some variables
|
||||
if __packetData is None:
|
||||
|
@ -170,33 +165,31 @@ def buildPacket(__packet, __packetData=None):
|
|||
|
||||
def readPacketID(stream):
|
||||
"""
|
||||
Read packetID from stream (0-1 bytes)
|
||||
Read packetID (first two bytes) from a packet
|
||||
|
||||
stream -- data stream
|
||||
return -- packet ID (int)
|
||||
:param stream: packet bytes
|
||||
:return: packet ID
|
||||
"""
|
||||
return unpackData(stream[0:2], dataTypes.UINT16)
|
||||
|
||||
def readPacketLength(stream):
|
||||
"""
|
||||
Read packet length from stream (3-4-5-6 bytes)
|
||||
Read packet data length (3:7 bytes) from a packet
|
||||
|
||||
stream -- data stream
|
||||
return -- packet length (int)
|
||||
:param stream: packet bytes
|
||||
:return: packet data length
|
||||
"""
|
||||
return unpackData(stream[3:7], dataTypes.UINT32)
|
||||
|
||||
|
||||
def readPacketData(stream, structure=None, hasFirstBytes = True):
|
||||
"""
|
||||
Read packet data from stream according to structure
|
||||
|
||||
stream -- data stream
|
||||
structure -- [[name, dataType], [name, dataType], ...]
|
||||
hasFirstBytes -- if True, stream has packetID and length bytes.
|
||||
if False, stream has only packetData.
|
||||
Optional. Default: True
|
||||
return -- dictionary. key: name, value: read data
|
||||
Read packet data from `stream` according to `structure`
|
||||
:param stream: packet bytes
|
||||
:param structure: packet structure: [[name, dataType], [name, dataType], ...]
|
||||
:param hasFirstBytes: if True, `stream` has packetID and length bytes.
|
||||
if False, `stream` has only packet data. Default: True
|
||||
:return: {name: unpackedValue, ...}
|
||||
"""
|
||||
# Read packet ID (first 2 bytes)
|
||||
if structure is None:
|
||||
|
|
|
@ -17,6 +17,7 @@ from objects import glob
|
|||
def dispose():
|
||||
"""
|
||||
Perform some clean up. Called on shutdown.
|
||||
|
||||
:return:
|
||||
"""
|
||||
print("> Disposing server... ")
|
||||
|
@ -27,7 +28,7 @@ def runningUnderUnix():
|
|||
"""
|
||||
Get if the server is running under UNIX or NT
|
||||
|
||||
return --- True if running under UNIX, otherwise False
|
||||
:return: True if running under UNIX, otherwise False
|
||||
"""
|
||||
return True if os.name == "posix" else False
|
||||
|
||||
|
@ -35,9 +36,11 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
|
|||
"""
|
||||
Schedule a server shutdown/restart
|
||||
|
||||
sendRestartTime -- time (seconds) to wait before sending server restart packets to every client
|
||||
restart -- if True, server will restart. if False, server will shudown
|
||||
message -- if set, send that message to every client to warn about the shutdown/restart
|
||||
:param sendRestartTime: time (seconds) to wait before sending server restart packets to every client
|
||||
:param restart: if True, server will restart. if False, server will shudown
|
||||
:param message: if set, send that message to every client to warn about the shutdown/restart
|
||||
:param delay: additional restart delay in seconds. Default: 20
|
||||
:return:
|
||||
"""
|
||||
# Console output
|
||||
log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay))
|
||||
|
@ -61,13 +64,21 @@ def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
|
|||
threading.Timer(sendRestartTime+delay, action).start()
|
||||
|
||||
def restartServer():
|
||||
"""Restart pep.py script"""
|
||||
"""
|
||||
Restart pep.py
|
||||
|
||||
:return:
|
||||
"""
|
||||
log.info("Restarting pep.py...")
|
||||
dispose()
|
||||
os.execv(sys.executable, [sys.executable] + sys.argv)
|
||||
|
||||
def shutdownServer():
|
||||
"""Shutdown pep.py"""
|
||||
"""
|
||||
Shutdown pep.py
|
||||
|
||||
:return:
|
||||
"""
|
||||
log.info("Shutting down pep.py...")
|
||||
dispose()
|
||||
sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
|
||||
|
@ -77,7 +88,7 @@ def getSystemInfo():
|
|||
"""
|
||||
Get a dictionary with some system/server info
|
||||
|
||||
return -- ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"]
|
||||
:return: ["unix", "connectedUsers", "webServer", "cpuUsage", "totalMemory", "usedMemory", "loadAverage"]
|
||||
"""
|
||||
data = {"unix": runningUnderUnix(), "connectedUsers": len(glob.tokens.tokens), "matches": len(glob.matches.matches)}
|
||||
|
||||
|
|
125
irc/ircserver.py
125
irc/ircserver.py
|
@ -22,18 +22,15 @@ from objects import glob
|
|||
|
||||
|
||||
class Client:
|
||||
"""
|
||||
IRC Client object
|
||||
"""
|
||||
__linesep_regexp = re.compile(r"\r?\n")
|
||||
|
||||
|
||||
def __init__(self, server, sock):
|
||||
"""
|
||||
Initialize a Client object
|
||||
|
||||
server -- server object
|
||||
sock -- socket connection object
|
||||
:param server: server object
|
||||
:param sock: socket connection object
|
||||
:return:
|
||||
"""
|
||||
self.__timestamp = time.time()
|
||||
self.__readbuffer = ""
|
||||
|
@ -60,7 +57,8 @@ class Client:
|
|||
Add a message (basic string) to client buffer.
|
||||
This is the lowest possible level.
|
||||
|
||||
msg -- message to add
|
||||
:param msg: message to add
|
||||
:return:
|
||||
"""
|
||||
self.__writebuffer += msg + "\r\n"
|
||||
|
||||
|
@ -69,7 +67,7 @@ class Client:
|
|||
"""
|
||||
Return this client's write buffer size
|
||||
|
||||
return -- write buffer size
|
||||
:return: write buffer size
|
||||
"""
|
||||
return len(self.__writebuffer)
|
||||
|
||||
|
@ -78,7 +76,8 @@ class Client:
|
|||
"""
|
||||
Add an IRC-like message to client buffer.
|
||||
|
||||
msg -- message (without IRC stuff)
|
||||
:param msg: message (without IRC stuff)
|
||||
:return:
|
||||
"""
|
||||
self.message(":{} {}".format(self.server.host, msg))
|
||||
|
||||
|
@ -87,10 +86,11 @@ class Client:
|
|||
"""
|
||||
Add an IRC-like message to client buffer with code
|
||||
|
||||
code -- response code
|
||||
message -- response message
|
||||
nickname -- receiver nickname
|
||||
channel -- optional
|
||||
:param code: response code
|
||||
:param message: response message
|
||||
:param nickname: receiver nickname
|
||||
:param channel: optional
|
||||
:return:
|
||||
"""
|
||||
if nickname == "":
|
||||
nickname = self.IRCUsername
|
||||
|
@ -103,7 +103,8 @@ class Client:
|
|||
"""
|
||||
Add a 403 reply (no such channel) to client buffer.
|
||||
|
||||
channel -- meh
|
||||
:param channel:
|
||||
:return:
|
||||
"""
|
||||
self.replyCode(403, "{} :No such channel".format(channel))
|
||||
|
||||
|
@ -112,7 +113,8 @@ class Client:
|
|||
"""
|
||||
Add a 461 reply (not enough parameters) to client buffer
|
||||
|
||||
command -- command that had not enough parameters
|
||||
:param command: name of the command that had not enough parameters
|
||||
:return:
|
||||
"""
|
||||
self.replyCode(403, "{} :Not enough parameters".format(command))
|
||||
|
||||
|
@ -121,8 +123,9 @@ class Client:
|
|||
"""
|
||||
Disconnects this client from the IRC server
|
||||
|
||||
quitmsg -- IRC quit message. Default: 'Client quit'
|
||||
callLogout -- if True, call logoutEvent on bancho
|
||||
:param quitmsg: IRC quit message. Default: 'Client quit'
|
||||
:param callLogout: if True, call logoutEvent on bancho
|
||||
:return:
|
||||
"""
|
||||
# Send error to client and close socket
|
||||
self.message("ERROR :{}".format(quitmsg))
|
||||
|
@ -138,7 +141,11 @@ class Client:
|
|||
|
||||
|
||||
def readSocket(self):
|
||||
"""Read data coming from this client socket"""
|
||||
"""
|
||||
Read data coming from this client socket
|
||||
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
# Try to read incoming data from socket
|
||||
data = self.socket.recv(2 ** 10)
|
||||
|
@ -161,7 +168,11 @@ class Client:
|
|||
|
||||
|
||||
def parseBuffer(self):
|
||||
"""Parse self.__readbuffer, get command, arguments and call its handler"""
|
||||
"""
|
||||
Parse self.__readbuffer, get command, arguments and call its handler
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Get lines from buffer
|
||||
lines = self.__linesep_regexp.split(self.__readbuffer)
|
||||
self.__readbuffer = lines[-1]
|
||||
|
@ -198,7 +209,11 @@ class Client:
|
|||
|
||||
|
||||
def writeSocket(self):
|
||||
"""Write buffer to socket"""
|
||||
"""
|
||||
Write buffer to socket
|
||||
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
sent = self.socket.send(self.__writebuffer.encode())
|
||||
log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent]))
|
||||
|
@ -206,9 +221,13 @@ class Client:
|
|||
except socket.error as x:
|
||||
self.disconnect(str(x))
|
||||
|
||||
|
||||
def checkAlive(self):
|
||||
"""Check if this client is still connected"""
|
||||
"""
|
||||
Check if this client is still connected.
|
||||
If the client is dead, disconnect it.
|
||||
|
||||
:return:
|
||||
"""
|
||||
now = time.time()
|
||||
if self.__timestamp + 180 < now:
|
||||
self.disconnect("ping timeout")
|
||||
|
@ -224,11 +243,19 @@ class Client:
|
|||
|
||||
|
||||
def sendLusers(self):
|
||||
"""Send lusers response to this client"""
|
||||
"""
|
||||
Send lusers response to this client
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.replyCode(251, "There are {} users and 0 services on 1 server".format(len(glob.tokens.tokens)))
|
||||
|
||||
def sendMotd(self):
|
||||
"""Send MOTD to this client"""
|
||||
"""
|
||||
Send MOTD to this client
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.replyCode(375, "- {} Message of the day - ".format(self.server.host))
|
||||
if len(self.server.motd) == 0:
|
||||
self.replyCode(422, "MOTD File is missing")
|
||||
|
@ -340,13 +367,13 @@ class Client:
|
|||
|
||||
# TODO: Part all channels
|
||||
if arguments[0] == "0":
|
||||
return
|
||||
'''for (channelname, channel) in self.channels.items():
|
||||
self.message_channel(channel, "PART", channelname, True)
|
||||
self.channel_log(channel, "left", meta=True)
|
||||
server.remove_member_from_channel(self, channelname)
|
||||
self.channels = {}
|
||||
return'''
|
||||
return
|
||||
|
||||
# Get channels to join list
|
||||
channels = arguments[0].split(",")
|
||||
|
@ -375,7 +402,7 @@ class Client:
|
|||
self.replyCode(332, description, channel=channel)
|
||||
|
||||
# Build connected users list
|
||||
users = glob.channels.channels[channel].getConnectedUsers()[:]
|
||||
users = glob.channels.channels[channel].connectedUsers[:]
|
||||
usernames = []
|
||||
for user in users:
|
||||
token = glob.tokens.getTokenFromUserID(user)
|
||||
|
@ -488,11 +515,14 @@ class Client:
|
|||
pass
|
||||
|
||||
def awayHandler(self, command, arguments):
|
||||
"""AWAY command handler"""
|
||||
response = chat.IRCAway(self.banchoUsername, " ".join(arguments))
|
||||
self.replyCode(response, "You are no longer marked as being away" if response == 305 else "You have been marked as being away")
|
||||
|
||||
def mainHandler(self, command, arguments):
|
||||
"""Handler for post-login commands"""
|
||||
"""
|
||||
Handler for post-login commands
|
||||
"""
|
||||
handlers = {
|
||||
"AWAY": self.awayHandler,
|
||||
#"ISON": ison_handler,
|
||||
|
@ -522,17 +552,18 @@ class Client:
|
|||
|
||||
class Server:
|
||||
def __init__(self, port):
|
||||
#self.host = socket.getfqdn("127.0.0.1")[:63]
|
||||
self.host = glob.conf.config["irc"]["hostname"]
|
||||
self.port = port
|
||||
self.clients = {} # Socket --> Client instance.
|
||||
self.clients = {} # Socket - - > Client instance.
|
||||
self.motd = ["Welcome to pep.py's embedded IRC server!", "This is a VERY simple IRC server and it's still in beta.", "Expect things to crash and not work as expected :("]
|
||||
|
||||
def forceDisconnection(self, username, isBanchoUsername=True):
|
||||
"""
|
||||
Disconnect someone from IRC if connected
|
||||
|
||||
username -- victim
|
||||
:param username: victim
|
||||
:param isBanchoUsername: if True, username is a bancho username, else convert it to a bancho username
|
||||
:return:
|
||||
"""
|
||||
for _, value in self.clients.items():
|
||||
if (value.IRCUsername == username and not isBanchoUsername) or (value.banchoUsername == username and isBanchoUsername):
|
||||
|
@ -543,8 +574,9 @@ class Server:
|
|||
"""
|
||||
Let every IRC client connected to a specific client know that 'username' joined the channel from bancho
|
||||
|
||||
username -- username of bancho user
|
||||
channel -- joined channel name
|
||||
:param username: username of bancho user
|
||||
:param channel: joined channel name
|
||||
:return:
|
||||
"""
|
||||
username = chat.fixUsernameForIRC(username)
|
||||
for _, value in self.clients.items():
|
||||
|
@ -555,8 +587,9 @@ class Server:
|
|||
"""
|
||||
Let every IRC client connected to a specific client know that 'username' parted the channel from bancho
|
||||
|
||||
username -- username of bancho user
|
||||
channel -- joined channel name
|
||||
:param username: username of bancho user
|
||||
:param channel: joined channel name
|
||||
:return:
|
||||
"""
|
||||
username = chat.fixUsernameForIRC(username)
|
||||
for _, value in self.clients.items():
|
||||
|
@ -567,9 +600,10 @@ class Server:
|
|||
"""
|
||||
Send a message to IRC when someone sends it from bancho
|
||||
|
||||
fro -- sender username
|
||||
to -- receiver username
|
||||
message -- text of the message
|
||||
:param fro: sender username
|
||||
:param to: receiver username
|
||||
:param message: text of the message
|
||||
:return:
|
||||
"""
|
||||
fro = chat.fixUsernameForIRC(fro)
|
||||
to = chat.fixUsernameForIRC(to)
|
||||
|
@ -589,14 +623,19 @@ class Server:
|
|||
"""
|
||||
Remove a client from connected clients
|
||||
|
||||
client -- client object
|
||||
quitmsg -- QUIT argument, useless atm
|
||||
:param client: client object
|
||||
:param quitmsg: QUIT argument, useless atm
|
||||
:return:
|
||||
"""
|
||||
if client.socket in self.clients:
|
||||
del self.clients[client.socket]
|
||||
|
||||
def start(self):
|
||||
"""Start IRC server main loop"""
|
||||
"""
|
||||
Start IRC server main loop
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Sentry
|
||||
if glob.sentry:
|
||||
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])
|
||||
|
@ -653,5 +692,11 @@ class Server:
|
|||
sentryClient.captureException()
|
||||
|
||||
def main(port=6667):
|
||||
"""
|
||||
Create and start an IRC server
|
||||
|
||||
:param port: IRC port. Default: 6667
|
||||
:return:
|
||||
"""
|
||||
glob.ircServer = Server(port)
|
||||
glob.ircServer.start()
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
from objects import glob
|
||||
|
||||
class channel:
|
||||
"""
|
||||
A chat channel
|
||||
"""
|
||||
|
||||
def __init__(self, name, description, publicRead, publicWrite, temp, hidden):
|
||||
"""
|
||||
Create a new chat channel object
|
||||
|
||||
name -- channel name
|
||||
description -- channel description
|
||||
publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins
|
||||
publicWrite -- bool, same as public read but relative to write permissions
|
||||
temp -- if True, channel will be deleted when there's no one in the channel
|
||||
hidden -- if True, channel won't be shown in channels list
|
||||
:param name: channel name
|
||||
:param description: channel description
|
||||
:param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins
|
||||
:param publicWrite: same as public read, but regards writing permissions
|
||||
:param temp: if True, this channel will be deleted when there's no one in this channel
|
||||
:param hidden: if True, thic channel won't be shown in channels list
|
||||
"""
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
@ -36,7 +32,8 @@ class channel:
|
|||
"""
|
||||
Add a user to connected users
|
||||
|
||||
userID -- user ID that joined the channel
|
||||
:param userID:
|
||||
:return:
|
||||
"""
|
||||
if userID not in self.connectedUsers:
|
||||
self.connectedUsers.append(userID)
|
||||
|
@ -45,7 +42,8 @@ class channel:
|
|||
"""
|
||||
Remove a user from connected users
|
||||
|
||||
userID -- user ID that left the channel
|
||||
:param userID:
|
||||
:return:
|
||||
"""
|
||||
if userID in self.connectedUsers:
|
||||
self.connectedUsers.remove(userID)
|
||||
|
@ -53,20 +51,4 @@ class channel:
|
|||
# Remove temp channels if empty or there's only fokabot connected
|
||||
l = len(self.connectedUsers)
|
||||
if self.temp == True and ((l == 0) or (l == 1 and 999 in self.connectedUsers)):
|
||||
glob.channels.removeChannel(self.name)
|
||||
|
||||
def getConnectedUsers(self):
|
||||
"""
|
||||
Get connected user IDs list
|
||||
|
||||
return -- connectedUsers list
|
||||
"""
|
||||
return self.connectedUsers
|
||||
|
||||
def getConnectedUsersCount(self):
|
||||
"""
|
||||
Count connected users
|
||||
|
||||
return -- connected users number
|
||||
"""
|
||||
return len(self.connectedUsers)
|
||||
glob.channels.removeChannel(self.name)
|
|
@ -4,18 +4,14 @@ from objects import glob
|
|||
|
||||
|
||||
class channelList:
|
||||
"""
|
||||
Channel list
|
||||
|
||||
channels -- dictionary. key: channel name, value: channel object
|
||||
"""
|
||||
channels = {}
|
||||
def __init__(self):
|
||||
self.channels = {}
|
||||
|
||||
def loadChannels(self):
|
||||
"""
|
||||
Load chat channels from db and add them to channels dictionary
|
||||
Load chat channels from db and add them to channels list
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Get channels from DB
|
||||
channels = glob.db.fetchAll("SELECT * FROM bancho_channels")
|
||||
|
||||
|
@ -28,14 +24,15 @@ class channelList:
|
|||
|
||||
def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False):
|
||||
"""
|
||||
Add a channel object to channels dictionary
|
||||
Add a channel to channels list
|
||||
|
||||
name -- channel name
|
||||
description -- channel description
|
||||
publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins
|
||||
publicWrite -- bool, same as public read but relative to write permissions
|
||||
temp -- if True, channel will be deleted when there's no one in the channel. Optional. Default = False.
|
||||
hidden -- if True, channel will be hidden in channels list. Optional. Default = False.
|
||||
:param name: channel name
|
||||
:param description: channel description
|
||||
:param publicRead: if True, this channel can be read by everyone. If False, it can be read only by mods/admins
|
||||
:param publicWrite: same as public read, but regards writing permissions
|
||||
:param temp: if True, this channel will be deleted when there's no one in this channel
|
||||
:param hidden: if True, thic channel won't be shown in channels list
|
||||
:return:
|
||||
"""
|
||||
self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden)
|
||||
log.info("Created channel {}".format(name))
|
||||
|
@ -45,8 +42,8 @@ class channelList:
|
|||
Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel
|
||||
and it's hidden in channels list
|
||||
|
||||
name -- channel name
|
||||
return -- True if channel was created, False if failed
|
||||
:param name: channel name
|
||||
:return: True if the channel was created, otherwise False
|
||||
"""
|
||||
if name in self.channels:
|
||||
return False
|
||||
|
@ -57,7 +54,8 @@ class channelList:
|
|||
"""
|
||||
Removes a channel from channels list
|
||||
|
||||
name -- channel name
|
||||
:param name: channel name
|
||||
:return:
|
||||
"""
|
||||
if name not in self.channels:
|
||||
log.debug("{} is not in channels list".format(name))
|
||||
|
|
|
@ -1,9 +1,20 @@
|
|||
class chatFilters:
|
||||
def __init__(self, fileName="filters.txt"):
|
||||
"""
|
||||
Initialize chat filters
|
||||
|
||||
:param fileName: name of the file containing filters. Default: filters.txt
|
||||
"""
|
||||
self.filters = {}
|
||||
self.loadFilters(fileName)
|
||||
|
||||
def loadFilters(self, fileName="filters.txt"):
|
||||
"""
|
||||
Load filters from a file
|
||||
|
||||
:param fileName: name of the file containing filters. Default: filters.txt
|
||||
:return:
|
||||
"""
|
||||
# Reset chat filters
|
||||
self.filters = {}
|
||||
|
||||
|
@ -19,6 +30,12 @@ class chatFilters:
|
|||
self.filters[lineSplit[0].lower()] = lineSplit[1].replace("\n", "")
|
||||
|
||||
def filterMessage(self, message):
|
||||
"""
|
||||
Replace forbidden words with filtered ones
|
||||
|
||||
:param message: normal message
|
||||
:return: filtered message
|
||||
"""
|
||||
# Split words by spaces
|
||||
messageTemp = message.split(" ")
|
||||
|
||||
|
|
|
@ -12,29 +12,35 @@ from objects import glob
|
|||
npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)")
|
||||
|
||||
def connect():
|
||||
"""Add FokaBot to connected users and send userpanel/stats packet to everyone"""
|
||||
"""
|
||||
Connect FokaBot to Bancho
|
||||
|
||||
:return:
|
||||
"""
|
||||
token = glob.tokens.addToken(999)
|
||||
token.actionID = actions.IDLE
|
||||
glob.streams.broadcast("main", serverPackets.userPanel(999))
|
||||
glob.streams.broadcast("main", serverPackets.userStats(999))
|
||||
|
||||
def disconnect():
|
||||
"""Remove FokaBot from connected users"""
|
||||
"""
|
||||
Disconnect FokaBot from Bancho
|
||||
|
||||
:return:
|
||||
"""
|
||||
glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999))
|
||||
|
||||
def fokabotResponse(fro, chan, message):
|
||||
"""
|
||||
Check if a message has triggered fokabot (and return its response)
|
||||
Check if a message has triggered FokaBot
|
||||
|
||||
fro -- sender username (for permissions stuff with admin commands)
|
||||
chan -- channel name
|
||||
message -- message
|
||||
|
||||
return -- fokabot's response string or False
|
||||
:param fro: sender username
|
||||
:param chan: channel name (or receiver username)
|
||||
:param message: chat mesage
|
||||
:return: FokaBot's response or False if no response
|
||||
"""
|
||||
for i in fokabotCommands.commands:
|
||||
# Loop though all commands
|
||||
#if i["trigger"] in message:
|
||||
if generalUtils.strContains(message, i["trigger"]):
|
||||
# message has triggered a command
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ try:
|
|||
with open("version") as f:
|
||||
VERSION = f.read()
|
||||
if VERSION == "":
|
||||
raise
|
||||
raise Exception
|
||||
except:
|
||||
VERSION = "¯\_(xd)_/¯"
|
||||
|
||||
|
|
192
objects/match.py
192
objects/match.py
|
@ -13,7 +13,7 @@ from objects import glob
|
|||
|
||||
class slot:
|
||||
def __init__(self):
|
||||
self.status = slotStatuses.free
|
||||
self.status = slotStatuses.FREE
|
||||
self.team = 0
|
||||
self.userID = -1
|
||||
self.user = None
|
||||
|
@ -23,19 +23,18 @@ class slot:
|
|||
self.complete = False
|
||||
|
||||
class match:
|
||||
"""Multiplayer match object"""
|
||||
def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
|
||||
"""
|
||||
Create a new match object
|
||||
|
||||
matchID -- match progressive identifier
|
||||
matchName -- match name, string
|
||||
matchPassword -- match md5 password. Leave empty for no password
|
||||
beatmapID -- beatmap ID
|
||||
beatmapName -- beatmap name, string
|
||||
beatmapMD5 -- beatmap md5 hash, string
|
||||
gameMode -- game mode ID. See gameModes.py
|
||||
hostUserID -- user id of the host
|
||||
:param matchID: match progressive identifier
|
||||
:param matchName: match name, string
|
||||
:param matchPassword: match md5 password. Leave empty for no password
|
||||
:param beatmapID: beatmap ID
|
||||
:param beatmapName: beatmap name, string
|
||||
:param beatmapMD5: beatmap md5 hash, string
|
||||
:param gameMode: game mode ID. See gameModes.py
|
||||
:param hostUserID: user id of the host
|
||||
"""
|
||||
self.matchID = matchID
|
||||
self.streamName = "multi/{}".format(self.matchID)
|
||||
|
@ -49,9 +48,9 @@ class match:
|
|||
self.beatmapMD5 = beatmapMD5
|
||||
self.hostUserID = hostUserID
|
||||
self.gameMode = gameMode
|
||||
self.matchScoringType = matchScoringTypes.score # default values
|
||||
self.matchTeamType = matchTeamTypes.headToHead # default value
|
||||
self.matchModMode = matchModModes.normal # default value
|
||||
self.matchScoringType = matchScoringTypes.SCORE # default values
|
||||
self.matchTeamType = matchTeamTypes.HEAD_TO_HEAD # default value
|
||||
self.matchModMode = matchModModes.NORMAL # default value
|
||||
self.seed = 0
|
||||
self.matchDataCache = bytes()
|
||||
|
||||
|
@ -70,6 +69,8 @@ class match:
|
|||
def getMatchData(self):
|
||||
"""
|
||||
Return binary match data structure for packetHelper
|
||||
|
||||
:return:
|
||||
"""
|
||||
# General match info
|
||||
# TODO: Test without safe copy, the error might have been caused by outdated python bytecode cache
|
||||
|
@ -109,7 +110,7 @@ class match:
|
|||
])
|
||||
|
||||
# Slot mods if free mod is enabled
|
||||
if safeMatch.matchModMode == matchModModes.freeMod:
|
||||
if safeMatch.matchModMode == matchModModes.FREE_MOD:
|
||||
for i in range(0,16):
|
||||
struct.append([safeMatch.slots[i].mods, dataTypes.UINT32])
|
||||
|
||||
|
@ -123,7 +124,8 @@ class match:
|
|||
"""
|
||||
Set room host to newHost and send him host packet
|
||||
|
||||
newHost -- new host userID
|
||||
:param newHost: new host userID
|
||||
:return:
|
||||
"""
|
||||
slotID = self.getUserSlotID(newHost)
|
||||
if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
|
||||
|
@ -135,7 +137,21 @@ class match:
|
|||
log.info("MPROOM{}: {} is now the host".format(self.matchID, token.username))
|
||||
|
||||
def setSlot(self, slotID, status = None, team = None, user = "", mods = None, loaded = None, skip = None, complete = None):
|
||||
#self.setSlot(i, slotStatuses.notReady, 0, user, 0)
|
||||
"""
|
||||
Set data for a specific slot.
|
||||
All fields but slotID are optional.
|
||||
Skipped fields won't be edited.
|
||||
|
||||
:param slotID: slot ID
|
||||
:param status: new status
|
||||
:param team: new team
|
||||
:param user: new user id
|
||||
:param mods: new mods
|
||||
:param loaded: new loaded status
|
||||
:param skip: new skip value
|
||||
:param complete: new completed value
|
||||
:return:
|
||||
"""
|
||||
if status is not None:
|
||||
self.slots[slotID].status = status
|
||||
|
||||
|
@ -161,8 +177,9 @@ class match:
|
|||
"""
|
||||
Set slotID mods. Same as calling setSlot and then sendUpdate
|
||||
|
||||
slotID -- slot number
|
||||
mods -- new mods
|
||||
:param slotID: slot number
|
||||
:param mods: new mods
|
||||
:return:
|
||||
"""
|
||||
# Set new slot data and send update
|
||||
self.setSlot(slotID, mods=mods)
|
||||
|
@ -174,14 +191,15 @@ class match:
|
|||
Switch slotID ready/not ready status
|
||||
Same as calling setSlot and then sendUpdate
|
||||
|
||||
slotID -- slot number
|
||||
:param slotID: slot number
|
||||
:return:
|
||||
"""
|
||||
# Update ready status and setnd update
|
||||
oldStatus = self.slots[slotID].status
|
||||
if oldStatus == slotStatuses.ready:
|
||||
newStatus = slotStatuses.notReady
|
||||
if oldStatus == slotStatuses.READY:
|
||||
newStatus = slotStatuses.NOT_READY
|
||||
else:
|
||||
newStatus = slotStatuses.ready
|
||||
newStatus = slotStatuses.READY
|
||||
self.setSlot(slotID, newStatus)
|
||||
self.sendUpdates()
|
||||
log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
|
||||
|
@ -191,13 +209,14 @@ class match:
|
|||
Lock a slot
|
||||
Same as calling setSlot and then sendUpdate
|
||||
|
||||
slotID -- slot number
|
||||
:param slotID: slot number
|
||||
:return:
|
||||
"""
|
||||
# Check if slot is already locked
|
||||
if self.slots[slotID].status == slotStatuses.locked:
|
||||
newStatus = slotStatuses.free
|
||||
if self.slots[slotID].status == slotStatuses.LOCKED:
|
||||
newStatus = slotStatuses.FREE
|
||||
else:
|
||||
newStatus = slotStatuses.locked
|
||||
newStatus = slotStatuses.LOCKED
|
||||
|
||||
# Send updated settings to kicked user, so he returns to lobby
|
||||
if self.slots[slotID].user is not None and self.slots[slotID].user in glob.tokens.tokens:
|
||||
|
@ -208,13 +227,14 @@ class match:
|
|||
|
||||
# Send updates to everyone else
|
||||
self.sendUpdates()
|
||||
log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.locked else "unlocked"))
|
||||
log.info("MPROOM{}: Slot{} {}".format(self.matchID, slotID, "locked" if newStatus == slotStatuses.LOCKED else "unlocked"))
|
||||
|
||||
def playerLoaded(self, userID):
|
||||
"""
|
||||
Set a player loaded status to True
|
||||
|
||||
userID -- ID of user
|
||||
:param userID: ID of user
|
||||
:return:
|
||||
"""
|
||||
slotID = self.getUserSlotID(userID)
|
||||
if slotID is None:
|
||||
|
@ -228,7 +248,7 @@ class match:
|
|||
total = 0
|
||||
loaded = 0
|
||||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.playing:
|
||||
if self.slots[i].status == slotStatuses.PLAYING:
|
||||
total+=1
|
||||
if self.slots[i].loaded:
|
||||
loaded+=1
|
||||
|
@ -237,7 +257,11 @@ class match:
|
|||
self.allPlayersLoaded()
|
||||
|
||||
def allPlayersLoaded(self):
|
||||
"""Send allPlayersLoaded packet to every playing usr in match"""
|
||||
"""
|
||||
Send allPlayersLoaded packet to every playing usr in match
|
||||
|
||||
:return:
|
||||
"""
|
||||
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded())
|
||||
log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID))
|
||||
|
||||
|
@ -245,7 +269,8 @@ class match:
|
|||
"""
|
||||
Set a player skip status to True
|
||||
|
||||
userID -- ID of user
|
||||
:param userID: ID of user
|
||||
:return:
|
||||
"""
|
||||
slotID = self.getUserSlotID(userID)
|
||||
if slotID is None:
|
||||
|
@ -263,7 +288,7 @@ class match:
|
|||
total = 0
|
||||
skipped = 0
|
||||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.playing:
|
||||
if self.slots[i].status == slotStatuses.PLAYING:
|
||||
total+=1
|
||||
if self.slots[i].skip:
|
||||
skipped+=1
|
||||
|
@ -272,7 +297,11 @@ class match:
|
|||
self.allPlayersSkipped()
|
||||
|
||||
def allPlayersSkipped(self):
|
||||
"""Send allPlayersSkipped packet to every playing usr in match"""
|
||||
"""
|
||||
Send allPlayersSkipped packet to every playing usr in match
|
||||
|
||||
:return:
|
||||
"""
|
||||
glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
|
||||
log.info("MPROOM{}: All players have skipped!".format(self.matchID))
|
||||
|
||||
|
@ -280,7 +309,7 @@ class match:
|
|||
"""
|
||||
Set userID's slot completed to True
|
||||
|
||||
userID -- ID of user
|
||||
:param userID: ID of user
|
||||
"""
|
||||
slotID = self.getUserSlotID(userID)
|
||||
if slotID is None:
|
||||
|
@ -294,7 +323,7 @@ class match:
|
|||
total = 0
|
||||
completed = 0
|
||||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.playing:
|
||||
if self.slots[i].status == slotStatuses.PLAYING:
|
||||
total+=1
|
||||
if self.slots[i].complete:
|
||||
completed+=1
|
||||
|
@ -303,15 +332,18 @@ class match:
|
|||
self.allPlayersCompleted()
|
||||
|
||||
def allPlayersCompleted(self):
|
||||
"""Cleanup match stuff and send match end packet to everyone"""
|
||||
"""
|
||||
Cleanup match stuff and send match end packet to everyone
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Reset inProgress
|
||||
self.inProgress = False
|
||||
|
||||
# Reset slots
|
||||
for i in range(0,16):
|
||||
if self.slots[i].user is not None and self.slots[i].status == slotStatuses.playing:
|
||||
self.slots[i].status = slotStatuses.notReady
|
||||
if self.slots[i].user is not None and self.slots[i].status == slotStatuses.PLAYING:
|
||||
self.slots[i].status = slotStatuses.NOT_READY
|
||||
self.slots[i].loaded = False
|
||||
self.slots[i].skip = False
|
||||
self.slots[i].complete = False
|
||||
|
@ -332,7 +364,7 @@ class match:
|
|||
"""
|
||||
Get slot ID occupied by userID
|
||||
|
||||
return -- slot id if found, None if user is not in room
|
||||
:return: slot id if found, None if user is not in room
|
||||
"""
|
||||
for i in range(0,16):
|
||||
if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens and glob.tokens.tokens[self.slots[i].user].userID == userID:
|
||||
|
@ -343,21 +375,20 @@ class match:
|
|||
"""
|
||||
Add someone to users in match
|
||||
|
||||
userID -- user id of the user
|
||||
return -- True if join success, False if fail (room is full)
|
||||
:param userID: user id of the user
|
||||
:return: True if join success, False if fail (room is full)
|
||||
"""
|
||||
|
||||
# Make sure we're not in this match
|
||||
for i in range(0,16):
|
||||
if self.slots[i].user == user.token:
|
||||
# Set bugged slot to free
|
||||
self.setSlot(i, slotStatuses.free, 0, None, 0)
|
||||
self.setSlot(i, slotStatuses.FREE, 0, None, 0)
|
||||
|
||||
# Find first free slot
|
||||
for i in range(0,16):
|
||||
if self.slots[i].status == slotStatuses.free:
|
||||
if self.slots[i].status == slotStatuses.FREE:
|
||||
# Occupy slot
|
||||
self.setSlot(i, slotStatuses.notReady, 0, user.token, 0)
|
||||
self.setSlot(i, slotStatuses.NOT_READY, 0, user.token, 0)
|
||||
|
||||
# Send updated match data
|
||||
self.sendUpdates()
|
||||
|
@ -372,7 +403,8 @@ class match:
|
|||
"""
|
||||
Remove someone from users in match
|
||||
|
||||
userID -- user if of the user
|
||||
:param userID: user if of the user
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
slotID = self.getUserSlotID(user.userID)
|
||||
|
@ -380,7 +412,7 @@ class match:
|
|||
return
|
||||
|
||||
# Set that slot to free
|
||||
self.setSlot(slotID, slotStatuses.free, 0, None, 0)
|
||||
self.setSlot(slotID, slotStatuses.FREE, 0, None, 0)
|
||||
|
||||
# Check if everyone left
|
||||
if self.countUsers() == 0:
|
||||
|
@ -407,8 +439,9 @@ class match:
|
|||
"""
|
||||
Change userID slot to newSlotID
|
||||
|
||||
userID -- user that changed slot
|
||||
newSlotID -- slot id of new slot
|
||||
:param userID: user that changed slot
|
||||
:param newSlotID: slot id of new slot
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
oldSlotID = self.getUserSlotID(userID)
|
||||
|
@ -416,7 +449,7 @@ class match:
|
|||
return
|
||||
|
||||
# Make sure there is no one inside new slot
|
||||
if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.free:
|
||||
if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.FREE:
|
||||
return
|
||||
|
||||
# Get old slot data
|
||||
|
@ -424,7 +457,7 @@ class match:
|
|||
oldData = copy.deepcopy(self.slots[oldSlotID])
|
||||
|
||||
# Free old slot
|
||||
self.setSlot(oldSlotID, slotStatuses.free, 0, None, 0, False, False, False)
|
||||
self.setSlot(oldSlotID, slotStatuses.FREE, 0, None, 0, False, False, False)
|
||||
|
||||
# Occupy new slot
|
||||
self.setSlot(newSlotID, oldData.status, oldData.team, oldData.user, oldData.mods)
|
||||
|
@ -439,13 +472,10 @@ class match:
|
|||
"""
|
||||
Change match password to newPassword
|
||||
|
||||
newPassword -- new password string
|
||||
:param newPassword: new password string
|
||||
:return:
|
||||
"""
|
||||
self.matchPassword = newPassword
|
||||
#if newPassword != "":
|
||||
# self.matchPassword = generalUtils.stringMd5(newPassword)
|
||||
#else:
|
||||
# self.matchPassword = ""
|
||||
|
||||
# Send password change to every user in match
|
||||
glob.streams.broadcast(self.streamName, serverPackets.changeMatchPassword(self.matchPassword))
|
||||
|
@ -460,7 +490,8 @@ class match:
|
|||
"""
|
||||
Set match global mods
|
||||
|
||||
mods -- mods bitwise int thing
|
||||
:param mods: mods bitwise int thing
|
||||
:return:
|
||||
"""
|
||||
# Set new mods and send update
|
||||
self.mods = mods
|
||||
|
@ -471,8 +502,9 @@ class match:
|
|||
"""
|
||||
Set no beatmap status for userID
|
||||
|
||||
userID -- ID of user
|
||||
has -- True if has beatmap, false if not
|
||||
:param userID: ID of user
|
||||
:param has: True if has beatmap, false if not
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
slotID = self.getUserSlotID(userID)
|
||||
|
@ -480,7 +512,7 @@ class match:
|
|||
return
|
||||
|
||||
# Set slot
|
||||
self.setSlot(slotID, slotStatuses.noMap if not has else slotStatuses.notReady)
|
||||
self.setSlot(slotID, slotStatuses.NO_MAP if not has else slotStatuses.NOT_READY)
|
||||
|
||||
# Send updates
|
||||
self.sendUpdates()
|
||||
|
@ -489,7 +521,8 @@ class match:
|
|||
"""
|
||||
Transfer host to slotID
|
||||
|
||||
slotID -- ID of slot
|
||||
:param slotID: ID of slot
|
||||
:return:
|
||||
"""
|
||||
# Make sure there is someone in that slot
|
||||
if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens:
|
||||
|
@ -505,7 +538,8 @@ class match:
|
|||
"""
|
||||
Send userID's failed packet to everyone in match
|
||||
|
||||
userID -- ID of user
|
||||
:param userID: ID of user
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
slotID = self.getUserSlotID(userID)
|
||||
|
@ -522,10 +556,10 @@ class match:
|
|||
"""
|
||||
Fro invites to in this match.
|
||||
|
||||
fro -- sender userID
|
||||
to -- receiver userID
|
||||
:param fro: sender userID
|
||||
:param to: receiver userID
|
||||
:return:
|
||||
"""
|
||||
|
||||
# Get tokens
|
||||
froToken = glob.tokens.getTokenFromUserID(fro)
|
||||
toToken = glob.tokens.getTokenFromUserID(to)
|
||||
|
@ -544,7 +578,7 @@ class match:
|
|||
"""
|
||||
Return how many players are in that match
|
||||
|
||||
return -- number of users
|
||||
:return: number of users
|
||||
"""
|
||||
c = 0
|
||||
for i in range(0,16):
|
||||
|
@ -556,7 +590,8 @@ class match:
|
|||
"""
|
||||
Change userID's team
|
||||
|
||||
userID -- id of user
|
||||
:param userID: id of user
|
||||
:return:
|
||||
"""
|
||||
# Make sure the user is in room
|
||||
slotID = self.getUserSlotID(userID)
|
||||
|
@ -564,11 +599,16 @@ class match:
|
|||
return
|
||||
|
||||
# Update slot and send update
|
||||
newTeam = matchTeams.blue if self.slots[slotID].team == matchTeams.red else matchTeams.red
|
||||
newTeam = matchTeams.BLUE if self.slots[slotID].team == matchTeams.RED else matchTeams.RED
|
||||
self.setSlot(slotID, None, newTeam)
|
||||
self.sendUpdates()
|
||||
|
||||
def sendUpdates(self):
|
||||
"""
|
||||
Send match updates packet to everyone in lobby and room streams
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.matchDataCache = serverPackets.updateMatch(self.matchID)
|
||||
if self.matchDataCache is not None:
|
||||
glob.streams.broadcast(self.streamName, self.matchDataCache)
|
||||
|
@ -580,16 +620,17 @@ class match:
|
|||
"""
|
||||
Check if match teams are valid
|
||||
|
||||
return -- True if valid, False if invalid
|
||||
:return: True if valid, False if invalid
|
||||
:return:
|
||||
"""
|
||||
if self.matchTeamType != matchTeamTypes.teamVs or self.matchTeamType != matchTeamTypes.tagTeamVs:
|
||||
if self.matchTeamType != matchTeamTypes.TEAM_VS or self.matchTeamType != matchTeamTypes.TAG_TEAM_VS:
|
||||
# Teams are always valid if we have no teams
|
||||
return True
|
||||
|
||||
# We have teams, check if they are valid
|
||||
firstTeam = -1
|
||||
for i in range(0,16):
|
||||
if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.noMap) == 0:
|
||||
if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.NO_MAP) == 0:
|
||||
if firstTeam == -1:
|
||||
firstTeam = self.slots[i].team
|
||||
elif firstTeam != self.slots[i].team:
|
||||
|
@ -600,6 +641,11 @@ class match:
|
|||
return False
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start the match
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Make sure we have enough players
|
||||
if self.countUsers() < 2 or not self.checkTeams():
|
||||
return
|
||||
|
@ -613,8 +659,8 @@ class match:
|
|||
# Set playing to ready players and set load, skip and complete to False
|
||||
# Make clients join playing stream
|
||||
for i in range(0, 16):
|
||||
if (self.slots[i].status & slotStatuses.ready) > 0 and self.slots[i].user in glob.tokens.tokens:
|
||||
self.slots[i].status = slotStatuses.playing
|
||||
if (self.slots[i].status & slotStatuses.READY) > 0 and self.slots[i].user in glob.tokens.tokens:
|
||||
self.slots[i].status = slotStatuses.PLAYING
|
||||
self.slots[i].loaded = False
|
||||
self.slots[i].skip = False
|
||||
self.slots[i].complete = False
|
||||
|
|
|
@ -13,14 +13,14 @@ class matchList:
|
|||
"""
|
||||
Add a new match to matches list
|
||||
|
||||
matchName -- match name, string
|
||||
matchPassword -- match md5 password. Leave empty for no password
|
||||
beatmapID -- beatmap ID
|
||||
beatmapName -- beatmap name, string
|
||||
beatmapMD5 -- beatmap md5 hash, string
|
||||
gameMode -- game mode ID. See gameModes.py
|
||||
hostUserID -- user id of who created the match
|
||||
return -- match ID
|
||||
:param matchName: match name, string
|
||||
:param matchPassword: match md5 password. Leave empty for no password
|
||||
:param beatmapID: beatmap ID
|
||||
:param beatmapName: beatmap name, string
|
||||
:param beatmapMD5: beatmap md5 hash, string
|
||||
:param gameMode: game mode ID. See gameModes.py
|
||||
:param hostUserID: user id of who created the match
|
||||
:return: match ID
|
||||
"""
|
||||
# Add a new match to matches list and create its stream
|
||||
matchID = self.lastID
|
||||
|
@ -32,7 +32,8 @@ class matchList:
|
|||
"""
|
||||
Destroy match object with id = matchID
|
||||
|
||||
matchID -- ID of match to dispose
|
||||
:param matchID: ID of match to dispose
|
||||
:return:
|
||||
"""
|
||||
# Make sure the match exists
|
||||
if matchID not in self.matches:
|
||||
|
|
|
@ -12,17 +12,17 @@ from objects import glob
|
|||
|
||||
|
||||
class token:
|
||||
|
||||
def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0, tournament = False):
|
||||
"""
|
||||
Create a token object and set userID and token
|
||||
|
||||
userID -- user associated to this token
|
||||
token -- if passed, set token to that value
|
||||
if not passed, token will be generated
|
||||
ip -- client ip. optional.
|
||||
irc -- if True, set this token as IRC client. optional.
|
||||
timeOffset -- the time offset from UTC for this user. optional.
|
||||
:param userID: user associated to this token
|
||||
:param token_: if passed, set token to that value
|
||||
if not passed, token will be generated
|
||||
:param ip: client ip. optional.
|
||||
:param irc: if True, set this token as IRC client. Default: False.
|
||||
:param timeOffset: the time offset from UTC for this user. Default: 0.
|
||||
:param tournament: if True, flag this client as a tournement client. Default: True.
|
||||
"""
|
||||
# Set stuff
|
||||
self.userID = userID
|
||||
|
@ -95,25 +95,23 @@ class token:
|
|||
"""
|
||||
Add bytes (packets) to queue
|
||||
|
||||
bytes -- (packet) bytes to enqueue
|
||||
:param bytes: (packet) bytes to enqueue
|
||||
"""
|
||||
if not self.irc:
|
||||
if len(bytes_) < 10 * 10 ** 6:
|
||||
self.queue += bytes_
|
||||
else:
|
||||
log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username))
|
||||
|
||||
# TODO: reduce max queue size
|
||||
if len(bytes_) < 10 * 10 ** 6:
|
||||
self.queue += bytes_
|
||||
else:
|
||||
log.warning("{}'s packets buffer is above 10M!! Lost some data!".format(self.username))
|
||||
|
||||
def resetQueue(self):
|
||||
"""Resets the queue. Call when enqueued packets have been sent"""
|
||||
self.queue = bytes()
|
||||
|
||||
|
||||
def joinChannel(self, channel):
|
||||
"""
|
||||
Add channel to joined channels list
|
||||
|
||||
channel -- channel name
|
||||
:param channel: channel name
|
||||
"""
|
||||
if channel not in self.joinedChannels:
|
||||
self.joinedChannels.append(channel)
|
||||
|
@ -122,24 +120,24 @@ class token:
|
|||
"""
|
||||
Remove channel from joined channels list
|
||||
|
||||
channel -- channel name
|
||||
:param channel: channel name
|
||||
"""
|
||||
if channel in self.joinedChannels:
|
||||
self.joinedChannels.remove(channel)
|
||||
|
||||
def setLocation(self, location):
|
||||
def setLocation(self, latitude, longitude):
|
||||
"""
|
||||
Set location (latitude and longitude)
|
||||
Set client location
|
||||
|
||||
location -- [latitude, longitude]
|
||||
:param location: [latitude, longitude]
|
||||
"""
|
||||
self.location = location
|
||||
self.location = (latitude, longitude)
|
||||
|
||||
def getLatitude(self):
|
||||
"""
|
||||
Get latitude
|
||||
|
||||
return -- latitude
|
||||
:return: latitude
|
||||
"""
|
||||
return self.location[0]
|
||||
|
||||
|
@ -147,15 +145,16 @@ class token:
|
|||
"""
|
||||
Get longitude
|
||||
|
||||
return -- longitude
|
||||
:return: longitude
|
||||
"""
|
||||
return self.location[1]
|
||||
|
||||
def startSpectating(self, host):
|
||||
"""
|
||||
Set the spectating user to userID
|
||||
Set the spectating user to userID, join spectator stream and chat channel
|
||||
and send required packets to host
|
||||
|
||||
user -- user object
|
||||
:param host: host osuToken object
|
||||
"""
|
||||
# Stop spectating old client
|
||||
self.stopSpectating()
|
||||
|
@ -195,6 +194,12 @@ class token:
|
|||
log.info("{} is spectating {}".format(self.username, host.username))
|
||||
|
||||
def stopSpectating(self):
|
||||
"""
|
||||
Stop spectating, leave spectator stream and channel
|
||||
and send required packets to host
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Remove our userID from host's spectators
|
||||
if self.spectating is None:
|
||||
return
|
||||
|
@ -233,36 +238,20 @@ class token:
|
|||
self.spectating = None
|
||||
self.spectatingUserID = 0
|
||||
|
||||
def setCountry(self, countryID):
|
||||
"""
|
||||
Set country to countryID
|
||||
|
||||
countryID -- numeric country ID. See countryHelper.py
|
||||
"""
|
||||
self.country = countryID
|
||||
|
||||
def getCountry(self):
|
||||
"""
|
||||
Get numeric country ID
|
||||
|
||||
return -- numeric country ID. See countryHelper.py
|
||||
"""
|
||||
return self.country
|
||||
|
||||
def updatePingTime(self):
|
||||
"""Update latest ping time"""
|
||||
"""
|
||||
Update latest ping time to current time
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.pingTime = int(time.time())
|
||||
|
||||
def setAwayMessage(self, __awayMessage):
|
||||
"""Set a new away message"""
|
||||
self.awayMessage = __awayMessage
|
||||
|
||||
|
||||
def joinMatch(self, matchID):
|
||||
"""
|
||||
Set match to matchID, join match stream and channel
|
||||
|
||||
matchID -- new match ID
|
||||
:param matchID: new match ID
|
||||
:return:
|
||||
"""
|
||||
# Make sure the match exists
|
||||
if matchID not in glob.matches.matches:
|
||||
|
@ -322,7 +311,10 @@ class token:
|
|||
"""
|
||||
Kick this user from the server
|
||||
|
||||
message -- Notification message to send to this user. Optional.
|
||||
:param message: Notification message to send to this user.
|
||||
Default: "You have been kicked from the server. Please login again."
|
||||
:param reason: Kick reason, used in logs. Default: "kick"
|
||||
:return:
|
||||
"""
|
||||
# Send packet to target
|
||||
log.info("{} has been disconnected. ({})".format(self.username, reason))
|
||||
|
@ -337,9 +329,10 @@ class token:
|
|||
"""
|
||||
Silences this user (db, packet and token)
|
||||
|
||||
seconds -- silence length in seconds
|
||||
reason -- silence reason
|
||||
author -- userID of who has silenced the target. Optional. Default: 999 (fokabot)
|
||||
:param seconds: silence length in seconds
|
||||
:param reason: silence reason
|
||||
:param author: userID of who has silenced the user. Default: 999 (FokaBot)
|
||||
:return:
|
||||
"""
|
||||
# Silence in db and token
|
||||
self.silenceEndTime = int(time.time())+seconds
|
||||
|
@ -355,7 +348,8 @@ class token:
|
|||
"""
|
||||
Silences the user if is spamming.
|
||||
|
||||
increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True
|
||||
:param increaseSpamRate: set to True if the user has sent a new message. Default: True
|
||||
:return:
|
||||
"""
|
||||
# Increase the spam rate if needed
|
||||
if increaseSpamRate:
|
||||
|
@ -369,7 +363,7 @@ class token:
|
|||
"""
|
||||
Returns True if this user is silenced, otherwise False
|
||||
|
||||
return -- True/False
|
||||
:return: True if this user is silenced, otherwise False
|
||||
"""
|
||||
return self.silenceEndTime-int(time.time()) > 0
|
||||
|
||||
|
@ -378,12 +372,16 @@ class token:
|
|||
Returns the seconds left for this user's silence
|
||||
(0 if user is not silenced)
|
||||
|
||||
return -- silence seconds left
|
||||
:return: silence seconds left (or 0)
|
||||
"""
|
||||
return max(0, self.silenceEndTime-int(time.time()))
|
||||
|
||||
def updateCachedStats(self):
|
||||
"""Update all cached stats for this token"""
|
||||
"""
|
||||
Update all cached stats for this token
|
||||
|
||||
:return:
|
||||
"""
|
||||
stats = userUtils.getUserStats(self.userID, self.gameMode)
|
||||
log.debug(str(stats))
|
||||
if stats is None:
|
||||
|
@ -400,8 +398,9 @@ class token:
|
|||
"""
|
||||
Check if this token is restricted. If so, send fokabot message
|
||||
|
||||
force -- If True, get restricted value from db.
|
||||
If false, get the cached one. Optional. Default: False
|
||||
:param force: If True, get restricted value from db.
|
||||
If False, get the cached one. Default: False
|
||||
:return:
|
||||
"""
|
||||
if force:
|
||||
self.restricted = userUtils.isRestricted(self.userID)
|
||||
|
@ -412,21 +411,40 @@ class token:
|
|||
"""
|
||||
Set this token as restricted, send FokaBot message to user
|
||||
and send offline packet to everyone
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.restricted = True
|
||||
chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
|
||||
|
||||
def joinStream(self, name):
|
||||
"""
|
||||
Join a packet stream, or create it if the stream doesn't exist.
|
||||
|
||||
:param name: stream name
|
||||
:return:
|
||||
"""
|
||||
glob.streams.join(name, token=self.token)
|
||||
if name not in self.streams:
|
||||
self.streams.append(name)
|
||||
|
||||
def leaveStream(self, name):
|
||||
"""
|
||||
Leave a packets stream
|
||||
|
||||
:param name: stream name
|
||||
:return:
|
||||
"""
|
||||
glob.streams.leave(name, token=self.token)
|
||||
if name in self.streams:
|
||||
self.streams.remove(name)
|
||||
|
||||
def leaveAllStreams(self):
|
||||
"""
|
||||
Leave all joined packet streams
|
||||
|
||||
:return:
|
||||
"""
|
||||
for i in self.streams:
|
||||
self.leaveStream(i)
|
||||
|
||||
|
|
|
@ -8,27 +8,19 @@ from events import logoutEvent
|
|||
from objects import glob
|
||||
from objects import osuToken
|
||||
|
||||
|
||||
class tokenList:
|
||||
"""
|
||||
List of connected osu tokens
|
||||
|
||||
tokens -- dictionary. key: token string, value: token object
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize a tokens list
|
||||
"""
|
||||
self.tokens = {}
|
||||
|
||||
def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False):
|
||||
"""
|
||||
Add a token object to tokens list
|
||||
|
||||
userID -- user id associated to that token
|
||||
irc -- if True, set this token as IRC client
|
||||
return -- token object
|
||||
:param userID: user id associated to that token
|
||||
:param irc: if True, set this token as IRC client
|
||||
:param timeOffset: the time offset from UTC for this user. Default: 0.
|
||||
:param tournament: if True, flag this client as a tournement client. Default: True.
|
||||
:return: token object
|
||||
"""
|
||||
newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset, tournament=tournament)
|
||||
self.tokens[newToken.token] = newToken
|
||||
|
@ -38,7 +30,8 @@ class tokenList:
|
|||
"""
|
||||
Delete a token from token list if it exists
|
||||
|
||||
token -- token string
|
||||
:param token: token string
|
||||
:return:
|
||||
"""
|
||||
if token in self.tokens:
|
||||
# Delete session from DB
|
||||
|
@ -52,8 +45,8 @@ class tokenList:
|
|||
"""
|
||||
Get user ID from a token
|
||||
|
||||
token -- token to find
|
||||
return -- false if not found, userID if found
|
||||
:param token: token to find
|
||||
:return: False if not found, userID if found
|
||||
"""
|
||||
# Make sure the token exists
|
||||
if token not in self.tokens:
|
||||
|
@ -66,8 +59,9 @@ class tokenList:
|
|||
"""
|
||||
Get token from a user ID
|
||||
|
||||
userID -- user ID to find
|
||||
return -- False if not found, token object if found
|
||||
:param userID: user ID to find
|
||||
:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
|
||||
:return: False if not found, token object if found
|
||||
"""
|
||||
# Make sure the token exists
|
||||
for _, value in self.tokens.items():
|
||||
|
@ -85,8 +79,8 @@ class tokenList:
|
|||
|
||||
:param username: normal username or safe username
|
||||
:param ignoreIRC: if True, consider bancho clients only and skip IRC clients
|
||||
:param safe: if True, username is a safe username,
|
||||
compare it with token's safe username rather than normal username
|
||||
:param safe: if True, username is a safe username,
|
||||
compare it with token's safe username rather than normal username
|
||||
:return: osuToken object or None
|
||||
"""
|
||||
# lowercase
|
||||
|
@ -106,7 +100,8 @@ class tokenList:
|
|||
"""
|
||||
Delete old userID's tokens if found
|
||||
|
||||
userID -- tokens associated to this user will be deleted
|
||||
:param userID: tokens associated to this user will be deleted
|
||||
:return:
|
||||
"""
|
||||
# Delete older tokens
|
||||
for key, value in list(self.tokens.items()):
|
||||
|
@ -118,9 +113,10 @@ class tokenList:
|
|||
"""
|
||||
Enqueue a packet to multiple users
|
||||
|
||||
packet -- packet bytes to enqueue
|
||||
who -- userIDs array
|
||||
but -- if True, enqueue to everyone but users in who array
|
||||
:param packet: packet bytes to enqueue
|
||||
:param who: userIDs array
|
||||
:param but: if True, enqueue to everyone but users in `who` array
|
||||
:return:
|
||||
"""
|
||||
for _, value in self.tokens.items():
|
||||
shouldEnqueue = False
|
||||
|
@ -136,19 +132,21 @@ class tokenList:
|
|||
"""
|
||||
Enqueue packet(s) to every connected user
|
||||
|
||||
packet -- packet bytes to enqueue
|
||||
:param packet: packet bytes to enqueue
|
||||
:return:
|
||||
"""
|
||||
for _, value in self.tokens.items():
|
||||
value.enqueue(packet)
|
||||
|
||||
def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100):
|
||||
"""
|
||||
Deletes all timed out users.
|
||||
If called once, will recall after checkTime seconds and so on, forever
|
||||
Start timed out users disconnect loop.
|
||||
This function will be called every `checkTime` seconds and so on, forever.
|
||||
CALL THIS FUNCTION ONLY ONCE!
|
||||
|
||||
timeoutTime - seconds of inactivity required to disconnect someone (Default: 100)
|
||||
checkTime - seconds between loops (Default: 100)
|
||||
:param timeoutTime: seconds of inactivity required to disconnect someone. Default: 100
|
||||
:param checkTime: seconds between loops. Default: 100
|
||||
:return:
|
||||
"""
|
||||
log.debug("Checking timed out clients")
|
||||
timedOutTokens = [] # timed out users
|
||||
|
@ -172,8 +170,11 @@ class tokenList:
|
|||
|
||||
def spamProtectionResetLoop(self):
|
||||
"""
|
||||
Reset spam rate every 10 seconds.
|
||||
Start spam protection reset loop.
|
||||
Called every 10 seconds.
|
||||
CALL THIS FUNCTION ONLY ONCE!
|
||||
|
||||
:return:
|
||||
"""
|
||||
# Reset spamRate for every token
|
||||
for _, value in self.tokens.items():
|
||||
|
@ -186,20 +187,22 @@ class tokenList:
|
|||
"""
|
||||
Truncate bancho_sessions table.
|
||||
Call at bancho startup to delete old cached sessions
|
||||
|
||||
:return:
|
||||
"""
|
||||
glob.db.execute("TRUNCATE TABLE bancho_sessions")
|
||||
|
||||
|
||||
def tokenExists(self, username = "", userID = -1):
|
||||
"""
|
||||
Check if a token exists (aka check if someone is connected)
|
||||
|
||||
username -- Optional.
|
||||
userID -- Optional.
|
||||
return -- True if it exists, otherwise False
|
||||
|
||||
Check if a token exists
|
||||
Use username or userid, not both at the same time.
|
||||
|
||||
:param username: Optional.
|
||||
:param userID: Optional.
|
||||
:return: True if it exists, otherwise False
|
||||
"""
|
||||
if userID > -1:
|
||||
return True if self.getTokenFromUserID(userID) is not None else False
|
||||
else:
|
||||
return True if self.getTokenFromUsername(username) is not None else False
|
||||
return True if self.getTokenFromUsername(username) is not None else False
|
Loading…
Reference in New Issue
Block a user