82 Commits

Author SHA1 Message Date
Nyo
a48aa73a0b .BANCHO. Add code for !update FokaBot command 2016-10-15 23:21:17 +02:00
Nyo
90dfb2c705 .BANCHO. .FIX. Don't time out tourney clients 2016-10-09 19:12:18 +02:00
Nyo
e7b7dc932a .BANCHO. Add max packets buffer size 2016-10-08 22:37:10 +02:00
Nyo
4ed5e0d14a .BANCHO. .HIDE. Update submodules 2016-10-08 22:25:27 +02:00
Nyo
0750ead5db .BANCHO. .HIDE. Removed dill from requirements list 2016-10-08 20:49:04 +02:00
Nyo
74f3ec0f4c .HIDE. Hey git pls. See last commit message 2016-10-08 20:47:19 +02:00
Nyo
fdec8620fb .BANCHO. .FIX. Match password gets set correctly on match creation 2016-10-08 20:45:53 +02:00
Nyo
8ff493f706 .BANCHO. .FIX. Make sure match data is cached before sending it 2016-10-08 20:35:09 +02:00
Nyo
2d26d36fbf .BANCHO. .FIX. Return bytes rather than None in match packets if match doesn't exist 2016-10-08 20:34:17 +02:00
Nyo
7c54df6586 .BANCHO. .FIX. Match passwords are in plain text again, fixed match invites for password protected matches 2016-10-08 20:24:16 +02:00
Nyo
2eb7fb609c .BANCHO. Cache match data for tourney client 2016-10-08 20:18:33 +02:00
Nyo
b1399aaf6e .BANCHO. Update submodules 2016-10-08 20:06:45 +02:00
Nyo
26dbdd72c1 .BANCHO. .FIX. Attempt to make getMatchData thread safe 2016-10-07 14:38:44 +02:00
Nyo
99d14b46e8 .BANCHO. .FIX. Fix typo in variable name 2016-10-07 14:14:09 +02:00
Nyo
927621cfcd .BANCHO. .FIX. Fix spectator chat 2016-10-07 14:13:42 +02:00
Nyo
847a955e2f .BANCHO. .HIDE. Meh 2016-10-07 13:44:00 +02:00
Nyo
99f76e3227 .BANCHO. .FIX. Fix None token in cantSpectateEvent 2016-10-07 13:43:47 +02:00
Nyo
ea83d3c86d .BANCHO. Use tokens in multiplayer rather than references to clients objects 2016-10-07 13:15:50 +02:00
Nyo
10e182ce55 .BANCHO. Use tokens while spectating rather than references to clients objects 2016-10-07 12:42:02 +02:00
Nyo
ca34583e7b .BANCHO. .HIDE. Update version 2016-10-07 11:06:28 +02:00
Nyo
7a7d64c209 .BANCHO. .FIX. Fix startup schiavo messages 2016-10-06 23:13:37 +02:00
Nyo
569230c16a .BANCHO. Update submodules 2016-10-06 23:07:32 +02:00
Nyo
f155b0567c .BANCHO. Add Datadog stats report 2016-10-06 23:06:59 +02:00
Nyo
05f09ad35b .BANCHO. Update submodules 2016-10-06 18:09:03 +02:00
Nyo
ead44270af .BANCHO. Update submodules 2016-10-06 17:42:26 +02:00
Nyo
820df034f4 .BANCHO. .FIX. Fix typo 2016-10-06 17:41:40 +02:00
Nyo
7347b1d455 .BANCHO. Add UserTournamentStaff privilege 2016-10-06 17:41:13 +02:00
Nyo
4babcd2643 .BANCHO. Update README and requirements 2016-10-05 23:34:42 +02:00
Nyo
142c3d35cb .BANCHO. .FIX. Fixed a bug when changing slot in multiplayer 2016-10-05 23:28:53 +02:00
Nyo
996287f871 .BANCHO. Add support for tournament client 2016-10-05 23:28:26 +02:00
Nyo
795b6f09be .BANCHO. Implemented packet streams for multiplayer 2016-10-04 23:43:02 +02:00
Nyo
afbd8e7e8c .BANCHO. Implemented packet streams for spectator and lobby 2016-10-04 22:10:07 +02:00
Nyo
cd3054c83c .HIDE. Update submodules 2016-10-02 23:23:01 +02:00
Nyo
7fcc9bca5e .BANCHO. .FIX. Fixed some missing references, code cleaning 2016-10-02 23:11:18 +02:00
Nyo
8173ff3bb6 .HIDE. Update README 2016-10-02 22:56:41 +02:00
Nyo
60e629a4fe .HIDE. Update submodules 2016-10-02 22:54:48 +02:00
Nyo
88c80a4080 Add submodules 2016-10-02 22:48:14 +02:00
Nyo
40264ceffe .BANCHO. Add streams and streamList object, add 'main' stream 2016-10-01 21:19:03 +02:00
Nyo
2213a99147 .BANCHO. .FIX. Fix negative pp value causing exception 2016-09-27 21:08:52 +02:00
Nyo
121d19aaa7 .BANCHO. Add notification when donor tag is expiring soon 2016-09-25 19:05:31 +02:00
Nyo
4b6e621a70 .BANCHO. .FIX. Fix recursive return statement 2016-09-23 17:17:30 +02:00
Nyo
712aecb602 .BANCHO. .HIDE. Update startup messages 2016-09-23 17:09:09 +02:00
Nyo
5bde0164fb .BANCHO. New MySQL helper 2016-09-23 17:08:57 +02:00
Nyo
f912f6ea82 .BANCHO. Add check for MySQL connections pool saturation 2016-09-16 18:21:34 +02:00
Nyo
6020f7cc47 .BANCHO. .FIX. Better support for username with spaces and underscores in IRC 2016-09-16 18:20:32 +02:00
Nyo
47ce794bfc .BANCHO. Add !pp command 2016-09-13 12:25:59 +02:00
Nyo
89af599e1a .BANCHO. .FIX. Fix PP overflow 2016-09-13 12:25:45 +02:00
Nyo
784973b298 Merge remote-tracking branch 'origin/master' 2016-09-13 12:03:10 +02:00
Nyo
8e4c44b857 .BANCHO. Add automatic beatmap download when spectating someone 2016-09-13 11:56:53 +02:00
Nyo
ba774bf567 .BANCHO. .HIDE. Remove debug line 2016-09-13 11:49:28 +02:00
Nyo
ea7cd9ff30 .BANCHO. .FIX. Filters are now case sensitive 2016-09-13 11:47:39 +02:00
Nyo
14348a68bb .BANCHO. Add support for locked accounts 2016-09-13 11:39:39 +02:00
Howl
fd9cde1ec6 remove (dead) getShowCountry code 2016-09-05 00:50:29 +02:00
Nyo
48925c58c0 .BANCHO. .FIX. Fix leave match right after joining 2016-09-04 17:59:12 +02:00
Nyo
17a8636de7 .BANCHO. .FIX. Fix some issues with spaced usernames on IRC 2016-09-04 17:53:12 +02:00
Nyo
f5a34d9048 .BANCHO. .FIX. Fix multiplayer free mods 2016-09-04 17:26:58 +02:00
Nyo
6aa7eb6b69 .BANCHO. .FIX. Why... 2016-09-04 16:39:59 +02:00
Nyo
19c4c32c7b .BANCHO. .FIX. Fix actions contants names 2016-09-04 16:29:36 +02:00
Nyo
7fc6d4c540 .BANCHO. Force stop spectating/quit match if user is not spectating or multiplaying 2016-09-04 16:24:00 +02:00
Nyo
255a0958ff .BANCHO. Revert ".BANCHO. Switched to tornado+gevent" 2016-09-04 16:07:10 +02:00
Nyo
31a78432bb .BANCHO. .FIX. Stop spectating only if user is spectating someone 2016-09-04 12:42:14 +02:00
Nyo
d5c87ba51c .BANCHO. Configurable IRC hostname 2016-09-04 12:36:21 +02:00
Nyo
2d91231320 .BANCHO. Add file buffers 2016-09-04 12:10:00 +02:00
Nyo
9ee2e5d7ff .BANCHO. Add userID cache 2016-09-04 12:09:53 +02:00
Nyo
cf60c167b6 .BANCHO. .FIX. Code cleaning 2016-09-02 17:45:10 +02:00
Nyo
3703618112 .BANCHO. Some code refactoring 2016-09-02 17:16:22 +02:00
Nyo
653303831b IRC Support for username with spaces
BATs with Donor have bright yellow username in chat
General performance improvements
Code cleaning
Multiplayer improvements and fixes
Fixed some spectator bugs
2016-09-02 12:41:19 +02:00
Nyo
e16e4d7493 Merge branch 'master' of git.zxq.co:ripple/pep.py 2016-08-28 17:26:02 +02:00
Nyo
a1d45a4419 .BANCHO. Add ir command, more info in system status 2016-08-28 17:25:47 +02:00
Isaac Drew
1ac48c51b5 .FIX. .BANCHO. Add scaled AR to /np with fokabot 2016-08-28 21:21:03 +08:00
Isaac Drew
27ac94a736 .FIX. .BANCHO. Add mod detection for /np when watching and playing 2016-08-28 21:20:41 +08:00
Nyo
771b6e294b .BANCHO. .HIDE. Add requirements.txt, update README 2016-08-28 11:53:35 +02:00
russelg
bd395ef30b .FIX. Actually fix meme 2016-08-24 11:10:29 +08:00
russelg
78dbd7e1f5 .FIX. Fix memes 2016-08-24 10:59:24 +08:00
Nyo
649c400883 .HIDE. version thing is bugged 2016-08-23 20:41:13 +02:00
Nyo
65b55fdcf1 .HIDE. Fix version 2016-08-23 20:39:56 +02:00
Nyo
de47c46d25 .BANCHO. Merge branch tornado-gevent into master 2016-08-23 20:35:47 +02:00
Nyo
1a31042619 .BANCHO. .FIX. Hopefully fixed headers issues with gevent 2016-08-22 22:28:18 +02:00
Nyo
7c906e48c5 .BANCHO. .FIX. Fix api/v1/onlineUsers handler 2016-08-22 15:54:01 +02:00
Nyo
2f8f8e1beb .HIDE. Update README 2016-08-22 15:31:10 +02:00
Nyo
8f79e58185 .BANCHO. /api/v1/isOnline now supports both userid and username 2016-08-21 20:32:17 +02:00
Nyo
7910291b77 .BANCHO. Switched from tornado to bottle + gevent, code cleaning 2016-08-17 16:41:05 +02:00
92 changed files with 2044 additions and 3231 deletions

4
.gitignore vendored
View File

@@ -2,3 +2,7 @@
config.ini config.ini
filters.txt filters.txt
.data .data
.idea
common_funzia
common_refractor
common_memato

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "common"]
path = common
url = git@git.zxq.co:ripple/ripple-python-common.git

View File

@@ -9,15 +9,19 @@ This is Ripple's bancho server. It handles:
## Requirements ## Requirements
- Python 3.5 - Python 3.5
- MySQLdb (`mysqlclient` or `mysql-python`) - MySQLdb (`mysqlclient`)
- Tornado - Tornado
- Gevent
- Bcrypt - Bcrypt
- Raven
## How to set up pep.py ## How to set up pep.py
First of all, install all the dependencies First of all, initialize and update the submodules
``` ```
$ pip install mysqlclient tornado gevent bcrypt $ git submodule init && git submodule update
```
afterwards, install the required dependencies with pip
```
$ pip install -r requirements.txt
``` ```
then, run pep.py once to create the default config file and edit it then, run pep.py once to create the default config file and edit it
``` ```

1
common Submodule

Submodule common added at a899c0be89

View File

@@ -1,17 +0,0 @@
"""Contains user actions"""
#TODO: Uppercase
idle = 0
afk = 1
playing = 2
editing = 3
modding = 4
multiplayer = 5
watching = 6
unknown = 7
testing = 8
submitting = 9
paused = 10
lobby = 11
multiplaying= 12
osuDirect = 13
none = 14

View File

@@ -1,9 +0,0 @@
"""Console colors"""
PINK = '\033[95m'
BLUE = '\033[94m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
RED = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'

View File

@@ -8,86 +8,86 @@ from constants import slotStatuses
def userActionChange(stream): def userActionChange(stream):
return packetHelper.readPacketData(stream, return packetHelper.readPacketData(stream,
[ [
["actionID", dataTypes.byte], ["actionID", dataTypes.BYTE],
["actionText", dataTypes.string], ["actionText", dataTypes.STRING],
["actionMd5", dataTypes.string], ["actionMd5", dataTypes.STRING],
["actionMods", dataTypes.uInt32], ["actionMods", dataTypes.UINT32],
["gameMode", dataTypes.byte] ["gameMode", dataTypes.BYTE],
["beatmapID", dataTypes.SINT32]
]) ])
def userStatsRequest(stream): def userStatsRequest(stream):
return packetHelper.readPacketData(stream, [["users", dataTypes.intList]]) return packetHelper.readPacketData(stream, [["users", dataTypes.INT_LIST]])
def userPanelRequest(stream): def userPanelRequest(stream):
return packetHelper.readPacketData(stream, [["users", dataTypes.intList]]) return packetHelper.readPacketData(stream, [["users", dataTypes.INT_LIST]])
""" Client chat packets """ """ Client chat packets """
def sendPublicMessage(stream): def sendPublicMessage(stream):
return packetHelper.readPacketData(stream, return packetHelper.readPacketData(stream,
[ [
["unknown", dataTypes.string], ["unknown", dataTypes.STRING],
["message", dataTypes.string], ["message", dataTypes.STRING],
["to", dataTypes.string] ["to", dataTypes.STRING]
]) ])
def sendPrivateMessage(stream): def sendPrivateMessage(stream):
return packetHelper.readPacketData(stream, return packetHelper.readPacketData(stream,
[ [
["unknown", dataTypes.string], ["unknown", dataTypes.STRING],
["message", dataTypes.string], ["message", dataTypes.STRING],
["to", dataTypes.string], ["to", dataTypes.STRING],
["unknown2", dataTypes.uInt32] ["unknown2", dataTypes.UINT32]
]) ])
def setAwayMessage(stream): def setAwayMessage(stream):
return packetHelper.readPacketData(stream, return packetHelper.readPacketData(stream,
[ [
["unknown", dataTypes.string], ["unknown", dataTypes.STRING],
["awayMessage", dataTypes.string] ["awayMessage", dataTypes.STRING]
]) ])
def channelJoin(stream): def channelJoin(stream):
return packetHelper.readPacketData(stream,[["channel", dataTypes.string]]) return packetHelper.readPacketData(stream, [["channel", dataTypes.STRING]])
def channelPart(stream): def channelPart(stream):
return packetHelper.readPacketData(stream,[["channel", dataTypes.string]]) return packetHelper.readPacketData(stream, [["channel", dataTypes.STRING]])
def addRemoveFriend(stream): def addRemoveFriend(stream):
return packetHelper.readPacketData(stream, [["friendID", dataTypes.sInt32]]) return packetHelper.readPacketData(stream, [["friendID", dataTypes.SINT32]])
""" Spectator packets """
""" SPECTATOR PACKETS """
def startSpectating(stream): def startSpectating(stream):
return packetHelper.readPacketData(stream,[["userID", dataTypes.sInt32]]) return packetHelper.readPacketData(stream, [["userID", dataTypes.SINT32]])
""" MULTIPLAYER PACKETS """ """ Multiplayer packets """
def matchSettings(stream): def matchSettings(stream):
# Data to return, will be merged later # Data to return, will be merged later
data = [] data = []
# Some settings # Some settings
struct = [ struct = [
["matchID", dataTypes.uInt16], ["matchID", dataTypes.UINT16],
["inProgress", dataTypes.byte], ["inProgress", dataTypes.BYTE],
["unknown", dataTypes.byte], ["unknown", dataTypes.BYTE],
["mods", dataTypes.uInt32], ["mods", dataTypes.UINT32],
["matchName", dataTypes.string], ["matchName", dataTypes.STRING],
["matchPassword", dataTypes.string], ["matchPassword", dataTypes.STRING],
["beatmapName", dataTypes.string], ["beatmapName", dataTypes.STRING],
["beatmapID", dataTypes.uInt32], ["beatmapID", dataTypes.UINT32],
["beatmapMD5", dataTypes.string] ["beatmapMD5", dataTypes.STRING]
] ]
# Slot statuses (not used) # Slot statuses (not used)
for i in range(0,16): for i in range(0,16):
struct.append(["slot{}Status".format(str(i)), dataTypes.byte]) struct.append(["slot{}Status".format(str(i)), dataTypes.BYTE])
# Slot statuses (not used) # Slot statuses (not used)
for i in range(0,16): for i in range(0,16):
struct.append(["slot{}Team".format(str(i)), dataTypes.byte]) struct.append(["slot{}Team".format(str(i)), dataTypes.BYTE])
# Read first part # Read first part
data.append(packetHelper.readPacketData(stream, struct)) data.append(packetHelper.readPacketData(stream, struct))
@@ -105,19 +105,16 @@ def matchSettings(stream):
# Other settings # Other settings
struct = [ struct = [
["hostUserID", dataTypes.sInt32], ["hostUserID", dataTypes.SINT32],
["gameMode", dataTypes.byte], ["gameMode", dataTypes.BYTE],
["scoringType", dataTypes.byte], ["scoringType", dataTypes.BYTE],
["teamType", dataTypes.byte], ["teamType", dataTypes.BYTE],
["freeMods", dataTypes.byte], ["freeMods", dataTypes.BYTE],
] ]
# Read last part # Read last part
data.append(packetHelper.readPacketData(stream[start:], struct, False)) data.append(packetHelper.readPacketData(stream[start:], struct, False))
# Mods if freemod (not used)
#if data[1]["freeMods"] == 1:
result = {} result = {}
for i in data: for i in data:
result.update(i) result.update(i)
@@ -130,19 +127,28 @@ def changeMatchSettings(stream):
return matchSettings(stream) return matchSettings(stream)
def changeSlot(stream): def changeSlot(stream):
return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]]) return packetHelper.readPacketData(stream, [["slotID", dataTypes.UINT32]])
def joinMatch(stream): def joinMatch(stream):
return packetHelper.readPacketData(stream, [["matchID", dataTypes.uInt32], ["password", dataTypes.string]]) return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32], ["password", dataTypes.STRING]])
def changeMods(stream): def changeMods(stream):
return packetHelper.readPacketData(stream, [["mods", dataTypes.uInt32]]) return packetHelper.readPacketData(stream, [["mods", dataTypes.UINT32]])
def lockSlot(stream): def lockSlot(stream):
return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]]) return packetHelper.readPacketData(stream, [["slotID", dataTypes.UINT32]])
def transferHost(stream): def transferHost(stream):
return packetHelper.readPacketData(stream, [["slotID", dataTypes.uInt32]]) return packetHelper.readPacketData(stream, [["slotID", dataTypes.UINT32]])
def matchInvite(stream): def matchInvite(stream):
return packetHelper.readPacketData(stream, [["userID", dataTypes.uInt32]]) return packetHelper.readPacketData(stream, [["userID", dataTypes.UINT32]])
def tournamentMatchInfoRequest(stream):
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])
def tournamentJoinMatchChannel(stream):
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])
def tournamentLeaveMatchChannel(stream):
return packetHelper.readPacketData(stream, [["matchID", dataTypes.UINT32]])

View File

@@ -1,13 +1,12 @@
"""Bancho packets data types""" """Bancho packets data types"""
#TODO: Uppercase, maybe? BYTE = 0
byte = 0 UINT16 = 1
uInt16 = 1 SINT16 = 2
sInt16 = 2 UINT32 = 3
uInt32 = 3 SINT32 = 4
sInt32 = 4 UINT64 = 5
uInt64 = 5 SINT64 = 6
sInt64 = 6 STRING = 7
string = 7 FFLOAT = 8 # because float is a keyword
ffloat = 8 # because float is a keyword BBYTES = 9
bbytes = 9 INT_LIST = 10 # TODO: Maybe there are some packets that still use uInt16 + uInt32 thing somewhere.
intList = 10 # TODO: Maybe there are some packets that still use uInt16 + uInt32 thing somewhere.

View File

@@ -83,3 +83,12 @@ class haxException(Exception):
class forceUpdateException(Exception): class forceUpdateException(Exception):
pass pass
class loginLockedException(Exception):
pass
class unknownStreamException(Exception):
pass
class userTournamentException(Exception):
pass

View File

@@ -1,17 +1,19 @@
from objects import fokabot
import random
from objects import glob
from constants import serverPackets
from constants import exceptions
from helpers import userHelper
from helpers import systemHelper
import requests
import json import json
from constants import mods import random
from helpers import generalFunctions
from helpers import logHelper as log import requests
from constants import gameModes
from constants import privileges from common import generalUtils
from common.constants import mods
from common.log import logUtils as log
from common.ripple import userUtils
from constants import exceptions
from common.constants import gameModes
from common.constants import privileges
from constants import serverPackets
from helpers import systemHelper
from objects import fokabot
from objects import glob
""" """
Commands callbacks Commands callbacks
@@ -25,7 +27,12 @@ message -- list containing arguments passed from the message
. . . . . .
return the message or **False** if there's no response by the bot 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
""" """
def instantRestart(fro, chan, message):
glob.streams.broadcast("main", serverPackets.notification("We are restarting Bancho. Be right back!"))
systemHelper.scheduleShutdown(0, True, delay=1)
return False
def faq(fro, chan, message): def faq(fro, chan, message):
if message[0] == "rules": if message[0] == "rules":
@@ -64,14 +71,14 @@ def roll(fro, chan, message):
# return random.choice(["yes", "no", "maybe"]) # return random.choice(["yes", "no", "maybe"])
def alert(fro, chan, message): def alert(fro, chan, message):
glob.tokens.enqueueAll(serverPackets.notification(' '.join(message[:]))) glob.streams.broadcast("main", serverPackets.notification(' '.join(message[:])))
return False return False
def alertUser(fro, chan, message): def alertUser(fro, chan, message):
target = message[0].replace("_", " ") target = message[0].replace("_", " ")
targetToken = glob.tokens.getTokenFromUsername(target) targetToken = glob.tokens.getTokenFromUsername(target)
if targetToken != None: if targetToken is not None:
targetToken.enqueue(serverPackets.notification(' '.join(message[1:]))) targetToken.enqueue(serverPackets.notification(' '.join(message[1:])))
return False return False
else: else:
@@ -80,7 +87,7 @@ def alertUser(fro, chan, message):
def moderated(fro, chan, message): def moderated(fro, chan, message):
try: try:
# Make sure we are in a channel and not PM # Make sure we are in a channel and not PM
if chan.startswith("#") == False: if not chan.startswith("#"):
raise exceptions.moderatedPMException raise exceptions.moderatedPMException
# Get on/off # Get on/off
@@ -99,7 +106,7 @@ def kickAll(fro, chan, message):
# Kick everyone but mods/admins # Kick everyone but mods/admins
toKick = [] toKick = []
for key, value in glob.tokens.tokens.items(): for key, value in glob.tokens.tokens.items():
if value.admin == False: if not value.admin:
toKick.append(key) toKick.append(key)
# Loop though users to kick (we can't change dictionary size while iterating) # Loop though users to kick (we can't change dictionary size while iterating)
@@ -115,7 +122,7 @@ def kick(fro, chan, message):
# Get target token and make sure is connected # Get target token and make sure is connected
targetToken = glob.tokens.getTokenFromUsername(target) targetToken = glob.tokens.getTokenFromUsername(target)
if targetToken == None: if targetToken is None:
return "{} is not online".format(target) return "{} is not online".format(target)
# Kick user # Kick user
@@ -126,7 +133,7 @@ def kick(fro, chan, message):
def fokabotReconnect(fro, chan, message): def fokabotReconnect(fro, chan, message):
# Check if fokabot is already connected # Check if fokabot is already connected
if glob.tokens.getTokenFromUserID(999) != None: if glob.tokens.getTokenFromUserID(999) is not None:
return "Fokabot is already connected to Bancho" return "Fokabot is already connected to Bancho"
# Fokabot is not connected, connect it # Fokabot is not connected, connect it
@@ -142,11 +149,11 @@ def silence(fro, chan, message):
reason = ' '.join(message[3:]) reason = ' '.join(message[3:])
# Get target user ID # Get target user ID
targetUserID = userHelper.getID(target) targetUserID = userUtils.getID(target)
userID = userHelper.getID(fro) userID = userUtils.getID(fro)
# Make sure the user exists # Make sure the user exists
if targetUserID == False: if not targetUserID:
return "{}: user not found".format(target) return "{}: user not found".format(target)
# Calculate silence seconds # Calculate silence seconds
@@ -167,12 +174,12 @@ def silence(fro, chan, message):
# Send silence packet to target if he's connected # Send silence packet to target if he's connected
targetToken = glob.tokens.getTokenFromUsername(target) targetToken = glob.tokens.getTokenFromUsername(target)
if targetToken != None: if targetToken is not None:
# user online, silence both in db and with packet # user online, silence both in db and with packet
targetToken.silence(silenceTime, reason, userID) targetToken.silence(silenceTime, reason, userID)
else: else:
# User offline, silence user only in db # User offline, silence user only in db
userHelper.silence(targetUserID, silenceTime, reason, userID) userUtils.silence(targetUserID, silenceTime, reason, userID)
# Log message # Log message
msg = "{} has been silenced for the following reason: {}".format(target, reason) msg = "{} has been silenced for the following reason: {}".format(target, reason)
@@ -185,19 +192,19 @@ def removeSilence(fro, chan, message):
target = message[0].replace("_", " ") target = message[0].replace("_", " ")
# Make sure the user exists # Make sure the user exists
targetUserID = userHelper.getID(target) targetUserID = userUtils.getID(target)
userID = userHelper.getID(fro) userID = userUtils.getID(fro)
if targetUserID == False: if not targetUserID:
return "{}: user not found".format(target) return "{}: user not found".format(target)
# Send new silence end packet to user if he's online # Send new silence end packet to user if he's online
targetToken = glob.tokens.getTokenFromUsername(target) targetToken = glob.tokens.getTokenFromUsername(target)
if targetToken != None: if targetToken is not None:
# User online, remove silence both in db and with packet # User online, remove silence both in db and with packet
targetToken.silence(0, "", userID) targetToken.silence(0, "", userID)
else: else:
# user offline, remove islene ofnlt from db # user offline, remove islene ofnlt from db
userHelper.silence(targetUserID, 0, "", userID) userUtils.silence(targetUserID, 0, "", userID)
return "{}'s silence reset".format(target) return "{}'s silence reset".format(target)
@@ -208,17 +215,17 @@ def ban(fro, chan, message):
target = message[0].replace("_", " ") target = message[0].replace("_", " ")
# Make sure the user exists # Make sure the user exists
targetUserID = userHelper.getID(target) targetUserID = userUtils.getID(target)
userID = userHelper.getID(fro) userID = userUtils.getID(fro)
if targetUserID == False: if not targetUserID:
return "{}: user not found".format(target) return "{}: user not found".format(target)
# Set allowed to 0 # Set allowed to 0
userHelper.ban(targetUserID) userUtils.ban(targetUserID)
# Send ban packet to the user if he's online # Send ban packet to the user if he's online
targetToken = glob.tokens.getTokenFromUsername(target) targetToken = glob.tokens.getTokenFromUsername(target)
if targetToken != None: if targetToken is not None:
targetToken.enqueue(serverPackets.loginBanned()) targetToken.enqueue(serverPackets.loginBanned())
log.rap(userID, "has banned {}".format(target), True) log.rap(userID, "has banned {}".format(target), True)
@@ -231,13 +238,13 @@ def unban(fro, chan, message):
target = message[0].replace("_", " ") target = message[0].replace("_", " ")
# Make sure the user exists # Make sure the user exists
targetUserID = userHelper.getID(target) targetUserID = userUtils.getID(target)
userID = userHelper.getID(fro) userID = userUtils.getID(fro)
if targetUserID == False: if not targetUserID:
return "{}: user not found".format(target) return "{}: user not found".format(target)
# Set allowed to 1 # Set allowed to 1
userHelper.unban(targetUserID) userUtils.unban(targetUserID)
log.rap(userID, "has unbanned {}".format(target), True) log.rap(userID, "has unbanned {}".format(target), True)
return "Welcome back {}!".format(target) return "Welcome back {}!".format(target)
@@ -249,17 +256,17 @@ def restrict(fro, chan, message):
target = message[0].replace("_", " ") target = message[0].replace("_", " ")
# Make sure the user exists # Make sure the user exists
targetUserID = userHelper.getID(target) targetUserID = userUtils.getID(target)
userID = userHelper.getID(fro) userID = userUtils.getID(fro)
if targetUserID == False: if not targetUserID:
return "{}: user not found".format(target) return "{}: user not found".format(target)
# Put this user in restricted mode # Put this user in restricted mode
userHelper.restrict(targetUserID) userUtils.restrict(targetUserID)
# Send restricted mode packet to this user if he's online # Send restricted mode packet to this user if he's online
targetToken = glob.tokens.getTokenFromUsername(target) targetToken = glob.tokens.getTokenFromUsername(target)
if targetToken != None: if targetToken is not None:
targetToken.setRestricted() targetToken.setRestricted()
log.rap(userID, "has put {} in restricted mode".format(target), True) log.rap(userID, "has put {} in restricted mode".format(target), True)
@@ -272,13 +279,13 @@ def unrestrict(fro, chan, message):
target = message[0].replace("_", " ") target = message[0].replace("_", " ")
# Make sure the user exists # Make sure the user exists
targetUserID = userHelper.getID(target) targetUserID = userUtils.getID(target)
userID = userHelper.getID(fro) userID = userUtils.getID(fro)
if targetUserID == False: if not targetUserID:
return "{}: user not found".format(target) return "{}: user not found".format(target)
# Set allowed to 1 # Set allowed to 1
userHelper.unrestrict(targetUserID) userUtils.unrestrict(targetUserID)
log.rap(userID, "has removed restricted mode from {}".format(target), True) log.rap(userID, "has removed restricted mode from {}".format(target), True)
return "Welcome back {}!".format(target) return "Welcome back {}!".format(target)
@@ -306,11 +313,11 @@ def systemReload(fro, chan, message):
glob.chatFilters.loadFilters() glob.chatFilters.loadFilters()
# Send new channels and new bottom icon to everyone # Send new channels and new bottom icon to everyone
glob.tokens.enqueueAll(serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"])) glob.streams.broadcast("main", serverPackets.mainMenuIcon(glob.banchoConf.config["menuIcon"]))
glob.tokens.enqueueAll(serverPackets.channelInfoEnd()) glob.streams.broadcast("main", serverPackets.channelInfoEnd())
for key, value in glob.channels.channels.items(): for key, value in glob.channels.channels.items():
if value.publicRead == True and value.hidden == False: if value.publicRead == True and value.hidden == False:
glob.tokens.enqueueAll(serverPackets.channelInfo(key)) glob.streams.broadcast("main", serverPackets.channelInfo(key))
return "Bancho settings reloaded!" return "Bancho settings reloaded!"
@@ -326,17 +333,17 @@ def systemMaintenance(fro, chan, message):
# Set new maintenance value in bancho_settings table # Set new maintenance value in bancho_settings table
glob.banchoConf.setMaintenance(maintenance) glob.banchoConf.setMaintenance(maintenance)
if maintenance == True: if maintenance:
# We have turned on maintenance mode # We have turned on maintenance mode
# Users that will be disconnected # Users that will be disconnected
who = [] who = []
# Disconnect everyone but mod/admins # Disconnect everyone but mod/admins
for _, value in glob.tokens.tokens.items(): for _, value in glob.tokens.tokens.items():
if value.admin == False: if not value.admin:
who.append(value.userID) who.append(value.userID)
glob.tokens.enqueueAll(serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later.")) glob.streams.broadcast("main", serverPackets.notification("Our bancho server is in maintenance mode. Please try to login again later."))
glob.tokens.multipleEnqueue(serverPackets.loginError(), who) glob.tokens.multipleEnqueue(serverPackets.loginError(), who)
msg = "The server is now in maintenance mode!" msg = "The server is now in maintenance mode!"
else: else:
@@ -352,17 +359,18 @@ def systemStatus(fro, chan, message):
data = systemHelper.getSystemInfo() data = systemHelper.getSystemInfo()
# Final message # Final message
msg = "pep.py bancho server v{}".format(glob.VERSION) msg = "pep.py bancho server v{}\n".format(glob.VERSION)
msg += "made by the Ripple team\n" msg += "made by the Ripple team\n"
msg += "\n" msg += "\n"
msg += "=== BANCHO STATS ===\n" msg += "=== BANCHO STATS ===\n"
msg += "Connected users: {}\n".format(data["connectedUsers"]) msg += "Connected users: {}\n".format(data["connectedUsers"])
msg += "Multiplayer matches: {}\n".format(data["matches"]) msg += "Multiplayer matches: {}\n".format(data["matches"])
msg += "Uptime: {}\n".format(data["uptime"])
msg += "\n" msg += "\n"
msg += "=== SYSTEM STATS ===\n" msg += "=== SYSTEM STATS ===\n"
msg += "CPU: {}%\n".format(data["cpuUsage"]) msg += "CPU: {}%\n".format(data["cpuUsage"])
msg += "RAM: {}GB/{}GB\n".format(data["usedMemory"], data["totalMemory"]) msg += "RAM: {}GB/{}GB\n".format(data["usedMemory"], data["totalMemory"])
if data["unix"] == True: if data["unix"]:
msg += "Load average: {}/{}/{}\n".format(data["loadAverage"][0], data["loadAverage"][1], data["loadAverage"][2]) msg += "Load average: {}/{}/{}\n".format(data["loadAverage"][0], data["loadAverage"][1], data["loadAverage"][2])
return msg return msg
@@ -372,11 +380,15 @@ def getPPMessage(userID, just_data = False):
try: try:
# Get user token # Get user token
token = glob.tokens.getTokenFromUserID(userID) token = glob.tokens.getTokenFromUserID(userID)
if token == None: if token is None:
return False return False
currentMap = token.tillerino[0]
currentMods = token.tillerino[1]
currentAcc = token.tillerino[2]
# Send request to LETS api # Send request to LETS api
resp = requests.get("http://127.0.0.1:5002/api/v1/pp?b={}&m={}&a={}".format(token.tillerino[0], token.tillerino[1], token.tillerino[2]), timeout=10).text resp = requests.get("http://127.0.0.1:5002/api/v1/pp?b={}&m={}".format(currentMap, currentMods, currentAcc), timeout=10).text
data = json.loads(resp) data = json.loads(resp)
# Make sure status is in response data # Make sure status is in response data
@@ -395,16 +407,25 @@ def getPPMessage(userID, just_data = False):
# Return response in chat # Return response in chat
# Song name and mods # Song name and mods
msg = "{song}{plus}{mods} ".format(song=data["song_name"], plus="+" if token.tillerino[1] > 0 else "", mods=generalFunctions.readableMods(token.tillerino[1])) msg = "{song}{plus}{mods} ".format(song=data["song_name"], plus="+" if currentMods > 0 else "", mods=generalUtils.readableMods(currentMods))
# PP values # PP values
if token.tillerino[2] == -1: if currentAcc == -1:
msg += "95%: {pp95}pp | 98%: {pp98}pp | 99% {pp99}pp | 100%: {pp100}pp".format(pp100=data["pp"][0], pp99=data["pp"][1], pp98=data["pp"][2], pp95=data["pp"][3]) msg += "95%: {pp95}pp | 98%: {pp98}pp | 99% {pp99}pp | 100%: {pp100}pp".format(pp100=data["pp"][0], pp99=data["pp"][1], pp98=data["pp"][2], pp95=data["pp"][3])
else: else:
msg += "{acc:.2f}%: {pp}pp".format(acc=token.tillerino[2], pp=data["pp"][0]) msg += "{acc:.2f}%: {pp}pp".format(acc=token.tillerino[2], pp=data["pp"][0])
originalAR = data["ar"]
# calc new AR if HR/EZ is on
if (currentMods & mods.EASY) > 0:
data["ar"] = max(0, data["ar"] / 2)
if (currentMods & mods.HARDROCK) > 0:
data["ar"] = min(10, data["ar"] * 1.4)
arstr = " ({})".format(originalAR) if originalAR != data["ar"] else ""
# Beatmap info # Beatmap info
msg += " | {bpm} BPM | AR {ar} | {stars:.2f} stars".format(bpm=data["bpm"], stars=data["stars"], ar=data["ar"]) msg += " | {bpm} BPM | AR {ar}{arstr} | {stars:.2f} stars".format(bpm=data["bpm"], stars=data["stars"], ar=data["ar"], arstr=arstr)
# Return final message # Return final message
return msg return msg
@@ -414,10 +435,10 @@ def getPPMessage(userID, just_data = False):
except exceptions.apiException: except exceptions.apiException:
# API error # API error
return "Unknown error in LETS API call. Please tell this to a dev." return "Unknown error in LETS API call. Please tell this to a dev."
except: #except:
# Unknown exception # Unknown exception
# TODO: print exception # TODO: print exception
return False # return False
def tillerinoNp(fro, chan, message): def tillerinoNp(fro, chan, message):
try: try:
@@ -425,21 +446,41 @@ def tillerinoNp(fro, chan, message):
if chan.startswith("#"): if chan.startswith("#"):
return False return False
playWatch = message[1] == "playing" or message[1] == "watching"
# Get URL from message # Get URL from message
if message[1] == "listening": if message[1] == "listening":
beatmapURL = str(message[3][1:]) beatmapURL = str(message[3][1:])
elif message[1] == "playing" or message[1] == "watching": elif playWatch:
beatmapURL = str(message[2][1:]) beatmapURL = str(message[2][1:])
else: else:
return False return False
modsEnum = 0
mapping = {
"-Easy": mods.EASY,
"-NoFail": mods.NOFAIL,
"+Hidden": mods.HIDDEN,
"+HardRock": mods.HARDROCK,
"+Nightcore": mods.NIGHTCORE,
"+DoubleTime": mods.DOUBLETIME,
"-HalfTime": mods.HALFTIME,
"+Flashlight": mods.FLASHLIGHT,
"-SpunOut": mods.SPUNOUT
}
if playWatch:
for part in message:
part = part.replace("\x01", "")
if part in mapping.keys():
modsEnum += mapping[part]
# Get beatmap id from URL # Get beatmap id from URL
beatmapID = fokabot.npRegex.search(beatmapURL).groups(0)[0] beatmapID = fokabot.npRegex.search(beatmapURL).groups(0)[0]
# Update latest tillerino song for current token # Update latest tillerino song for current token
token = glob.tokens.getTokenFromUsername(fro) token = glob.tokens.getTokenFromUsername(fro)
if token != None: if token is not None:
token.tillerino = [int(beatmapID), 0, -1.0] token.tillerino = [int(beatmapID), modsEnum, -1.0]
userID = token.userID userID = token.userID
# Return tillerino message # Return tillerino message
@@ -456,7 +497,7 @@ def tillerinoMods(fro, chan, message):
# Get token and user ID # Get token and user ID
token = glob.tokens.getTokenFromUsername(fro) token = glob.tokens.getTokenFromUsername(fro)
if token == None: if token is None:
return False return False
userID = token.userID userID = token.userID
@@ -474,23 +515,23 @@ def tillerinoMods(fro, chan, message):
modsEnum = 0 modsEnum = 0
break break
elif i == "NF": elif i == "NF":
modsEnum += mods.NoFail modsEnum += mods.NOFAIL
elif i == "EZ": elif i == "EZ":
modsEnum += mods.Easy modsEnum += mods.EASY
elif i == "HD": elif i == "HD":
modsEnum += mods.Hidden modsEnum += mods.HIDDEN
elif i == "HR": elif i == "HR":
modsEnum += mods.HardRock modsEnum += mods.HARDROCK
elif i == "DT": elif i == "DT":
modsEnum += mods.DoubleTime modsEnum += mods.DOUBLETIME
elif i == "HT": elif i == "HT":
modsEnum += mods.HalfTime modsEnum += mods.HALFTIME
elif i == "NC": elif i == "NC":
modsEnum += mods.Nightcore modsEnum += mods.NIGHTCORE
elif i == "FL": elif i == "FL":
modsEnum += mods.Flashlight modsEnum += mods.FLASHLIGHT
elif i == "SO": elif i == "SO":
modsEnum += mods.SpunOut modsEnum += mods.SPUNOUT
# Set mods # Set mods
token.tillerino[1] = modsEnum token.tillerino[1] = modsEnum
@@ -508,7 +549,7 @@ def tillerinoAcc(fro, chan, message):
# Get token and user ID # Get token and user ID
token = glob.tokens.getTokenFromUsername(fro) token = glob.tokens.getTokenFromUsername(fro)
if token == None: if token is None:
return False return False
userID = token.userID userID = token.userID
@@ -539,26 +580,26 @@ def tillerinoLast(fro, chan, message):
WHERE users.username = %s WHERE users.username = %s
ORDER BY scores.time DESC ORDER BY scores.time DESC
LIMIT 1""", [fro]) LIMIT 1""", [fro])
if data == None: if data is None:
return False return False
diffString = "difficulty_{}".format(gameModes.getGameModeForDB(data["play_mode"])) diffString = "difficulty_{}".format(gameModes.getGameModeForDB(data["play_mode"]))
rank = generalFunctions.getRank(data["play_mode"], data["mods"], data["accuracy"],\ rank = generalUtils.getRank(data["play_mode"], data["mods"], data["accuracy"],
data["300_count"], data["100_count"], data["50_count"], data["misses_count"]) data["300_count"], data["100_count"], data["50_count"], data["misses_count"])
ifPlayer = "{0} | ".format(fro) if chan != "FokaBot" else "" ifPlayer = "{0} | ".format(fro) if chan != "FokaBot" else ""
ifFc = " (FC)" if data["max_combo"] == data["fc"] else " {0}x/{1}x".format(data["max_combo"], data["fc"]) ifFc = " (FC)" if data["max_combo"] == data["fc"] else " {0}x/{1}x".format(data["max_combo"], data["fc"])
beatmapLink = "[http://osu.ppy.sh/b/{1} {0}]".format(data["sn"], data["bid"]) beatmapLink = "[http://osu.ppy.sh/b/{1} {0}]".format(data["sn"], data["bid"])
hasPP = data["play_mode"] == gameModes.std or data["play_mode"] == gameModes.mania hasPP = data["play_mode"] == gameModes.STD or data["play_mode"] == gameModes.MANIA
msg = ifPlayer msg = ifPlayer
msg += beatmapLink msg += beatmapLink
if data["play_mode"] != gameModes.std: if data["play_mode"] != gameModes.STD:
msg += " <{0}>".format(gameModes.getGameModeForPrinting(data["play_mode"])) msg += " <{0}>".format(gameModes.getGameModeForPrinting(data["play_mode"]))
if data["mods"]: if data["mods"]:
msg += ' +' + generalFunctions.readableMods(data["mods"]) msg += ' +' + generalUtils.readableMods(data["mods"])
if not hasPP: if not hasPP:
msg += " | {0:,}".format(data["score"]) msg += " | {0:,}".format(data["score"])
@@ -575,7 +616,7 @@ def tillerinoLast(fro, chan, message):
stars = data[diffString] stars = data[diffString]
if data["mods"]: if data["mods"]:
token = glob.tokens.getTokenFromUsername(fro) token = glob.tokens.getTokenFromUsername(fro)
if token == None: if token is None:
return False return False
userID = token.userID userID = token.userID
token.tillerino[0] = data["bid"] token.tillerino[0] = data["bid"]
@@ -595,6 +636,69 @@ def mm00(fro, chan, message):
random.seed() random.seed()
return random.choice(["meme", "MA MAURO ESISTE?"]) return random.choice(["meme", "MA MAURO ESISTE?"])
def pp(fro, chan, message):
if chan.startswith("#"):
return False
gameMode = None
if len(message) >= 1:
gm = {
"standard": 0,
"std": 0,
"taiko": 1,
"ctb": 2,
"mania": 3
}
if message[0].lower() not in gm:
return "What's that game mode? I've never heard of it :/"
else:
gameMode = gm[message[0].lower()]
token = glob.tokens.getTokenFromUsername(fro)
if token is None:
return False
if gameMode is None:
gameMode = token.gameMode
if gameMode == gameModes.TAIKO or gameMode == gameModes.CTB:
return "PP for your current game mode is not supported yet."
pp = userUtils.getPP(token.userID, gameMode)
return "You have {:,} pp".format(pp)
def updateBeatmap(fro, chan, to):
try:
# Run the command in PM only
if chan.startswith("#"):
return False
# Get token and user ID
token = glob.tokens.getTokenFromUsername(fro)
if token is None:
return False
# Make sure the user has triggered the bot with /np command
if token.tillerino[0] == 0:
return "Please give me a beatmap first with /np command."
# Send request
beatmapData = glob.db.fetch("SELECT beatmapset_id, song_name FROM beatmaps WHERE beatmap_id = %s LIMIT 1", [token.tillerino[0]])
if beatmapData is None:
return "Couldn't find beatmap data in database. Please load the beatmap's leaderboard and try again."
response = requests.post("{}/api/v1/update_beatmap".format(glob.conf.config["mirror"]["apiurl"]), {
"beatmap_set_id": beatmapData["beatmapset_id"],
"beatmap_name": beatmapData["song_name"],
"username": token.username,
"key": glob.conf.config["mirror"]["apikey"]
})
if response.status_code == 200:
return "An update request for that beatmap has been queued. You'll receive a message once the beatmap has been updated on our mirror!"
elif response.status_code == 429:
return "You are sending too many beatmaps update requests. Wait a bit and retry later."
else:
return "Error in beatmap mirror API request. Tell this to a dev: {}".format(response.text)
except:
return False
""" """
Commands list Commands list
@@ -724,6 +828,17 @@ commands = [
}, { }, {
"trigger": "!last", "trigger": "!last",
"callback": tillerinoLast "callback": tillerinoLast
}, {
"trigger": "!ir",
"privileges": privileges.ADMIN_MANAGE_SERVERS,
"callback": instantRestart
}, {
"trigger": "!pp",
"callback": pp
}, {
"trigger": "!update",
"callback": updateBeatmap,
"privileges": privileges.ADMIN_MANAGE_SERVERS, # TODO: Remove privileges for !update
} }
# #
# "trigger": "!acc", # "trigger": "!acc",

View File

@@ -1,41 +0,0 @@
"""Contains readable gamemodes with their codes"""
std = 0
taiko = 1
ctb = 2
mania = 3
def getGameModeForDB(gameMode):
"""
Convert a gamemode number to string for database table/column
gameMode -- gameMode int or variable (ex: gameMode.std)
return -- game mode readable string for db
"""
if gameMode == std:
return "std"
elif gameMode == taiko:
return "taiko"
elif gameMode == ctb:
return "ctb"
else:
return "mania"
def getGameModeForPrinting(gameMode):
"""
Convert a gamemode number to string for showing to a user (e.g. !last)
gameMode -- gameMode int or variable (ex: gameMode.std)
return -- game mode readable string for a human
"""
if gameMode == std:
return "osu!"
elif gameMode == taiko:
return "Taiko"
elif gameMode == ctb:
return "CatchTheBeat"
else:
return "osu!mania"

View File

@@ -1,30 +0,0 @@
Nomod = 0
NoFail = 1
Easy = 2
NoVideo = 4
Hidden = 8
HardRock = 16
SuddenDeath = 32
DoubleTime = 64
Relax = 128
HalfTime = 256
Nightcore = 512
Flashlight = 1024
Autoplay = 2048
SpunOut = 4096
Relax2 = 8192
Perfect = 16384
Key4 = 32768
Key5 = 65536
Key6 = 131072
Key7 = 262144
Key8 = 524288
keyMod = 1015808
FadeIn = 1048576
Random = 2097152
LastMod = 4194304
Key9 = 16777216
Key10 = 33554432
Key1 = 67108864
Key3 = 134217728
Key2 = 268435456

View File

@@ -74,7 +74,9 @@ server_channelInfoEnd = 89
client_matchChangePassword = 90 client_matchChangePassword = 90
server_matchChangePassword = 91 server_matchChangePassword = 91
server_silenceEnd = 92 server_silenceEnd = 92
client_specialMatchInfoRequest = 93
server_userSilenced = 94 server_userSilenced = 94
server_userPresenceBundle = 96 server_userPresenceBundle = 96
client_userPanelRequest = 97 client_userPanelRequest = 97
client_tournamentMatchInfoRequest = 93
client_tournamentJoinMatchChannel = 108
client_tournamentLeaveMatchChannel = 109

View File

@@ -1,21 +0,0 @@
USER_PUBLIC = 1
USER_NORMAL = 2 << 0
USER_DONOR = 2 << 1
ADMIN_ACCESS_RAP = 2 << 2
ADMIN_MANAGE_USERS = 2 << 3
ADMIN_BAN_USERS = 2 << 4
ADMIN_SILENCE_USERS = 2 << 5
ADMIN_WIPE_USERS = 2 << 6
ADMIN_MANAGE_BEATMAPS = 2 << 7
ADMIN_MANAGE_SERVERS = 2 << 8
ADMIN_MANAGE_SETTINGS = 2 << 9
ADMIN_MANAGE_BETAKEYS = 2 << 10
ADMIN_MANAGE_REPORTS = 2 << 11
ADMIN_MANAGE_DOCS = 2 << 12
ADMIN_MANAGE_BADGES = 2 << 13
ADMIN_VIEW_RAP_LOGS = 2 << 14
ADMIN_MANAGE_PRIVILEGES = 2 << 15
ADMIN_SEND_ALERTS = 2 << 16
ADMIN_CHAT_MOD = 2 << 17
ADMIN_KICK_USERS = 2 << 18
USER_PENDING_VERIFICATION = 2 << 19

View File

@@ -1,69 +1,65 @@
""" Contains functions used to write specific server packets to byte streams """ """ Contains functions used to write specific server packets to byte streams """
from helpers import packetHelper from common.constants import privileges
from common.ripple import userUtils
from constants import dataTypes from constants import dataTypes
from helpers import userHelper
from objects import glob
from constants import userRanks
from constants import packetIDs from constants import packetIDs
from constants import userRanks
from helpers import packetHelper
from objects import glob
""" Login errors packets """ Login errors packets """
(userID packets derivates) """
def loginFailed(): def loginFailed():
return packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.SINT32]])
def forceUpdate(): def forceUpdate():
return packetHelper.buildPacket(packetIDs.server_userID, [[-2, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-2, dataTypes.SINT32]])
def loginBanned(): def loginBanned():
packets = packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.sInt32]]) packets = packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.SINT32]])
packets += notification("You are banned. You can ask to get unbanned after 1 month since your ban by contacting support@ripple.moe") packets += notification("You are banned. You can appeal after one month since your ban by sending an email to support@ripple.moe from the email address you've used to sign up.")
return packets
def loginLocked():
packets = packetHelper.buildPacket(packetIDs.server_userID, [[-1, dataTypes.SINT32]])
packets += notification("Your account is locked. You can't log in, but your profile and scores are still visible from the website. If you want to unlock your account, send an email to support@ripple.moe from the email address you've used to sign up.")
return packets return packets
#return packetHelper.buildPacket(packetIDs.server_userID, [[-3, dataTypes.sInt32]])
def loginError(): def loginError():
return packetHelper.buildPacket(packetIDs.server_userID, [[-5, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-5, dataTypes.SINT32]])
def needSupporter(): def needSupporter():
return packetHelper.buildPacket(packetIDs.server_userID, [[-6, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-6, dataTypes.SINT32]])
def needVerification(): def needVerification():
return packetHelper.buildPacket(packetIDs.server_userID, [[-8, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[-8, dataTypes.SINT32]])
""" Login packets """ """ Login packets """
def userID(uid): def userID(uid):
return packetHelper.buildPacket(packetIDs.server_userID, [[uid, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_userID, [[uid, dataTypes.SINT32]])
def silenceEndTime(seconds): def silenceEndTime(seconds):
return packetHelper.buildPacket(packetIDs.server_silenceEnd, [[seconds, dataTypes.uInt32]]) return packetHelper.buildPacket(packetIDs.server_silenceEnd, [[seconds, dataTypes.UINT32]])
def protocolVersion(version = 19): def protocolVersion(version = 19):
return packetHelper.buildPacket(packetIDs.server_protocolVersion, [[version, dataTypes.uInt32]]) return packetHelper.buildPacket(packetIDs.server_protocolVersion, [[version, dataTypes.UINT32]])
def mainMenuIcon(icon): def mainMenuIcon(icon):
return packetHelper.buildPacket(packetIDs.server_mainMenuIcon, [[icon, dataTypes.string]]) return packetHelper.buildPacket(packetIDs.server_mainMenuIcon, [[icon, dataTypes.STRING]])
def userSupporterGMT(supporter, GMT): def userSupporterGMT(supporter, GMT, tournamentStaff):
result = 1 result = 1
if supporter == True: if supporter:
result += 4 result |= userRanks.SUPPORTER
if GMT == True: if GMT:
result += 2 result |= userRanks.BAT
return packetHelper.buildPacket(packetIDs.server_supporterGMT, [[result, dataTypes.uInt32]]) if tournamentStaff:
result |= userRanks.TOURNAMENT_STAFF
return packetHelper.buildPacket(packetIDs.server_supporterGMT, [[result, dataTypes.UINT32]])
def friendList(userID): def friendList(userID):
friendsData = [] friends = userUtils.getFriendList(userID)
return packetHelper.buildPacket(packetIDs.server_friendsList, [[friends, dataTypes.INT_LIST]])
# Get friend IDs from db
friends = userHelper.getFriendList(userID)
# Friends number
friendsData.append([len(friends), dataTypes.uInt16])
# Add all friend user IDs to friendsData
for i in friends:
friendsData.append([i, dataTypes.sInt32])
return packetHelper.buildPacket(packetIDs.server_friendsList, friendsData)
def onlineUsers(): def onlineUsers():
userIDs = [] userIDs = []
@@ -71,27 +67,25 @@ def onlineUsers():
# Create list with all connected (and not restricted) users # Create list with all connected (and not restricted) users
for _, value in users.items(): for _, value in users.items():
if value.restricted == False: if not value.restricted:
userIDs.append(value.userID) userIDs.append(value.userID)
return packetHelper.buildPacket(packetIDs.server_userPresenceBundle, [[userIDs, dataTypes.intList]]) return packetHelper.buildPacket(packetIDs.server_userPresenceBundle, [[userIDs, dataTypes.INT_LIST]])
""" Users packets """ """ Users packets """
def userLogout(userID): def userLogout(userID):
return packetHelper.buildPacket(packetIDs.server_userLogout, [[userID, dataTypes.sInt32], [0, dataTypes.byte]]) return packetHelper.buildPacket(packetIDs.server_userLogout, [[userID, dataTypes.SINT32], [0, dataTypes.BYTE]])
def userPanel(userID, force = False): def userPanel(userID, force = False):
# Connected and restricted check # Connected and restricted check
userToken = glob.tokens.getTokenFromUserID(userID) userToken = glob.tokens.getTokenFromUserID(userID)
if userToken == None: if userToken is None or ((userToken.restricted) and not force):
return bytes()
if userToken.restricted == True and force == False:
return bytes() return bytes()
# Get user data # Get user data
username = userToken.username username = userToken.username
timezone = 24+userToken.timeOffset # TODO: Timezone timezone = 24+userToken.timeOffset
country = userToken.country country = userToken.country
gameRank = userToken.gameRank gameRank = userToken.gameRank
latitude = userToken.getLatitude() latitude = userToken.getLatitude()
@@ -99,134 +93,141 @@ def userPanel(userID, force = False):
# Get username color according to rank # Get username color according to rank
# Only admins and normal users are currently supported # Only admins and normal users are currently supported
userRank = 0
if username == "FokaBot": if username == "FokaBot":
userRank = userRanks.MOD userRank |= userRanks.MOD
elif userHelper.isInPrivilegeGroup(userID, "community manager") == True: elif userUtils.isInPrivilegeGroup(userID, "community manager"):
userRank = userRanks.MOD userRank |= userRanks.MOD
elif userHelper.isInPrivilegeGroup(userID, "developer") == True: elif userUtils.isInPrivilegeGroup(userID, "developer"):
userRank = userRanks.ADMIN userRank |= userRanks.ADMIN
elif userHelper.isInPrivilegeGroup(userID, "donor") == True: elif (userToken.privileges & privileges.USER_DONOR) > 0:
userRank = userRanks.SUPPORTER userRank |= userRanks.SUPPORTER
else: else:
userRank = userRanks.NORMAL userRank |= userRanks.NORMAL
return packetHelper.buildPacket(packetIDs.server_userPanel, return packetHelper.buildPacket(packetIDs.server_userPanel,
[ [
[userID, dataTypes.sInt32], [userID, dataTypes.SINT32],
[username, dataTypes.string], [username, dataTypes.STRING],
[timezone, dataTypes.byte], [timezone, dataTypes.BYTE],
[country, dataTypes.byte], [country, dataTypes.BYTE],
[userRank, dataTypes.byte], [userRank, dataTypes.BYTE],
[longitude, dataTypes.ffloat], [longitude, dataTypes.FFLOAT],
[latitude, dataTypes.ffloat], [latitude, dataTypes.FFLOAT],
[gameRank, dataTypes.uInt32] [gameRank, dataTypes.UINT32]
]) ])
def userStats(userID, force = False): def userStats(userID, force = False):
# Get userID's token from tokens list # Get userID's token from tokens list
userToken = glob.tokens.getTokenFromUserID(userID) userToken = glob.tokens.getTokenFromUserID(userID)
if userToken == None: if userToken is None or ((userToken.restricted or userToken.irc or userToken.tournament) and not force):
return bytes()
if (userToken.restricted == True or userToken.irc == True) and force == False:
return bytes() return bytes()
return packetHelper.buildPacket(packetIDs.server_userStats, return packetHelper.buildPacket(packetIDs.server_userStats,
[ [
[userID, dataTypes.uInt32], [userID, dataTypes.UINT32],
[userToken.actionID, dataTypes.byte], [userToken.actionID, dataTypes.BYTE],
[userToken.actionText, dataTypes.string], [userToken.actionText, dataTypes.STRING],
[userToken.actionMd5, dataTypes.string], [userToken.actionMd5, dataTypes.STRING],
[userToken.actionMods, dataTypes.sInt32], [userToken.actionMods, dataTypes.SINT32],
[userToken.gameMode, dataTypes.byte], [userToken.gameMode, dataTypes.BYTE],
[0, dataTypes.sInt32], [userToken.beatmapID, dataTypes.SINT32],
[userToken.rankedScore, dataTypes.uInt64], [userToken.rankedScore, dataTypes.UINT64],
[userToken.accuracy, dataTypes.ffloat], [userToken.accuracy, dataTypes.FFLOAT],
[userToken.playcount, dataTypes.uInt32], [userToken.playcount, dataTypes.UINT32],
[userToken.totalScore, dataTypes.uInt64], [userToken.totalScore, dataTypes.UINT64],
[userToken.gameRank, dataTypes.uInt32], [userToken.gameRank, dataTypes.UINT32],
[userToken.pp, dataTypes.uInt16] [userToken.pp if 65535 >= userToken.pp > 0 else 0, dataTypes.UINT16]
]) ])
""" Chat packets """ """ Chat packets """
def sendMessage(fro, to, message): def sendMessage(fro, to, message):
return packetHelper.buildPacket(packetIDs.server_sendMessage, [[fro, dataTypes.string], [message, dataTypes.string], [to, dataTypes.string], [userHelper.getID(fro), dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_sendMessage, [
[fro, dataTypes.STRING],
[message, dataTypes.STRING],
[to, dataTypes.STRING],
[userUtils.getID(fro), dataTypes.SINT32]
])
def channelJoinSuccess(userID, chan): def channelJoinSuccess(userID, chan):
return packetHelper.buildPacket(packetIDs.server_channelJoinSuccess, [[chan, dataTypes.string]]) return packetHelper.buildPacket(packetIDs.server_channelJoinSuccess, [[chan, dataTypes.STRING]])
def channelInfo(chan): def channelInfo(chan):
channel = glob.channels.channels[chan] channel = glob.channels.channels[chan]
return packetHelper.buildPacket(packetIDs.server_channelInfo, [[chan, dataTypes.string], [channel.description, dataTypes.string], [channel.getConnectedUsersCount(), dataTypes.uInt16]]) return packetHelper.buildPacket(packetIDs.server_channelInfo, [
[chan, dataTypes.STRING],
[channel.description, dataTypes.STRING],
[channel.getConnectedUsersCount(), dataTypes.UINT16]
])
def channelInfoEnd(): def channelInfoEnd():
return packetHelper.buildPacket(packetIDs.server_channelInfoEnd, [[0, dataTypes.uInt32]]) return packetHelper.buildPacket(packetIDs.server_channelInfoEnd, [[0, dataTypes.UINT32]])
def channelKicked(chan): def channelKicked(chan):
return packetHelper.buildPacket(packetIDs.server_channelKicked, [[chan, dataTypes.string]]) return packetHelper.buildPacket(packetIDs.server_channelKicked, [[chan, dataTypes.STRING]])
def userSilenced(userID): def userSilenced(userID):
return packetHelper.buildPacket(packetIDs.server_userSilenced, [[userID, dataTypes.uInt32]]) return packetHelper.buildPacket(packetIDs.server_userSilenced, [[userID, dataTypes.UINT32]])
""" Spectator packets """ """ Spectator packets """
def addSpectator(userID): def addSpectator(userID):
return packetHelper.buildPacket(packetIDs.server_spectatorJoined, [[userID, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_spectatorJoined, [[userID, dataTypes.SINT32]])
def removeSpectator(userID): def removeSpectator(userID):
return packetHelper.buildPacket(packetIDs.server_spectatorLeft, [[userID, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_spectatorLeft, [[userID, dataTypes.SINT32]])
def spectatorFrames(data): def spectatorFrames(data):
return packetHelper.buildPacket(packetIDs.server_spectateFrames, [[data, dataTypes.bbytes]]) return packetHelper.buildPacket(packetIDs.server_spectateFrames, [[data, dataTypes.BBYTES]])
def noSongSpectator(userID): def noSongSpectator(userID):
return packetHelper.buildPacket(packetIDs.server_spectatorCantSpectate, [[userID, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_spectatorCantSpectate, [[userID, dataTypes.SINT32]])
def fellowSpectatorJoined(userID): def fellowSpectatorJoined(userID):
return packetHelper.buildPacket(packetIDs.server_fellowSpectatorJoined, [[userID, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_fellowSpectatorJoined, [[userID, dataTypes.SINT32]])
def fellowSpectatorLeft(userID): def fellowSpectatorLeft(userID):
return packetHelper.buildPacket(packetIDs.server_fellowSpectatorLeft, [[userID, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_fellowSpectatorLeft, [[userID, dataTypes.SINT32]])
""" Multiplayer Packets """ """ Multiplayer Packets """
def createMatch(matchID): def createMatch(matchID):
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return None return bytes()
# Get match binary data and build packet # Get match binary data and build packet
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
return packetHelper.buildPacket(packetIDs.server_newMatch, match.getMatchData()) return packetHelper.buildPacket(packetIDs.server_newMatch, match.getMatchData())
# TODO: Add match object argument to save some CPU
def updateMatch(matchID): def updateMatch(matchID):
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return None return bytes()
# Get match binary data and build packet # Get match binary data and build packet
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
return packetHelper.buildPacket(packetIDs.server_updateMatch, match.getMatchData()) return packetHelper.buildPacket(packetIDs.server_updateMatch, match.getMatchData())
def matchStart(matchID): def matchStart(matchID):
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return None return bytes()
# Get match binary data and build packet # Get match binary data and build packet
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
return packetHelper.buildPacket(packetIDs.server_matchStart, match.getMatchData()) return packetHelper.buildPacket(packetIDs.server_matchStart, match.getMatchData())
def disposeMatch(matchID): def disposeMatch(matchID):
return packetHelper.buildPacket(packetIDs.server_disposeMatch, [[matchID, dataTypes.uInt16]]) return packetHelper.buildPacket(packetIDs.server_disposeMatch, [[matchID, dataTypes.UINT32]])
def matchJoinSuccess(matchID): def matchJoinSuccess(matchID):
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
return None return bytes()
# Get match binary data and build packet # Get match binary data and build packet
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
@@ -237,32 +238,33 @@ def matchJoinFail():
return packetHelper.buildPacket(packetIDs.server_matchJoinFail) return packetHelper.buildPacket(packetIDs.server_matchJoinFail)
def changeMatchPassword(newPassword): def changeMatchPassword(newPassword):
return packetHelper.buildPacket(packetIDs.server_matchChangePassword, [[newPassword, dataTypes.string]]) return packetHelper.buildPacket(packetIDs.server_matchChangePassword, [[newPassword, dataTypes.STRING]])
def allPlayersLoaded(): def allPlayersLoaded():
return packetHelper.buildPacket(packetIDs.server_matchAllPlayersLoaded) return packetHelper.buildPacket(packetIDs.server_matchAllPlayersLoaded)
def playerSkipped(userID): def playerSkipped(userID):
return packetHelper.buildPacket(packetIDs.server_matchPlayerSkipped, [[userID, dataTypes.sInt32]]) return packetHelper.buildPacket(packetIDs.server_matchPlayerSkipped, [[userID, dataTypes.SINT32]])
def allPlayersSkipped(): def allPlayersSkipped():
return packetHelper.buildPacket(packetIDs.server_matchSkip) return packetHelper.buildPacket(packetIDs.server_matchSkip)
def matchFrames(slotID, data): def matchFrames(slotID, data):
return packetHelper.buildPacket(packetIDs.server_matchScoreUpdate, [[data[7:11], dataTypes.bbytes], [slotID, dataTypes.byte], [data[12:], dataTypes.bbytes]]) return packetHelper.buildPacket(packetIDs.server_matchScoreUpdate, [[data[7:11], dataTypes.BBYTES], [slotID, dataTypes.BYTE], [data[12:], dataTypes.BBYTES]])
def matchComplete(): def matchComplete():
return packetHelper.buildPacket(packetIDs.server_matchComplete) return packetHelper.buildPacket(packetIDs.server_matchComplete)
def playerFailed(slotID): def playerFailed(slotID):
return packetHelper.buildPacket(packetIDs.server_matchPlayerFailed, [[slotID, dataTypes.uInt32]]) return packetHelper.buildPacket(packetIDs.server_matchPlayerFailed, [[slotID, dataTypes.UINT32]])
def matchTransferHost(): def matchTransferHost():
return packetHelper.buildPacket(packetIDs.server_matchTransferHost) return packetHelper.buildPacket(packetIDs.server_matchTransferHost)
""" Other packets """ """ Other packets """
def notification(message): def notification(message):
return packetHelper.buildPacket(packetIDs.server_notification, [[message, dataTypes.string]]) return packetHelper.buildPacket(packetIDs.server_notification, [[message, dataTypes.STRING]])
def banchoRestart(msUntilReconnection): def banchoRestart(msUntilReconnection):
return packetHelper.buildPacket(packetIDs.server_restart, [[msUntilReconnection, dataTypes.uInt32]]) return packetHelper.buildPacket(packetIDs.server_restart, [[msUntilReconnection, dataTypes.UINT32]])

View File

@@ -1,8 +1,9 @@
"""Bancho user ranks""" """Bancho user ranks"""
NORMAL = 0 NORMAL = 0
PLAYER = 1 PLAYER = 1
BAT = 2
SUPPORTER = 4 SUPPORTER = 4
MOD = 6 MOD = 6
PEPPY = 8 PEPPY = 8
ADMIN = 16 ADMIN = 16
TOURNAMENTSTAFF = 32 TOURNAMENT_STAFF = 32

View File

@@ -1,19 +1,17 @@
from objects import glob from common.log import logUtils as log
from constants import serverPackets
from constants import exceptions from constants import exceptions
from helpers import logHelper as log from constants import serverPackets
from objects import glob
def handle(userToken, packetData):
# get usertoken data
userID = userToken.userID
def handle(userToken, _):
try: try:
# We don't have the beatmap, we can't spectate # We don't have the beatmap, we can't spectate
target = userToken.spectating if userToken.spectating not in glob.tokens.tokens:
targetToken = glob.tokens.getTokenFromUserID(target) raise exceptions.tokenNotFoundException()
# Send the packet to host # Send the packet to host
targetToken.enqueue(serverPackets.noSongSpectator(userID)) glob.tokens.tokens[userToken.spectating].enqueue(serverPackets.noSongSpectator(userToken.userID))
except exceptions.tokenNotFoundException: except exceptions.tokenNotFoundException:
# Stop spectating if token not found # Stop spectating if token not found
log.warning("Spectator can't spectate: token not found") log.warning("Spectator can't spectate: token not found")

View File

@@ -1,10 +1,10 @@
from objects import glob from common.constants import actions
from common.log import logUtils as log
from common.ripple import userUtils
from constants import clientPackets from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from helpers import userHelper from objects import glob
from helpers import logHelper as log
from constants import actions
from helpers import chatHelper as chat
def handle(userToken, packetData): def handle(userToken, packetData):
# Get usertoken data # Get usertoken data
@@ -12,55 +12,54 @@ def handle(userToken, packetData):
username = userToken.username username = userToken.username
# Make sure we are not banned # Make sure we are not banned
if userHelper.isBanned(userID) == True: if userUtils.isBanned(userID):
userToken.enqueue(serverPackets.loginBanned()) userToken.enqueue(serverPackets.loginBanned())
return return
# Send restricted message if needed # Send restricted message if needed
if userToken.restricted == False: if not userToken.restricted:
if userHelper.isRestricted(userID) == True: if userUtils.isRestricted(userID):
userToken.setRestricted() userToken.setRestricted()
# Change action packet # Change action packet
packetData = clientPackets.userActionChange(packetData) packetData = clientPackets.userActionChange(packetData)
# Update cached stats if our pp changedm if we've just submitted a score or we've changed gameMode # If we are not in spectate status but we're spectating someone, stop spectating
if (userToken.actionID == actions.playing or userToken.actionID == actions.multiplaying) or (userToken.pp != userHelper.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]): '''
log.debug("!!!! UPDATING CACHED STATS !!!!") if userToken.spectating != 0 and userToken.actionID != actions.WATCHING and userToken.actionID != actions.IDLE and userToken.actionID != actions.AFK:
userToken.stopSpectating()
# If we are not in multiplayer but we are in a match, part match
if userToken.matchID != -1 and userToken.actionID != actions.MULTIPLAYING and userToken.actionID != actions.MULTIPLAYER and userToken.actionID != actions.AFK:
userToken.partMatch()
'''
# Update cached stats if our pp changed if we've just submitted a score or we've changed gameMode
if (userToken.actionID == actions.PLAYING or userToken.actionID == actions.MULTIPLAYING) or (userToken.pp != userUtils.getPP(userID, userToken.gameMode)) or (userToken.gameMode != packetData["gameMode"]):
# Always update game mode, or we'll cache stats from the wrong game mode if we've changed it # Always update game mode, or we'll cache stats from the wrong game mode if we've changed it
userToken.gameMode = packetData["gameMode"] userToken.gameMode = packetData["gameMode"]
userToken.updateCachedStats() userToken.updateCachedStats()
# Always update action id, text and md5 # Always update action id, text, md5 and beatmapID
userToken.actionID = packetData["actionID"] userToken.actionID = packetData["actionID"]
userToken.actionText = packetData["actionText"] userToken.actionText = packetData["actionText"]
userToken.actionMd5 = packetData["actionMd5"] userToken.actionMd5 = packetData["actionMd5"]
userToken.actionMods = packetData["actionMods"] userToken.actionMods = packetData["actionMods"]
userToken.beatmapID = packetData["beatmapID"]
# Enqueue our new user panel and stats to us and our spectators # Enqueue our new user panel and stats to us and our spectators
recipients = [userID] recipients = [userToken]
if len(userToken.spectators) > 0: if len(userToken.spectators) > 0:
recipients += userToken.spectators for i in userToken.spectators:
if i in glob.tokens.tokens:
recipients.append(glob.tokens.tokens[i])
for i in recipients: for i in recipients:
if i == userID: if i is not None:
# Save some loops
token = userToken
else:
token = glob.tokens.getTokenFromUserID(i)
if token != None:
# Force our own packet # Force our own packet
force = True if token.userID == userID else False force = True if i == userToken else False
token.enqueue(serverPackets.userPanel(userID, force)) i.enqueue(serverPackets.userPanel(userID, force))
token.enqueue(serverPackets.userStats(userID, force)) i.enqueue(serverPackets.userStats(userID, force))
# Send osu!direct alert if needed
# NOTE: Remove this when osu!direct will be fixed
if userToken.actionID == actions.osuDirect and userToken.osuDirectAlert == False:
userToken.osuDirectAlert = True
chat.sendMessage("FokaBot", userToken.username, "Sup! osu!direct works, but you'll need to update the switcher to have the Download button working. If you didn't update the switcher yet, please do!")
# Console output # Console output
log.info("{} changed action: {} [{}][{}]".format(username, str(userToken.actionID), userToken.actionText, userToken.actionMd5)) log.info("{} changed action: {} [{}][{}][{}]".format(username, str(userToken.actionID), userToken.actionText, userToken.actionMd5, userToken.beatmapID))

View File

@@ -1,7 +1,8 @@
from objects import glob from common.constants import mods
from constants import clientPackets from constants import clientPackets
from constants import matchModModes from constants import matchModModes
from constants import mods from objects import glob
def handle(userToken, packetData): def handle(userToken, packetData):
# Get token data # Get token data
@@ -19,25 +20,24 @@ def handle(userToken, packetData):
# Set slot or match mods according to modType # Set slot or match mods according to modType
if match.matchModMode == matchModModes.freeMod: if match.matchModMode == matchModModes.freeMod:
# Freemod # Freemod
# Host can set global DT/HT # Host can set global DT/HT
if userID == match.hostUserID: if userID == match.hostUserID:
# If host has selected DT/HT and Freemod is enabled, set DT/HT as match mod # If host has selected DT/HT and Freemod is enabled, set DT/HT as match mod
if (packetData["mods"] & mods.DoubleTime) > 0: if (packetData["mods"] & mods.DOUBLETIME) > 0:
match.changeMatchMods(mods.DoubleTime) match.changeMods(mods.DOUBLETIME)
# Nighcore # Nightcore
if (packetData["mods"] & mods.Nightcore) > 0: if (packetData["mods"] & mods.NIGHTCORE) > 0:
match.changeMatchMods(match.mods+mods.Nightcore) match.changeMods(match.mods + mods.NIGHTCORE)
elif (packetData["mods"] & mods.HalfTime) > 0: elif (packetData["mods"] & mods.HALFTIME) > 0:
match.changeMatchMods(mods.HalfTime) match.changeMods(mods.HALFTIME)
else: else:
# No DT/HT, set global mods to 0 (we are in freemod mode) # No DT/HT, set global mods to 0 (we are in freemod mode)
match.changeMatchMods(0) match.changeMods(0)
# Set slot mods # Set slot mods
slotID = match.getUserSlotID(userID) slotID = match.getUserSlotID(userID)
if slotID != None: if slotID is not None:
match.setSlotMods(slotID, packetData["mods"]) match.setSlotMods(slotID, packetData["mods"])
else: else:
# Not freemod, set match mods # Not freemod, set match mods
match.changeMatchMods(packetData["mods"]) match.changeMods(packetData["mods"])

View File

@@ -13,5 +13,9 @@ def handle(userToken, packetData):
# Get our match # Get our match
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
# Host check
if userToken.userID != match.hostUserID:
return
# Update match password # Update match password
match.changePassword(packetData["matchPassword"]) match.changePassword(packetData["matchPassword"])

View File

@@ -1,13 +1,14 @@
from objects import glob import random
from common import generalUtils
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import matchModModes from constants import matchModModes
from helpers import consoleHelper
from constants import bcolors
import random
from constants import matchTeamTypes from constants import matchTeamTypes
from constants import matchTeams from constants import matchTeams
from constants import slotStatuses from constants import slotStatuses
from helpers import logHelper as log from objects import glob
def handle(userToken, packetData): def handle(userToken, packetData):
# Read new settings # Read new settings
@@ -23,6 +24,10 @@ def handle(userToken, packetData):
# Get match object # Get match object
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
# Host check
if userToken.userID != match.hostUserID:
return
# Some dank memes easter egg # Some dank memes easter egg
memeTitles = [ memeTitles = [
"RWC 2020", "RWC 2020",
@@ -55,7 +60,10 @@ def handle(userToken, packetData):
# Update match settings # Update match settings
match.inProgress = packetData["inProgress"] match.inProgress = packetData["inProgress"]
match.matchPassword = packetData["matchPassword"] if packetData["matchPassword"] != "":
match.matchPassword = generalUtils.stringMd5(packetData["matchPassword"])
else:
match.matchPassword = ""
match.beatmapName = packetData["beatmapName"] match.beatmapName = packetData["beatmapName"]
match.beatmapID = packetData["beatmapID"] match.beatmapID = packetData["beatmapID"]
match.hostUserID = packetData["hostUserID"] match.hostUserID = packetData["hostUserID"]
@@ -73,14 +81,14 @@ def handle(userToken, packetData):
# Reset ready if needed # Reset ready if needed
if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5: if oldMods != match.mods or oldBeatmapMD5 != match.beatmapMD5:
for i in range(0,16): for i in range(0,16):
if match.slots[i]["status"] == slotStatuses.ready: if match.slots[i].status == slotStatuses.ready:
match.slots[i]["status"] = slotStatuses.notReady match.slots[i].status = slotStatuses.notReady
# Reset mods if needed # Reset mods if needed
if match.matchModMode == matchModModes.normal: if match.matchModMode == matchModModes.normal:
# Reset slot mods if not freeMods # Reset slot mods if not freeMods
for i in range(0,16): for i in range(0,16):
match.slots[i]["mods"] = 0 match.slots[i].mods = 0
else: else:
# Reset match mods if freemod # Reset match mods if freemod
match.mods = 0 match.mods = 0
@@ -90,21 +98,20 @@ def handle(userToken, packetData):
# Set teams # Set teams
c=0 c=0
for i in range(0,16): for i in range(0,16):
if match.slots[i]["team"] == matchTeams.noTeam: if match.slots[i].team == matchTeams.noTeam:
match.slots[i]["team"] = matchTeams.red if c % 2 == 0 else matchTeams.blue match.slots[i].team = matchTeams.red if c % 2 == 0 else matchTeams.blue
c+=1 c+=1
else: else:
# Reset teams # Reset teams
for i in range(0,16): for i in range(0,16):
match.slots[i]["team"] = matchTeams.noTeam match.slots[i].team = matchTeams.noTeam
# Force no freemods if tag coop # Force no freemods if tag coop
if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs: if match.matchTeamType == matchTeamTypes.tagCoop or match.matchTeamType == matchTeamTypes.tagTeamVs:
match.matchModMode = matchModModes.normal match.matchModMode = matchModModes.normal
# Send updated settings # Send updated settings
match.sendUpdate() match.sendUpdates()
# Console output # Console output
log.info("MPROOM{}: Updated room settings".format(match.matchID)) log.info("MPROOM{}: Updated room settings".format(match.matchID))
#consoleHelper.printColored("> MPROOM{}: DEBUG: Host is {}".format(match.matchID, match.hostUserID), bcolors.PINK)

View File

@@ -1,12 +1,9 @@
from constants import clientPackets from constants import clientPackets
from objects import glob from objects import glob
from helpers import consoleHelper
from constants import bcolors
def handle(userToken, packetData): def handle(userToken, packetData):
# Get usertoken data # Get usertoken data
userID = userToken.userID userID = userToken.userID
username = userToken.username
# Read packet data # Read packet data
packetData = clientPackets.changeSlot(packetData) packetData = clientPackets.changeSlot(packetData)

View File

@@ -1,6 +1,3 @@
"""
Event called when someone parts a channel
"""
from constants import clientPackets from constants import clientPackets
from helpers import chatHelper as chat from helpers import chatHelper as chat

View File

@@ -1,9 +1,9 @@
from constants import serverPackets from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from objects import glob
from events import joinMatchEvent
from constants import exceptions from constants import exceptions
from helpers import logHelper as log from constants import serverPackets
from objects import glob
def handle(userToken, packetData): def handle(userToken, packetData):
try: try:
@@ -25,17 +25,12 @@ def handle(userToken, packetData):
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
# Join that match # Join that match
joinMatchEvent.joinMatch(userToken, matchID, packetData["matchPassword"]) userToken.joinMatch(matchID)
# Give host to match creator # Give host to match creator
match.setHost(userID) match.setHost(userID)
match.sendUpdates()
# Send match create packet to everyone in lobby match.changePassword(packetData["matchPassword"])
for i in glob.matches.usersInLobby:
# Make sure this user is still connected
token = glob.tokens.getTokenFromUserID(i)
if token != None:
token.enqueue(serverPackets.createMatch(matchID))
# Console output # Console output
log.info("MPROOM{}: Room created!".format(matchID)) log.info("MPROOM{}: Room created!".format(matchID))

View File

@@ -1,11 +1,12 @@
from helpers import userHelper from common.log import logUtils as log
from common.ripple import userUtils
from constants import clientPackets from constants import clientPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Friend add packet # Friend add packet
packetData = clientPackets.addRemoveFriend(packetData) packetData = clientPackets.addRemoveFriend(packetData)
userHelper.addFriend(userToken.userID, packetData["friendID"]) userUtils.addFriend(userToken.userID, packetData["friendID"])
# Console output # Console output
log.info("{} have added {} to their friends".format(userToken.username, str(packetData["friendID"]))) log.info("{} have added {} to their friends".format(userToken.username, str(packetData["friendID"])))

View File

@@ -1,11 +1,12 @@
from helpers import userHelper from common.log import logUtils as log
from common.ripple import userUtils
from constants import clientPackets from constants import clientPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Friend remove packet # Friend remove packet
packetData = clientPackets.addRemoveFriend(packetData) packetData = clientPackets.addRemoveFriend(packetData)
userHelper.removeFriend(userToken.userID, packetData["friendID"]) userUtils.removeFriend(userToken.userID, packetData["friendID"])
# Console output # Console output
log.info("{} have removed {} from their friends".format(userToken.username, str(packetData["friendID"]))) log.info("{} have removed {} from their friends".format(userToken.username, str(packetData["friendID"])))

View File

@@ -1,14 +1,14 @@
from common.log import logUtils as log
from constants import serverPackets from constants import serverPackets
from objects import glob from objects import glob
from helpers import logHelper as log
def handle(userToken, _): def handle(userToken, _):
# Get userToken data # Get userToken data
username = userToken.username username = userToken.username
userID = userToken.userID
# Add user to users in lobby # Add user to users in lobby
glob.matches.lobbyUserJoin(userID) userToken.joinStream("lobby")
# Send matches data # Send matches data
for key, _ in glob.matches.matches.items(): for key, _ in glob.matches.matches.items():

View File

@@ -1,32 +1,30 @@
from common import generalUtils
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import exceptions
from constants import serverPackets from constants import serverPackets
from objects import glob from objects import glob
from constants import exceptions
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, packetData): def handle(userToken, packetData):
# read packet data # read packet data
packetData = clientPackets.joinMatch(packetData) packetData = clientPackets.joinMatch(packetData)
matchID = packetData["matchID"]
password = packetData["password"]
# Get match from ID # Get match from ID
joinMatch(userToken, packetData["matchID"], packetData["password"])
def joinMatch(userToken, matchID, password):
try: try:
# TODO: leave other matches
# TODO: Stop spectating
# get usertoken data
userID = userToken.userID
# Make sure the match exists # Make sure the match exists
if matchID not in glob.matches.matches: if matchID not in glob.matches.matches:
raise exceptions.matchNotFoundException return
# Match exists, get object # Match exists, get object
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
# Hash password if needed
#if password != "":
# password = generalUtils.stringMd5(password)
# Check password # Check password
# TODO: Admins can enter every match # TODO: Admins can enter every match
if match.matchPassword != "": if match.matchPassword != "":
@@ -34,24 +32,7 @@ def joinMatch(userToken, matchID, password):
raise exceptions.matchWrongPasswordException raise exceptions.matchWrongPasswordException
# Password is correct, join match # Password is correct, join match
result = match.userJoin(userID)
# Check if we've joined the match successfully
if result == False:
raise exceptions.matchJoinErrorException
# Match joined, set matchID for usertoken
userToken.joinMatch(matchID) userToken.joinMatch(matchID)
# Send packets
userToken.enqueue(serverPackets.matchJoinSuccess(matchID))
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID))
except exceptions.matchNotFoundException:
userToken.enqueue(serverPackets.matchJoinFail())
log.warning("{} has tried to join a mp room, but it doesn't exist".format(userToken.username))
except exceptions.matchWrongPasswordException: except exceptions.matchWrongPasswordException:
userToken.enqueue(serverPackets.matchJoinFail()) userToken.enqueue(serverPackets.matchJoinFail())
log.warning("{} has tried to join a mp room, but he typed the wrong password".format(userToken.username)) log.warning("{} has tried to join a mp room, but he typed the wrong password".format(userToken.username))
except exceptions.matchJoinErrorException:
userToken.enqueue(serverPackets.matchJoinFail())
log.warning("{} has tried to join a mp room, but an error has occured".format(userToken.username))

View File

@@ -1,20 +1,17 @@
from helpers import userHelper
from constants import serverPackets
from constants import exceptions
from objects import glob
from helpers import consoleHelper
from constants import bcolors
from helpers import locationHelper
from helpers import countryHelper
import time
from helpers import generalFunctions
import sys import sys
import time
import traceback import traceback
from helpers import requestHelper
from helpers import discordBotHelper from common.constants import privileges
from helpers import logHelper as log from common.log import logUtils as log
from common.ripple import userUtils
from constants import exceptions
from constants import serverPackets
from helpers import chatHelper as chat from helpers import chatHelper as chat
from constants import privileges from helpers import countryHelper
from helpers import locationHelper
from objects import glob
def handle(tornadoRequest): def handle(tornadoRequest):
# Data to return # Data to return
@@ -37,7 +34,7 @@ def handle(tornadoRequest):
# Make sure loginData is valid # Make sure loginData is valid
if len(loginData) < 3: if len(loginData) < 3:
raise exceptions.haxException() raise exceptions.invalidArgumentsException()
# Get HWID, MAC address and more # Get HWID, MAC address and more
# Structure (new line = "|", already split) # Structure (new line = "|", already split)
@@ -56,22 +53,24 @@ def handle(tornadoRequest):
# Try to get the ID from username # Try to get the ID from username
username = str(loginData[0]) username = str(loginData[0])
userID = userHelper.getID(username) userID = userUtils.getID(username)
if userID == False: if not userID:
# Invalid username # Invalid username
raise exceptions.loginFailedException() raise exceptions.loginFailedException()
if userHelper.checkLogin(userID, loginData[1]) == False: if not userUtils.checkLogin(userID, loginData[1]):
# Invalid password # Invalid password
raise exceptions.loginFailedException() raise exceptions.loginFailedException()
# Make sure we are not banned # Make sure we are not banned or locked
priv = userHelper.getPrivileges(userID) priv = userUtils.getPrivileges(userID)
if userHelper.isBanned(userID) == True and priv & privileges.USER_PENDING_VERIFICATION == 0: if userUtils.isBanned(userID) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
raise exceptions.loginBannedException() raise exceptions.loginBannedException()
if userUtils.isLocked(userID) == True and priv & privileges.USER_PENDING_VERIFICATION == 0:
raise exceptions.loginLockedException()
# 2FA check # 2FA check
if userHelper.check2FA(userID, requestIP) == True: if userUtils.check2FA(userID, requestIP):
log.warning("Need 2FA check for user {}".format(loginData[0])) log.warning("Need 2FA check for user {}".format(loginData[0]))
raise exceptions.need2FAException() raise exceptions.need2FAException()
@@ -79,8 +78,8 @@ def handle(tornadoRequest):
# Verify this user (if pending activation) # Verify this user (if pending activation)
firstLogin = False firstLogin = False
if priv & privileges.USER_PENDING_VERIFICATION > 0 or userHelper.hasVerifiedHardware(userID) == False: if priv & privileges.USER_PENDING_VERIFICATION > 0 or userUtils.hasVerifiedHardware(userID) == False:
if userHelper.verifyUser(userID, clientData) == True: if userUtils.verifyUser(userID, clientData):
# Valid account # Valid account
log.info("Account {} verified successfully!".format(userID)) log.info("Account {} verified successfully!".format(userID))
glob.verifiedCache[str(userID)] = 1 glob.verifiedCache[str(userID)] = 1
@@ -93,27 +92,38 @@ def handle(tornadoRequest):
# Save HWID in db for multiaccount detection # Save HWID in db for multiaccount detection
hwAllowed = userHelper.logHardware(userID, clientData, firstLogin) hwAllowed = userUtils.logHardware(userID, clientData, firstLogin)
# This is false only if HWID is empty # This is false only if HWID is empty
# if HWID is banned, we get restricted so there's no # if HWID is banned, we get restricted so there's no
# need to deny bancho access # need to deny bancho access
if hwAllowed == False: if not hwAllowed:
raise exceptions.haxException() raise exceptions.haxException()
# Log user IP # Log user IP
userHelper.logIP(userID, requestIP) userUtils.logIP(userID, requestIP)
# Delete old tokens for that user and generate a new one # Delete old tokens for that user and generate a new one
glob.tokens.deleteOldTokens(userID) isTournament = "tourney" in osuVersion
responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset) if not isTournament:
glob.tokens.deleteOldTokens(userID)
responseToken = glob.tokens.addToken(userID, requestIP, timeOffset=timeOffset, tournament=isTournament)
responseTokenString = responseToken.token responseTokenString = responseToken.token
# Check restricted mode (and eventually send message) # Check restricted mode (and eventually send message)
responseToken.checkRestricted() responseToken.checkRestricted()
# Send message if donor expires soon
if responseToken.privileges & privileges.USER_DONOR > 0:
expireDate = userUtils.getDonorExpire(responseToken.userID)
if expireDate-int(time.time()) <= 86400*3:
expireDays = round((expireDate-int(time.time()))/86400)
expireIn = "{} days".format(expireDays) if expireDays > 1 else "less than 24 hours"
responseToken.enqueue(serverPackets.notification("Your donor tag expires in {}! When your donor tag expires, you won't have any of the donor privileges, like yellow username, custom badge and discord custom role and username color! If you wish to keep supporting Ripple and you don't want to lose your donor privileges, you can donate again by clicking on 'Support us' on Ripple's website.".format(expireIn)))
# Set silence end UNIX time in token # Set silence end UNIX time in token
responseToken.silenceEndTime = userHelper.getSilenceEnd(userID) responseToken.silenceEndTime = userUtils.getSilenceEnd(userID)
# Get only silence remaining seconds # Get only silence remaining seconds
silenceSeconds = responseToken.getSilenceSecondsLeft() silenceSeconds = responseToken.getSilenceSecondsLeft()
@@ -121,11 +131,14 @@ def handle(tornadoRequest):
# Get supporter/GMT # Get supporter/GMT
userGMT = False userGMT = False
userSupporter = True userSupporter = True
if responseToken.admin == True: userTournament = False
if responseToken.admin:
userGMT = True userGMT = True
if responseToken.privileges & privileges.USER_TOURNAMENT_STAFF > 0:
userTournament = True
# Server restarting check # Server restarting check
if glob.restarting == True: if glob.restarting:
raise exceptions.banchoRestartingException() raise exceptions.banchoRestartingException()
# Send login notification before maintenance message # Send login notification before maintenance message
@@ -133,8 +146,8 @@ def handle(tornadoRequest):
responseToken.enqueue(serverPackets.notification(glob.banchoConf.config["loginNotification"])) responseToken.enqueue(serverPackets.notification(glob.banchoConf.config["loginNotification"]))
# Maintenance check # Maintenance check
if glob.banchoConf.config["banchoMaintenance"] == True: if glob.banchoConf.config["banchoMaintenance"]:
if userGMT == False: if not userGMT:
# We are not mod/admin, delete token, send notification and logout # We are not mod/admin, delete token, send notification and logout
glob.tokens.deleteToken(responseTokenString) glob.tokens.deleteToken(responseTokenString)
raise exceptions.banchoMaintenanceException() raise exceptions.banchoMaintenanceException()
@@ -146,7 +159,7 @@ def handle(tornadoRequest):
responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds)) responseToken.enqueue(serverPackets.silenceEndTime(silenceSeconds))
responseToken.enqueue(serverPackets.userID(userID)) responseToken.enqueue(serverPackets.userID(userID))
responseToken.enqueue(serverPackets.protocolVersion()) responseToken.enqueue(serverPackets.protocolVersion())
responseToken.enqueue(serverPackets.userSupporterGMT(userSupporter, userGMT)) responseToken.enqueue(serverPackets.userSupporterGMT(userSupporter, userGMT, userTournament))
responseToken.enqueue(serverPackets.userPanel(userID, True)) responseToken.enqueue(serverPackets.userPanel(userID, True))
responseToken.enqueue(serverPackets.userStats(userID, True)) responseToken.enqueue(serverPackets.userStats(userID, True))
@@ -158,7 +171,7 @@ def handle(tornadoRequest):
chat.joinChannel(token=responseToken, channel="#announce") chat.joinChannel(token=responseToken, channel="#announce")
# Join admin channel if we are an admin # Join admin channel if we are an admin
if responseToken.admin == True: if responseToken.admin:
chat.joinChannel(token=responseToken, channel="#admin") chat.joinChannel(token=responseToken, channel="#admin")
# Output channels info # Output channels info
@@ -177,7 +190,7 @@ def handle(tornadoRequest):
responseToken.enqueue(serverPackets.onlineUsers()) responseToken.enqueue(serverPackets.onlineUsers())
# Get location and country from ip.zxq.co or database # Get location and country from ip.zxq.co or database
if glob.localize == True: if glob.localize:
# Get location and country from IP # Get location and country from IP
location = locationHelper.getLocation(requestIP) location = locationHelper.getLocation(requestIP)
countryLetters = locationHelper.getCountry(requestIP) countryLetters = locationHelper.getCountry(requestIP)
@@ -187,19 +200,19 @@ def handle(tornadoRequest):
log.warning("Location skipped") log.warning("Location skipped")
location = [0,0] location = [0,0]
countryLetters = "XX" countryLetters = "XX"
country = countryHelper.getCountryID(userHelper.getCountry(userID)) country = countryHelper.getCountryID(userUtils.getCountry(userID))
# Set location and country # Set location and country
responseToken.setLocation(location) responseToken.setLocation(location)
responseToken.setCountry(country) responseToken.setCountry(country)
# Set country in db if user has no country (first bancho login) # Set country in db if user has no country (first bancho login)
if userHelper.getCountry(userID) == "XX": if userUtils.getCountry(userID) == "XX":
userHelper.setCountry(userID, countryLetters) userUtils.setCountry(userID, countryLetters)
# Send to everyone our userpanel if we are not restricted # Send to everyone our userpanel if we are not restricted or tournament
if responseToken.restricted == False: if not responseToken.restricted:
glob.tokens.enqueueAll(serverPackets.userPanel(userID)) glob.streams.broadcast("main", serverPackets.userPanel(userID))
# Set reponse data to right value and reset our queue # Set reponse data to right value and reset our queue
responseData = responseToken.queue responseData = responseToken.queue
@@ -209,7 +222,7 @@ def handle(tornadoRequest):
# (we don't use enqueue because we don't have a token since login has failed) # (we don't use enqueue because we don't have a token since login has failed)
err = True err = True
responseData += serverPackets.loginFailed() responseData += serverPackets.loginFailed()
except exceptions.haxException: except exceptions.invalidArgumentsException:
# Invalid POST data # Invalid POST data
# (we don't use enqueue because we don't have a token since login has failed) # (we don't use enqueue because we don't have a token since login has failed)
err = True err = True
@@ -219,6 +232,10 @@ def handle(tornadoRequest):
# Login banned error packet # Login banned error packet
err = True err = True
responseData += serverPackets.loginBanned() responseData += serverPackets.loginBanned()
except exceptions.loginLockedException:
# Login banned error packet
err = True
responseData += serverPackets.loginLocked()
except exceptions.banchoMaintenanceException: except exceptions.banchoMaintenanceException:
# Bancho is in maintenance mode # Bancho is in maintenance mode
responseData = responseToken.queue responseData = responseToken.queue
@@ -236,7 +253,7 @@ def handle(tornadoRequest):
# (we don't use enqueue because we don't have a token since login has failed) # (we don't use enqueue because we don't have a token since login has failed)
err = True err = True
responseData += serverPackets.forceUpdate() responseData += serverPackets.forceUpdate()
responseData += serverPackets.notification("Hory shitto, your client is TOO old! Nice preistoria! Please turn off the switcher and update it.") responseData += serverPackets.notification("Hory shitto, your client is TOO old! Nice prehistory! Please turn update it from the settings!")
except: except:
log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc())) log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
finally: finally:
@@ -248,4 +265,4 @@ def handle(tornadoRequest):
log.info(msg, "bunker") log.info(msg, "bunker")
# Return token string and data # Return token string and data
return (responseTokenString, responseData) return responseTokenString, responseData

View File

@@ -1,10 +1,10 @@
from objects import glob
from helpers import consoleHelper
from constants import bcolors
from constants import serverPackets
import time import time
from helpers import logHelper as log
from common.log import logUtils as log
from constants import serverPackets
from helpers import chatHelper as chat from helpers import chatHelper as chat
from objects import glob
def handle(userToken, _=None): def handle(userToken, _=None):
# get usertoken data # get usertoken data
@@ -16,24 +16,22 @@ def handle(userToken, _=None):
# the old logout packet will still be in the queue and will be sent to # the old logout packet will still be in the queue and will be sent to
# the server, so we accept logout packets sent at least 5 seconds after login # the server, so we accept logout packets sent at least 5 seconds after login
# if the user logs out before 5 seconds, he will be disconnected later with timeout check # if the user logs out before 5 seconds, he will be disconnected later with timeout check
if int(time.time()-userToken.loginTime) >= 5 or userToken.irc == True: if (int(time.time()-userToken.loginTime) >= 5 or userToken.irc):
# Stop spectating if needed # Stop spectating
# TODO: Call stopSpectatingEvent!!!!!!!!! userToken.stopSpectating()
if userToken.spectating != 0:
# The user was spectating someone # Part matches
spectatorHostToken = glob.tokens.getTokenFromUserID(userToken.spectating) userToken.leaveMatch()
if spectatorHostToken != None:
# The host is still online, send removeSpectator to him
spectatorHostToken.enqueue(serverPackets.removeSpectator(userID))
# Part all joined channels # Part all joined channels
for i in userToken.joinedChannels: for i in userToken.joinedChannels:
chat.partChannel(token=userToken, channel=i) chat.partChannel(token=userToken, channel=i)
# TODO: Lobby left if joined # Leave all joined streams
userToken.leaveAllStreams()
# Enqueue our disconnection to everyone else # Enqueue our disconnection to everyone else
glob.tokens.enqueueAll(serverPackets.userLogout(userID)) glob.streams.broadcast("main", serverPackets.userLogout(userID))
# Disconnect from IRC if needed # Disconnect from IRC if needed
if userToken.irc == True and glob.irc == True: if userToken.irc == True and glob.irc == True:
@@ -43,4 +41,4 @@ def handle(userToken, _=None):
glob.tokens.deleteToken(requestToken) glob.tokens.deleteToken(requestToken)
# Console output # Console output
log.info("{} has been disconnected.".format(username)) log.info("{} has been disconnected. (logout)".format(username))

View File

@@ -1,7 +1,6 @@
from objects import glob from objects import glob
from constants import slotStatuses from constants import slotStatuses
from constants import serverPackets from constants import serverPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Get usertoken data # Get usertoken data
@@ -25,8 +24,4 @@ def handle(userToken, packetData):
slotID = match.getUserSlotID(userID) slotID = match.getUserSlotID(userID)
# Enqueue frames to who's playing # Enqueue frames to who's playing
for i in range(0,16): glob.streams.broadcast(match.playingStreamName, serverPackets.matchFrames(slotID, packetData))
if match.slots[i]["userID"] > -1 and match.slots[i]["status"] == slotStatuses.playing:
token = glob.tokens.getTokenFromUserID(match.slots[i]["userID"])
if token != None:
token.enqueue(serverPackets.matchFrames(slotID, packetData))

View File

@@ -1,3 +1,4 @@
from events import matchBeatmapEvent from events import matchBeatmapEvent
def handle(userToken, packetData): def handle(userToken, packetData):
matchBeatmapEvent.handle(userToken, packetData, True) matchBeatmapEvent.handle(userToken, packetData, True)

View File

@@ -14,6 +14,10 @@ def handle(userToken, packetData):
return return
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
# Host check
if userID != match.hostUserID:
return
# Make sure we aren't locking our slot # Make sure we aren't locking our slot
ourSlot = match.getUserSlotID(userID) ourSlot = match.getUserSlotID(userID)
if packetData["slotID"] == ourSlot: if packetData["slotID"] == ourSlot:

View File

@@ -1,3 +1,4 @@
from events import matchBeatmapEvent from events import matchBeatmapEvent
def handle(userToken, packetData): def handle(userToken, packetData):
matchBeatmapEvent.handle(userToken, packetData, False) matchBeatmapEvent.handle(userToken, packetData, False)

View File

@@ -12,5 +12,5 @@ def handle(userToken, _):
# Get our slotID and change ready status # Get our slotID and change ready status
slotID = match.getUserSlotID(userID) slotID = match.getUserSlotID(userID)
if slotID != None: if slotID is not None:
match.toggleSlotReady(slotID) match.toggleSlotReady(slotID)

View File

@@ -3,7 +3,6 @@ from constants import slotStatuses
from constants import serverPackets from constants import serverPackets
def handle(userToken, _): def handle(userToken, _):
# TODO: Host check
# Get match ID and match object # Get match ID and match object
matchID = userToken.matchID matchID = userToken.matchID
@@ -19,29 +18,8 @@ def handle(userToken, _):
# The match exists, get object # The match exists, get object
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
force = False # TODO: Force thing # Host check
if userToken.userID != match.hostUserID:
# Make sure we have enough players
if (match.countUsers() < 2 or not match.checkTeams()) and not force:
return return
# Change inProgress value match.start()
match.inProgress = True
# Set playing to ready players and set load, skip and complete to False
for i in range(0,16):
if (match.slots[i]["status"] & slotStatuses.ready) > 0:
match.slots[i]["status"] = slotStatuses.playing
match.slots[i]["loaded"] = False
match.slots[i]["skip"] = False
match.slots[i]["complete"] = False
# Send match start packet
for i in range(0,16):
if (match.slots[i]["status"] & slotStatuses.playing) > 0 and match.slots[i]["userID"] != -1:
token = glob.tokens.getTokenFromUserID(match.slots[i]["userID"])
if token != None:
token.enqueue(serverPackets.matchStart(matchID))
# Send updates
match.sendUpdate()

View File

@@ -19,5 +19,9 @@ def handle(userToken, packetData):
# Match exists, get object # Match exists, get object
match = glob.matches.matches[matchID] match = glob.matches.matches[matchID]
# Host check
if userToken.userID != match.hostUserID:
return
# Transfer host # Transfer host
match.transferHost(packetData["slotID"]) match.transferHost(packetData["slotID"])

View File

@@ -1,7 +1,7 @@
from objects import glob from common.log import logUtils as log
from events import channelPartEvent
from helpers import logHelper as log
from helpers import chatHelper as chat from helpers import chatHelper as chat
from objects import glob
def handle(userToken, _): def handle(userToken, _):
# Get usertoken data # Get usertoken data
@@ -9,7 +9,7 @@ def handle(userToken, _):
username = userToken.username username = userToken.username
# Remove user from users in lobby # Remove user from users in lobby
glob.matches.lobbyUserPart(userID) userToken.leaveStream("lobby")
# Part lobby channel # Part lobby channel
chat.partChannel(channel="#lobby", token=userToken, kick=True) chat.partChannel(channel="#lobby", token=userToken, kick=True)

View File

@@ -1,29 +1,2 @@
from objects import glob def handle(userToken, _=None):
from helpers import chatHelper as chat userToken.leaveMatch()
def handle(userToken, _):
# get data from usertoken
userID = userToken.userID
# Get match ID and match object
matchID = userToken.matchID
# Make sure we are in a match
if matchID == -1:
return
# Make sure the match exists
if matchID not in glob.matches.matches:
return
# The match exists, get object
match = glob.matches.matches[matchID]
# Set slot to free
match.userLeft(userID)
# Part #multiplayer channel
#chat.partChannel(token=userToken, channel="#multi_{}".format(matchID), kick=True)
# Set usertoken match to -1
userToken.partMatch()

View File

@@ -1,11 +1,7 @@
from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from helpers import userHelper
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
log.debug("Requested status update")
# Update cache and send new stats # Update cache and send new stats
userToken.updateCachedStats() userToken.updateCachedStats()
userToken.enqueue(serverPackets.userStats(userToken.userID)) userToken.enqueue(serverPackets.userStats(userToken.userID))

View File

@@ -1,6 +1,7 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# get token data # get token data

View File

@@ -7,7 +7,8 @@ def handle(userToken, packetData):
userID = userToken.userID userID = userToken.userID
# Send spectator frames to every spectator # Send spectator frames to every spectator
for i in userToken.spectators: glob.streams.broadcast("spect/{}".format(userID), serverPackets.spectatorFrames(packetData[7:]))
'''for i in userToken.spectators:
# Send to every user but host # Send to every user but host
if i != userID: if i != userID:
try: try:
@@ -15,7 +16,7 @@ def handle(userToken, packetData):
spectatorToken = glob.tokens.getTokenFromUserID(i) spectatorToken = glob.tokens.getTokenFromUserID(i)
# Make sure the token exists # Make sure the token exists
if spectatorToken == None: if spectatorToken is None:
raise exceptions.stopSpectating raise exceptions.stopSpectating
# Make sure this user is spectating us # Make sure this user is spectating us
@@ -27,4 +28,4 @@ def handle(userToken, packetData):
except exceptions.stopSpectating: except exceptions.stopSpectating:
# Remove this user from spectators # Remove this user from spectators
userToken.removeSpectator(i) userToken.removeSpectator(i)
userToken.enqueue(serverPackets.removeSpectator(i)) userToken.enqueue(serverPackets.removeSpectator(i))'''

View File

@@ -1,56 +1,20 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import serverPackets
from constants import exceptions from constants import exceptions
from objects import glob from objects import glob
from helpers import userHelper
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, packetData): def handle(userToken, packetData):
try: try:
# Get usertoken data
userID = userToken.userID
username = userToken.username
# Start spectating packet # Start spectating packet
packetData = clientPackets.startSpectating(packetData) packetData = clientPackets.startSpectating(packetData)
# Stop spectating old user if needed
if userToken.spectating != 0:
oldTargetToken = glob.tokens.getTokenFromUserID(userToken.spectating)
oldTargetToken.enqueue(serverPackets.removeSpectator(userID))
userToken.stopSpectating()
# Start spectating new user
userToken.startSpectating(packetData["userID"])
# Get host token # Get host token
targetToken = glob.tokens.getTokenFromUserID(packetData["userID"]) targetToken = glob.tokens.getTokenFromUserID(packetData["userID"])
if targetToken == None: if targetToken is None:
raise exceptions.tokenNotFoundException raise exceptions.tokenNotFoundException
# Add us to host's spectators # Start spectating new user
targetToken.addSpectator(userID) userToken.startSpectating(targetToken)
# Send spectator join packet to host
targetToken.enqueue(serverPackets.addSpectator(userID))
# Create and join #spectator (#spect_userid) channel
glob.channels.addTempChannel("#spect_{}".format(targetToken.userID))
chat.joinChannel(token=userToken, channel="#spect_{}".format(targetToken.userID))
if len(targetToken.spectators) == 1:
# First spectator, send #spectator join to host too
chat.joinChannel(token=targetToken, channel="#spect_{}".format(targetToken.userID))
# send fellowSpectatorJoined to all spectators
for spec in targetToken.spectators:
if spec is not userID:
c = glob.tokens.getTokenFromUserID(spec)
userToken.enqueue(serverPackets.fellowSpectatorJoined(c.userID))
c.enqueue(serverPackets.fellowSpectatorJoined(userID))
# Console output
log.info("{} are spectating {}".format(username, userHelper.getUsername(packetData["userID"])))
except exceptions.tokenNotFoundException: except exceptions.tokenNotFoundException:
# Stop spectating if token not found # Stop spectating if token not found
log.warning("Spectator start: token not found") log.warning("Spectator start: token not found")

View File

@@ -1,37 +1,2 @@
from objects import glob def handle(userToken, _=None):
from constants import serverPackets userToken.stopSpectating()
from constants import exceptions
from helpers import logHelper as log
from helpers import chatHelper as chat
def handle(userToken, _):
try:
# get user token data
userID = userToken.userID
username = userToken.username
# Remove our userID from host's spectators
target = userToken.spectating
targetToken = glob.tokens.getTokenFromUserID(target)
if targetToken == None:
raise exceptions.tokenNotFoundException
targetToken.removeSpectator(userID)
# Part #spectator channel
chat.partChannel(token=userToken, channel="#spect_{}".format(target))
# Send the spectator left packet to host
targetToken.enqueue(serverPackets.removeSpectator(userID))
for c in targetToken.spectators:
spec = glob.tokens.getTokenFromUserID(c)
spec.enqueue(serverPackets.fellowSpectatorLeft(userID))
#targetToken.enqueue(serverPackets.fellowSpectatorLeft(userID))
# Console output
log.info("{} are no longer spectating {}".format(username, target))
except exceptions.tokenNotFoundException:
log.warning("Spectator stop: token not found")
finally:
# Set our spectating user to 0
userToken.stopSpectating()

View File

@@ -0,0 +1,11 @@
from constants import clientPackets
from objects import glob
from helpers import chatHelper as chat
def handle(userToken, packetData):
packetData = clientPackets.tournamentJoinMatchChannel(packetData)
matchID = packetData["matchID"]
if matchID not in glob.matches.matches:
return
userToken.matchID = matchID
chat.joinChannel(token=userToken, channel="#multi_{}".format(matchID))

View File

@@ -0,0 +1,11 @@
from constants import clientPackets
from objects import glob
from helpers import chatHelper as chat
def handle(userToken, packetData):
packetData = clientPackets.tournamentLeaveMatchChannel(packetData)
matchID = packetData["matchID"]
if matchID not in glob.matches.matches:
return
chat.partChannel(token=userToken, channel="#multi_{}".format(matchID))
userToken.matchID = 0

View File

@@ -0,0 +1,10 @@
from constants import clientPackets
from constants import serverPackets
from objects import glob
def handle(userToken, packetData):
packetData = clientPackets.tournamentMatchInfoRequest(packetData)
matchID = packetData["matchID"]
if matchID not in glob.matches.matches:
return
userToken.enqueue(glob.matches.matches[matchID].matchDataCache)

View File

@@ -1,6 +1,7 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Read userIDs list # Read userIDs list

View File

@@ -1,6 +1,7 @@
from common.log import logUtils as log
from constants import clientPackets from constants import clientPackets
from constants import serverPackets from constants import serverPackets
from helpers import logHelper as log
def handle(userToken, packetData): def handle(userToken, packetData):
# Read userIDs list # Read userIDs list

View File

@@ -1,17 +1,19 @@
from helpers import requestHelper
from constants import exceptions
import json import json
from objects import glob
from helpers import chatHelper
from helpers import logHelper as log
class handler(requestHelper.asyncRequestHandler): from common.log import logUtils as log
from common.web import requestsManager
from constants import exceptions
from helpers import chatHelper
from objects import glob
class handler(requestsManager.asyncRequestHandler):
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if requestHelper.checkArguments(self.request.arguments, ["k", "to", "msg"]) == False: if not requestsManager.checkArguments(self.request.arguments, ["k", "to", "msg"]):
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Check ci key # Check ci key

View File

@@ -1,23 +1,37 @@
from helpers import requestHelper
from constants import exceptions
import json import json
from common.web import requestsManager
from constants import exceptions
from objects import glob from objects import glob
class handler(requestHelper.asyncRequestHandler):
class handler(requestsManager.asyncRequestHandler):
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if requestHelper.checkArguments(self.request.arguments, ["u"]) == False: if "u" not in self.request.arguments and "id" not in self.request.arguments:
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Get online staus # Get online staus
username = self.get_argument("u") username = None
if username == None: userID = None
if "u" in self.request.arguments:
username = self.get_argument("u")
else:
try:
userID = int(self.get_argument("id"))
except:
raise exceptions.invalidArgumentsException()
if username is None and userID is None:
data["result"] = False data["result"] = False
else: else:
data["result"] = True if glob.tokens.getTokenFromUsername(username) != None else False if username is not None:
data["result"] = True if glob.tokens.getTokenFromUsername(username) is not None else False
else:
data["result"] = True if glob.tokens.getTokenFromUserID(userID) is not None else False
# Status code and message # Status code and message
statusCode = 200 statusCode = 200

View File

@@ -1,8 +1,10 @@
from helpers import requestHelper
import json import json
from common.web import requestsManager
from objects import glob from objects import glob
class handler(requestHelper.asyncRequestHandler):
class handler(requestsManager.asyncRequestHandler):
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}

View File

@@ -1,8 +1,10 @@
from helpers import requestHelper
import json import json
from common.web import requestsManager
from objects import glob from objects import glob
class handler(requestHelper.asyncRequestHandler):
class handler(requestsManager.asyncRequestHandler):
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}

View File

@@ -1,16 +1,17 @@
from helpers import requestHelper
from helpers import logHelper as log
import json import json
from objects import glob
from constants import exceptions
class handler(requestHelper.asyncRequestHandler): from common.web import requestsManager
from constants import exceptions
from objects import glob
class handler(requestsManager.asyncRequestHandler):
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if requestHelper.checkArguments(self.request.arguments, ["u"]) == False: if not requestsManager.checkArguments(self.request.arguments, ["u"]):
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Get userID and its verified cache thing # Get userID and its verified cache thing

View File

@@ -1,17 +1,19 @@
from helpers import requestHelper
from constants import exceptions
import json import json
from objects import glob
from helpers import systemHelper
from helpers import logHelper as log
class handler(requestHelper.asyncRequestHandler): from common.log import logUtils as log
from common.web import requestsManager
from constants import exceptions
from helpers import systemHelper
from objects import glob
class handler(requestsManager.asyncRequestHandler):
def asyncGet(self): def asyncGet(self):
statusCode = 400 statusCode = 400
data = {"message": "unknown error"} data = {"message": "unknown error"}
try: try:
# Check arguments # Check arguments
if requestHelper.checkArguments(self.request.arguments, ["k"]) == False: if not requestsManager.checkArguments(self.request.arguments, ["k"]):
raise exceptions.invalidArgumentsException() raise exceptions.invalidArgumentsException()
# Check ci key # Check ci key
@@ -33,7 +35,5 @@ class handler(requestHelper.asyncRequestHandler):
data["status"] = statusCode data["status"] = statusCode
# Send response # Send response
#self.clear()
self.write(json.dumps(data)) self.write(json.dumps(data))
self.set_status(statusCode) self.set_status(statusCode)
#self.finish(json.dumps(data))

25
handlers/heavyHandler.py Normal file
View File

@@ -0,0 +1,25 @@
import tornado.gen
import tornado.web
from common.web import requestsManager
from objects import glob
import time
class handler(requestsManager.asyncRequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
def asyncGet(self):
if not glob.debug:
self.write("Nope")
return
time.sleep(0.5)
self.write("meemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeemmeem")
self.set_status(200)
self.add_header("cho-token", "tua madre")
self.add_header("cho-protocol", "19")
self.add_header("Connection", "keep-alive")
self.add_header("Keep-Alive", "timeout=5, max=100")
self.add_header("Content-Type", "text/html; charset=UTF-8")
#glob.db.fetchAll("SELECT SQL_NO_CACHE * FROM beatmaps")
#glob.db.fetchAll("SELECT SQL_NO_CACHE * FROM users")
#glob.db.fetchAll("SELECT SQL_NO_CACHE * FROM scores")
#self.write("ibmd")

View File

@@ -1,66 +1,70 @@
import datetime import datetime
import gzip import gzip
from helpers import requestHelper
from objects import glob
from constants import exceptions
from constants import packetIDs
from helpers import packetHelper
from constants import serverPackets
from events import sendPublicMessageEvent
from events import sendPrivateMessageEvent
from events import channelJoinEvent
from events import channelPartEvent
from events import changeActionEvent
from events import cantSpectateEvent
from events import startSpectatingEvent
from events import stopSpectatingEvent
from events import spectateFramesEvent
from events import friendAddEvent
from events import friendRemoveEvent
from events import logoutEvent
from events import loginEvent
from events import setAwayMessageEvent
from events import joinLobbyEvent
from events import createMatchEvent
from events import partLobbyEvent
from events import changeSlotEvent
from events import joinMatchEvent
from events import partMatchEvent
from events import changeMatchSettingsEvent
from events import changeMatchPasswordEvent
from events import changeMatchModsEvent
from events import matchReadyEvent
from events import matchLockEvent
from events import matchStartEvent
from events import matchPlayerLoadEvent
from events import matchSkipEvent
from events import matchFramesEvent
from events import matchCompleteEvent
from events import matchNoBeatmapEvent
from events import matchHasBeatmapEvent
from events import matchTransferHostEvent
from events import matchFailedEvent
from events import matchInviteEvent
from events import matchChangeTeamEvent
from events import userStatsRequestEvent
from events import requestStatusUpdateEvent
from events import userPanelRequestEvent
# Exception tracking
import tornado.web
import tornado.gen
import sys import sys
import traceback import traceback
from raven.contrib.tornado import SentryMixin
from helpers import logHelper as log
class handler(SentryMixin, requestHelper.asyncRequestHandler): import tornado.gen
import tornado.web
from raven.contrib.tornado import SentryMixin
from common.log import logUtils as log
from common.web import requestsManager
from constants import exceptions
from constants import packetIDs
from constants import serverPackets
from events import cantSpectateEvent
from events import changeActionEvent
from events import changeMatchModsEvent
from events import changeMatchPasswordEvent
from events import changeMatchSettingsEvent
from events import changeSlotEvent
from events import channelJoinEvent
from events import channelPartEvent
from events import createMatchEvent
from events import friendAddEvent
from events import friendRemoveEvent
from events import joinLobbyEvent
from events import joinMatchEvent
from events import loginEvent
from events import logoutEvent
from events import matchChangeTeamEvent
from events import matchCompleteEvent
from events import matchFailedEvent
from events import matchFramesEvent
from events import matchHasBeatmapEvent
from events import matchInviteEvent
from events import matchLockEvent
from events import matchNoBeatmapEvent
from events import matchPlayerLoadEvent
from events import matchReadyEvent
from events import matchSkipEvent
from events import matchStartEvent
from events import matchTransferHostEvent
from events import partLobbyEvent
from events import partMatchEvent
from events import requestStatusUpdateEvent
from events import sendPrivateMessageEvent
from events import sendPublicMessageEvent
from events import setAwayMessageEvent
from events import spectateFramesEvent
from events import startSpectatingEvent
from events import stopSpectatingEvent
from events import userPanelRequestEvent
from events import userStatsRequestEvent
from events import tournamentMatchInfoRequestEvent
from events import tournamentJoinMatchChannelEvent
from events import tournamentLeaveMatchChannelEvent
from helpers import packetHelper
from objects import glob
class handler(SentryMixin, requestsManager.asyncRequestHandler):
@tornado.web.asynchronous @tornado.web.asynchronous
@tornado.gen.engine @tornado.gen.engine
def asyncPost(self): def asyncPost(self):
try: try:
# Track time if needed # Track time if needed
if glob.outputRequestTime == True: if glob.outputRequestTime:
# Start time # Start time
st = datetime.datetime.now() st = datetime.datetime.now()
@@ -72,7 +76,7 @@ class handler(SentryMixin, requestHelper.asyncRequestHandler):
responseTokenString = "ayy" responseTokenString = "ayy"
responseData = bytes() responseData = bytes()
if requestTokenString == None: if requestTokenString is None:
# No token, first request. Handle login. # No token, first request. Handle login.
responseTokenString, responseData = loginEvent.handle(self) responseTokenString, responseData = loginEvent.handle(self)
else: else:
@@ -155,6 +159,10 @@ class handler(SentryMixin, requestHelper.asyncRequestHandler):
packetIDs.client_matchFailed: handleEvent(matchFailedEvent), packetIDs.client_matchFailed: handleEvent(matchFailedEvent),
packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent), packetIDs.client_matchChangeTeam: handleEvent(matchChangeTeamEvent),
packetIDs.client_invite: handleEvent(matchInviteEvent), packetIDs.client_invite: handleEvent(matchInviteEvent),
packetIDs.client_tournamentMatchInfoRequest: handleEvent(tournamentMatchInfoRequestEvent),
packetIDs.client_tournamentJoinMatchChannel: handleEvent(tournamentJoinMatchChannelEvent),
packetIDs.client_tournamentLeaveMatchChannel: handleEvent(tournamentLeaveMatchChannelEvent),
} }
# Packets processed if in restricted mode. # Packets processed if in restricted mode.
@@ -187,9 +195,6 @@ class handler(SentryMixin, requestHelper.asyncRequestHandler):
responseTokenString = userToken.token responseTokenString = userToken.token
responseData = userToken.queue responseData = userToken.queue
userToken.resetQueue() userToken.resetQueue()
# Update ping time for timeout
userToken.updatePingTime()
except exceptions.tokenNotFoundException: except exceptions.tokenNotFoundException:
# Token not found. Disconnect that user # Token not found. Disconnect that user
responseData = serverPackets.loginError() responseData = serverPackets.loginError()
@@ -198,10 +203,13 @@ class handler(SentryMixin, requestHelper.asyncRequestHandler):
log.info("{} has been disconnected (invalid token)".format(requestTokenString)) log.info("{} has been disconnected (invalid token)".format(requestTokenString))
finally: finally:
# Unlock token # Unlock token
if userToken != None: if userToken is not None:
# Update ping time for timeout
userToken.updatePingTime()
# Release token lock
userToken.lock.release() userToken.lock.release()
if glob.outputRequestTime == True: if glob.outputRequestTime:
# End time # End time
et = datetime.datetime.now() et = datetime.datetime.now()
@@ -211,7 +219,7 @@ class handler(SentryMixin, requestHelper.asyncRequestHandler):
# Send server's response to client # Send server's response to client
# We don't use token object because we might not have a token (failed login) # We don't use token object because we might not have a token (failed login)
if glob.gzip == True: if glob.gzip:
# First, write the gzipped response # First, write the gzipped response
self.write(gzip.compress(responseData, int(glob.conf.config["server"]["gziplevel"]))) self.write(gzip.compress(responseData, int(glob.conf.config["server"]["gziplevel"])))
@@ -226,8 +234,8 @@ class handler(SentryMixin, requestHelper.asyncRequestHandler):
self.set_status(200) self.set_status(200)
self.add_header("cho-token", responseTokenString) self.add_header("cho-token", responseTokenString)
self.add_header("cho-protocol", "19") self.add_header("cho-protocol", "19")
#self.add_header("Keep-Alive", "timeout=5, max=100") self.add_header("Connection", "keep-alive")
#self.add_header("Connection", "keep-alive") self.add_header("Keep-Alive", "timeout=5, max=100")
self.add_header("Content-Type", "text/html; charset=UTF-8") self.add_header("Content-Type", "text/html; charset=UTF-8")
except: except:
log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc())) log.error("Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
@@ -260,5 +268,3 @@ class handler(SentryMixin, requestHelper.asyncRequestHandler):
html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>" html += "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^<br>"
html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.<br><br><i>&copy; Ripple team, 2016</i></pre></body></html>" html += "</marquee><br><strike>reverse engineering a protocol impossible to reverse engineer since always</strike><br>we are actually reverse engineering bancho successfully. for the third time.<br><br><i>&copy; Ripple team, 2016</i></pre></body></html>"
self.write(html) self.write(html)
#yield tornado.gen.Task(self.captureMessage, "test")
#self.finish()

View File

@@ -1,52 +0,0 @@
"""
WIP feature that will come in the future.
Don't import
"""
import flask
from objects import glob
from constants import exceptions
@app.route("/api/online-users-count")
def APIonlineUsersCount():
return flask.jsonify({"count" : len(glob.tokens.tokens)-1})
@app.route("/api/user-info")
def APIonlineUsers():
resp = {}
try:
u = flask.request.args.get('u')
# Username/userID
if u.isdigit():
u = int(u)
else:
u = userHelper.getID(u)
if u == None:
raise exceptions.userNotFoundException
# Make sure this user is online
userToken = glob.tokens.getTokenFromUserID(u)
if userToken == None:
raise exceptions.tokenNotFoundException
# Build response dictionary
resp["response"] = "1"
resp[userToken.username] = {
"userID" : userToken.userID,
"actionID" : userToken.actionID,
"actionText" : userToken.actionText,
"actionMd5" : userToken.actionMd5,
"actionMods": userToken.actionMods,
"gameMode": userToken.gameMode,
"country": countryHelper.getCountryLetters(userToken.country),
"position": userToken.location,
"spectating": userToken.spectating,
"spectators": userToken.spectators
}
except exceptions.userNotFoundException:
resp["response"] = "-1"
except exceptions.tokenNotFoundException:
resp["response"] = "-2"
finally:
return flask.jsonify(resp)

View File

@@ -1,12 +1,12 @@
from objects import glob from common.log import logUtils as log
from helpers import logHelper as log from common.ripple import userUtils
from constants import exceptions from constants import exceptions
from constants import serverPackets
from objects import fokabot
from helpers import discordBotHelper
from helpers import userHelper
from events import logoutEvent
from constants import messageTemplates from constants import messageTemplates
from constants import serverPackets
from events import logoutEvent
from objects import fokabot
from objects import glob
def joinChannel(userID = 0, channel = "", token = None, toIRC = True): def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
""" """
@@ -23,10 +23,10 @@ def joinChannel(userID = 0, channel = "", token = None, toIRC = True):
""" """
try: try:
# Get token if not defined # Get token if not defined
if token == None: if token is None:
token = glob.tokens.getTokenFromUserID(userID) token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists # Make sure the token exists
if token == None: if token is None:
raise exceptions.userNotFoundException raise exceptions.userNotFoundException
else: else:
token = token token = token
@@ -89,10 +89,10 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
""" """
try: try:
# Get token if not defined # Get token if not defined
if token == None: if token is None:
token = glob.tokens.getTokenFromUserID(userID) token = glob.tokens.getTokenFromUserID(userID)
# Make sure the token exists # Make sure the token exists
if token == None: if token is None:
raise exceptions.userNotFoundException raise exceptions.userNotFoundException
else: else:
token = token token = token
@@ -105,10 +105,10 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
# (toclient is used clientwise for #multiplayer and #spectator channels) # (toclient is used clientwise for #multiplayer and #spectator channels)
channelClient = channel channelClient = channel
if channel == "#spectator": if channel == "#spectator":
if token.spectating == 0: if token.spectating is None:
s = userID s = userID
else: else:
s = token.spectating s = token.spectatingUserID
channel = "#spect_{}".format(s) channel = "#spect_{}".format(s)
elif channel == "#multiplayer": elif channel == "#multiplayer":
channel = "#multi_{}".format(token.matchID) channel = "#multi_{}".format(token.matchID)
@@ -128,7 +128,7 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
# Force close tab if needed # Force close tab if needed
# NOTE: Maybe always needed, will check later # NOTE: Maybe always needed, will check later
if kick == True: if kick:
token.enqueue(serverPackets.channelKicked(channelClient)) token.enqueue(serverPackets.channelKicked(channelClient))
# IRC part # IRC part
@@ -147,9 +147,6 @@ def partChannel(userID = 0, channel = "", token = None, toIRC = True, kick = Fal
log.warning("User not connected to IRC/Bancho") log.warning("User not connected to IRC/Bancho")
return 442 # idk return 442 # idk
def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True): def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
""" """
Send a message to osu!bancho and IRC server Send a message to osu!bancho and IRC server
@@ -167,9 +164,9 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
try: try:
tokenString = "" tokenString = ""
# Get token object if not passed # Get token object if not passed
if token == None: if token is None:
token = glob.tokens.getTokenFromUsername(fro) token = glob.tokens.getTokenFromUsername(fro)
if token == None: if token is None:
raise exceptions.userNotFoundException raise exceptions.userNotFoundException
else: else:
# token object alredy passed, get its string and its username (fro) # token object alredy passed, get its string and its username (fro)
@@ -179,24 +176,27 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
# Set some variables # Set some variables
userID = token.userID userID = token.userID
username = token.username username = token.username
recipients = []
# Make sure this is not a tournament client
if token.tournament:
raise exceptions.userTournamentException()
# Make sure the user is not in restricted mode # Make sure the user is not in restricted mode
if token.restricted == True: if token.restricted:
raise exceptions.userRestrictedException raise exceptions.userRestrictedException()
# Make sure the user is not silenced # Make sure the user is not silenced
if token.isSilenced() == True: if token.isSilenced():
raise exceptions.userSilencedException raise exceptions.userSilencedException()
# Determine internal name if needed # Determine internal name if needed
# (toclient is used clientwise for #multiplayer and #spectator channels) # (toclient is used clientwise for #multiplayer and #spectator channels)
toClient = to toClient = to
if to == "#spectator": if to == "#spectator":
if token.spectating == 0: if token.spectating is None:
s = userID s = userID
else: else:
s = token.spectating s = token.spectatingUserID
to = "#spect_{}".format(s) to = "#spect_{}".format(s)
elif to == "#multiplayer": elif to == "#multiplayer":
to = "#multi_{}".format(token.matchID) to = "#multi_{}".format(token.matchID)
@@ -216,7 +216,7 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
# Send the message # Send the message
isChannel = to.startswith("#") isChannel = to.startswith("#")
if isChannel == True: if isChannel:
# CHANNEL # CHANNEL
# Make sure the channel exists # Make sure the channel exists
if to not in glob.channels.channels: if to not in glob.channels.channels:
@@ -243,12 +243,16 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
# USER # USER
# Make sure recipient user is connected # Make sure recipient user is connected
recipientToken = glob.tokens.getTokenFromUsername(to) recipientToken = glob.tokens.getTokenFromUsername(to)
if recipientToken == None: if recipientToken is None:
raise exceptions.userNotFoundException raise exceptions.userNotFoundException
# Make sure the recipient is not a tournament client
if recipientToken.tournament:
raise exceptions.userTournamentException()
# Make sure the recipient is not restricted or we are FokaBot # Make sure the recipient is not restricted or we are FokaBot
if recipientToken.restricted == True and fro.lower() != "fokabot": if recipientToken.restricted == True and fro.lower() != "fokabot":
raise exceptions.userRestrictedException raise exceptions.userRestrictedException()
# TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend # TODO: Make sure the recipient has not disabled PMs for non-friends or he's our friend
@@ -270,13 +274,13 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
# Fokabot message # Fokabot message
if isChannel == True or to.lower() == "fokabot": if isChannel == True or to.lower() == "fokabot":
fokaMessage = fokabot.fokabotResponse(username, to, message) fokaMessage = fokabot.fokabotResponse(username, to, message)
if fokaMessage != False: if fokaMessage:
sendMessage("FokaBot", to if isChannel else fro, fokaMessage) sendMessage("FokaBot", to if isChannel else fro, fokaMessage)
# File and discord logs (public chat only) # File and discord logs (public chat only)
if to.startswith("#") == True: if to.startswith("#"):
log.chat("{fro} @ {to}: {message}".format(fro=username, to=to, message=str(message.encode("utf-8")))) log.chat("{fro} @ {to}: {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))))
discordBotHelper.sendChatlog("**{fro} @ {to}:** {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))[2:-1])) glob.schiavo.sendChatlog("**{fro} @ {to}:** {message}".format(fro=username, to=to, message=str(message.encode("utf-8"))[2:-1]))
return 0 return 0
except exceptions.userSilencedException: except exceptions.userSilencedException:
token.enqueue(serverPackets.silenceEndTime(token.getSilenceSecondsLeft())) token.enqueue(serverPackets.silenceEndTime(token.getSilenceSecondsLeft()))
@@ -294,34 +298,53 @@ def sendMessage(fro = "", to = "", message = "", token = None, toIRC = True):
except exceptions.userRestrictedException: except exceptions.userRestrictedException:
log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(username, to)) log.warning("{} tried to send a message {}, but the recipient is in restricted mode".format(username, to))
return 404 return 404
except exceptions.userTournamentException:
log.warning("{} tried to send a message {}, but the recipient is a tournament client".format(username, to))
return 404
except exceptions.userNotFoundException: except exceptions.userNotFoundException:
log.warning("User not connected to IRC/Bancho") log.warning("User not connected to IRC/Bancho")
return 401 return 401
""" IRC-Bancho Connect/Disconnect/Join/Part interfaces""" """ IRC-Bancho Connect/Disconnect/Join/Part interfaces"""
def fixUsernameForBancho(username):
# If there are no spaces or underscores in the name
# return it
if " " not in username and "_" not in username:
return username
# Exact match first
result = glob.db.fetch("SELECT id FROM users WHERE username = %s LIMIT 1", [username])
if result is not None:
return username
# Username not found, replace _ with space
return username.replace("_", " ")
def fixUsernameForIRC(username):
return username.replace(" ", "_")
def IRCConnect(username): def IRCConnect(username):
userID = userHelper.getID(username) userID = userUtils.getID(username)
if userID == False: if not userID:
log.warning("{} doesn't exist".format(username)) log.warning("{} doesn't exist".format(username))
return return
glob.tokens.deleteOldTokens(userID) glob.tokens.deleteOldTokens(userID)
glob.tokens.addToken(userID, irc=True) glob.tokens.addToken(userID, irc=True)
glob.tokens.enqueueAll(serverPackets.userPanel(userID)) glob.streams.broadcast("main", serverPackets.userPanel(userID))
log.info("{} logged in from IRC".format(username)) log.info("{} logged in from IRC".format(username))
def IRCDisconnect(username): def IRCDisconnect(username):
token = glob.tokens.getTokenFromUsername(username) token = glob.tokens.getTokenFromUsername(username)
if token == None: if token is None:
log.warning("{} doesn't exist".format(username)) log.warning("{} doesn't exist".format(username))
return return
logoutEvent.handle(token) logoutEvent.handle(token)
log.info("{} disconnected from IRC".format(username)) log.info("{} disconnected from IRC".format(username))
def IRCJoinChannel(username, channel): def IRCJoinChannel(username, channel):
userID = userHelper.getID(username) userID = userUtils.getID(username)
if userID == False: if not userID:
log.warning("{} doesn't exist".format(username)) log.warning("{} doesn't exist".format(username))
return return
# NOTE: This should have also `toIRC` = False` tho, # NOTE: This should have also `toIRC` = False` tho,
@@ -330,8 +353,8 @@ def IRCJoinChannel(username, channel):
return joinChannel(userID, channel) return joinChannel(userID, channel)
def IRCPartChannel(username, channel): def IRCPartChannel(username, channel):
userID = userHelper.getID(username) userID = userUtils.getID(username)
if userID == False: if not userID:
log.warning("{} doesn't exist".format(username)) log.warning("{} doesn't exist".format(username))
return return
return partChannel(userID, channel) return partChannel(userID, channel)

View File

@@ -2,26 +2,16 @@ import os
import configparser import configparser
class config: class config:
"""
config.ini object
config -- list with ini data
default -- if true, we have generated a default config.ini
"""
config = configparser.ConfigParser()
fileName = "" # config filename
default = True
# Check if config.ini exists and load/generate it # Check if config.ini exists and load/generate it
def __init__(self, __file): def __init__(self, file):
""" """
Initialize a config object Initialize a config object
__file -- filename file -- filename
""" """
self.config = configparser.ConfigParser()
self.fileName = __file self.default = True
self.fileName = file
if os.path.isfile(self.fileName): if os.path.isfile(self.fileName):
# config.ini found, load it # config.ini found, load it
self.config.read(self.fileName) self.config.read(self.fileName)
@@ -39,7 +29,6 @@ class config:
return -- True if valid, False if not return -- True if valid, False if not
""" """
try: try:
# Try to get all the required keys # Try to get all the required keys
self.config.get("db","host") self.config.get("db","host")
@@ -49,11 +38,15 @@ class config:
self.config.get("db","workers") self.config.get("db","workers")
self.config.get("server","port") self.config.get("server","port")
self.config.get("server","threads")
self.config.get("server","gzip") self.config.get("server","gzip")
self.config.get("server","gziplevel") self.config.get("server","gziplevel")
self.config.get("server","cikey") self.config.get("server","cikey")
self.config.get("server","cloudflare") self.config.get("server","cloudflare")
self.config.get("mirror","apiurl")
self.config.get("mirror","apikey")
self.config.get("debug","enable") self.config.get("debug","enable")
self.config.get("debug","packets") self.config.get("debug","packets")
self.config.get("debug","time") self.config.get("debug","time")
@@ -66,8 +59,13 @@ class config:
self.config.get("discord","boturl") self.config.get("discord","boturl")
self.config.get("discord","devgroup") self.config.get("discord","devgroup")
self.config.get("datadog", "enable")
self.config.get("datadog", "apikey")
self.config.get("datadog", "appkey")
self.config.get("irc","enable") self.config.get("irc","enable")
self.config.get("irc","port") self.config.get("irc","port")
self.config.get("irc","hostname")
self.config.get("localize","enable") self.config.get("localize","enable")
self.config.get("localize","ipapiurl") self.config.get("localize","ipapiurl")
@@ -75,11 +73,10 @@ class config:
except: except:
return False return False
# Generate a default config.ini
def generateDefaultConfig(self): def generateDefaultConfig(self):
"""Open and set default keys for that config file""" """
Open and set default keys for that config file
"""
# Open config.ini in write mode # Open config.ini in write mode
f = open(self.fileName, "w") f = open(self.fileName, "w")
@@ -93,11 +90,16 @@ class config:
self.config.add_section("server") self.config.add_section("server")
self.config.set("server", "port", "5001") self.config.set("server", "port", "5001")
self.config.set("server", "threads", "16")
self.config.set("server", "gzip", "1") self.config.set("server", "gzip", "1")
self.config.set("server", "gziplevel", "6") self.config.set("server", "gziplevel", "6")
self.config.set("server", "cikey", "changeme") self.config.set("server", "cikey", "changeme")
self.config.set("server", "cloudflare", "0") self.config.set("server", "cloudflare", "0")
self.config.add_section("mirror")
self.config.set("mirror", "apiurl", "http://storage.ripple.moe")
self.config.set("mirror", "apikey", "anotherkey")
self.config.add_section("debug") self.config.add_section("debug")
self.config.set("debug", "enable", "0") self.config.set("debug", "enable", "0")
self.config.set("debug", "packets", "0") self.config.set("debug", "packets", "0")
@@ -113,9 +115,15 @@ class config:
self.config.set("discord", "boturl", "") self.config.set("discord", "boturl", "")
self.config.set("discord", "devgroup", "") self.config.set("discord", "devgroup", "")
self.config.add_section("datadog")
self.config.set("datadog", "enable")
self.config.set("datadog", "apikey")
self.config.set("datadog", "appkey")
self.config.add_section("irc") self.config.add_section("irc")
self.config.set("irc", "enable", "1") self.config.set("irc", "enable", "1")
self.config.set("irc", "port", "6667") self.config.set("irc", "port", "6667")
self.config.set("irc", "hostname", "ripple")
self.config.add_section("localize") self.config.add_section("localize")
self.config.set("localize", "enable", "1") self.config.set("localize", "enable", "1")

View File

@@ -1,14 +1,13 @@
"""Some console related functions""" from common.constants import bcolors
from constants import bcolors
from objects import glob from objects import glob
def printServerStartHeader(asciiArt): def printServerStartHeader(asciiArt):
"""Print server start header with optional ascii art """
Print server start header with optional ascii art
asciiArt -- if True, will print ascii art too""" asciiArt -- if True, will print ascii art too
"""
if asciiArt == True: if asciiArt:
print("{} _ __".format(bcolors.GREEN)) print("{} _ __".format(bcolors.GREEN))
print(" (_) / /") print(" (_) / /")
print(" ______ __ ____ ____ / /____") print(" ______ __ ____ ____ / /____")
@@ -28,9 +27,8 @@ def printServerStartHeader(asciiArt):
printColored("> Welcome to pep.py osu!bancho server v{}".format(glob.VERSION), bcolors.GREEN) printColored("> Welcome to pep.py osu!bancho server v{}".format(glob.VERSION), bcolors.GREEN)
printColored("> Made by the Ripple team", bcolors.GREEN) printColored("> Made by the Ripple team", bcolors.GREEN)
printColored("> {}https://github.com/osuripple/ripple".format(bcolors.UNDERLINE), bcolors.GREEN) printColored("> {}https://git.zxq.co/ripple/pep.py".format(bcolors.UNDERLINE), bcolors.GREEN)
printColored("> Press CTRL+C to exit\n",bcolors.GREEN) printColored("> Press CTRL+C to exit\n", bcolors.GREEN)
def printNoNl(string): def printNoNl(string):
""" """
@@ -38,10 +36,8 @@ def printNoNl(string):
string -- string to print string -- string to print
""" """
print(string, end="") print(string, end="")
def printColored(string, color): def printColored(string, color):
""" """
Print colored string Print colored string
@@ -49,23 +45,22 @@ def printColored(string, color):
string -- string to print string -- string to print
color -- see bcolors.py color -- see bcolors.py
""" """
print("{}{}{}".format(color, string, bcolors.ENDC)) print("{}{}{}".format(color, string, bcolors.ENDC))
def printError(): def printError():
"""Print error text FOR LOADING""" """
Print error text FOR LOADING
"""
printColored("Error", bcolors.RED) printColored("Error", bcolors.RED)
def printDone(): def printDone():
"""Print error text FOR LOADING""" """
Print error text FOR LOADING
"""
printColored("Done", bcolors.GREEN) printColored("Done", bcolors.GREEN)
def printWarning(): def printWarning():
"""Print error text FOR LOADING""" """
Print error text FOR LOADING
"""
printColored("Warning", bcolors.YELLOW) printColored("Warning", bcolors.YELLOW)

View File

@@ -253,7 +253,6 @@ countryCodes = {
"AI": 7 "AI": 7
} }
def getCountryID(code): def getCountryID(code):
""" """
Get country ID for osu client Get country ID for osu client

View File

@@ -1,138 +0,0 @@
import pymysql
from constants import bcolors
from helpers import consoleHelper
import threading
from objects import glob
class db:
"""A MySQL database connection"""
connection = None
disconnected = False
pingTime = 600
def __init__(self, __host, __username, __password, __database, __pingTime = 600):
"""
Connect to MySQL database
__host -- MySQL host name
__username -- MySQL username
__password -- MySQL password
__database -- MySQL database name
__pingTime -- MySQL database ping time (default: 600)
"""
self.connection = pymysql.connect(host=__host, user=__username, password=__password, db=__database, cursorclass=pymysql.cursors.DictCursor, autocommit=True)
self.pingTime = __pingTime
self.pingLoop()
def bindParams(self, __query, __params):
"""
Replace every ? with the respective **escaped** parameter in array
__query -- query with ?s
__params -- array with params
return -- new query
"""
for i in __params:
escaped = self.connection.escape(i)
__query = __query.replace("?", str(escaped), 1)
return __query
def execute(self, __query, __params = None):
"""
Execute a SQL query
__query -- query, can contain ?s
__params -- array with params. Optional
"""
log.debug(query)
with self.connection.cursor() as cursor:
try:
# Bind params if needed
if __params != None:
__query = self.bindParams(__query, __params)
# Execute the query
cursor.execute(__query)
finally:
# Close this connection
cursor.close()
def fetch(self, __query, __params = None, __all = False):
"""
Fetch the first (or all) element(s) of SQL query result
__query -- query, can contain ?s
__params -- array with params. Optional
__all -- if true, will fetch all values. Same as fetchAll
return -- dictionary with result data or False if failed
"""
log.debug(query)
with self.connection.cursor() as cursor:
try:
# Bind params if needed
if __params != None:
__query = self.bindParams(__query, __params)
# Execute the query with binded params
cursor.execute(__query)
# Get first result and return it
if __all == False:
return cursor.fetchone()
else:
return cursor.fetchall()
finally:
# Close this connection
cursor.close()
def fetchAll(self, __query, __params = None):
"""
Fetch the all elements of SQL query result
__query -- query, can contain ?s
__params -- array with params. Optional
return -- dictionary with result data
"""
return self.fetch(__query, __params, True)
def pingLoop(self):
"""
Pings MySQL server. We need to ping/execute a query at least once every 8 hours
or the connection will die.
If called once, will recall after 30 minutes and so on, forever
CALL THIS FUNCTION ONLY ONCE!
"""
# Default loop time
time = self.pingTime
# Make sure the connection is alive
try:
# Try to ping and reconnect if not connected
self.connection.ping()
if self.disconnected == True:
# If we were disconnected, set disconnected to false and print message
self.disconnected = False
log.error("> Reconnected to MySQL server!", bcolors.GREEN)
except:
# Can't ping MySQL server. Show error and call loop in 5 seconds
log.error("[!] CRITICAL!! MySQL connection died! Make sure your MySQL server is running! Checking again in 5 seconds...", bcolors.RED)
self.disconnected = True
time = 5
# Schedule a new check (endless loop)
threading.Timer(time, self.pingLoop).start()

View File

@@ -1,121 +0,0 @@
import MySQLdb
import threading
from helpers import logHelper as log
class mysqlWorker:
"""
Instance of a pettirosso meme
"""
def __init__(self, wid, host, username, password, database):
"""
Create a pettirosso meme (mysql worker)
wid -- worker id
host -- hostname
username -- MySQL username
password -- MySQL password
database -- MySQL database name
"""
self.wid = wid
self.connection = MySQLdb.connect(host, username, password, database)
self.connection.autocommit(True)
self.ready = True
self.lock = threading.Lock()
class db:
"""
A MySQL db connection with multiple workers
"""
def __init__(self, host, username, password, database, workers):
"""
Create MySQL workers aka pettirossi meme
host -- hostname
username -- MySQL username
password -- MySQL password
database -- MySQL database name
workers -- Number of workers to spawn
"""
#self.lock = threading.Lock()
#self.connection = MySQLdb.connect(host, username, password, database)
self.workers = []
self.lastWorker = 0
self.workersNumber = workers
for i in range(0,self.workersNumber):
print(".", end="")
self.workers.append(mysqlWorker(i, host, username, password, database))
def getWorker(self):
"""
Return a worker object (round-robin way)
return -- worker object
"""
if self.lastWorker >= self.workersNumber-1:
self.lastWorker = 0
else:
self.lastWorker += 1
#print("Using worker {}".format(self.lastWorker))
return self.workers[self.lastWorker]
def execute(self, query, params = ()):
"""
Executes a query
query -- Query to execute. You can bind parameters with %s
params -- Parameters list. First element replaces first %s and so on. Optional.
"""
log.debug(query)
# Get a worker and acquire its lock
worker = self.getWorker()
worker.lock.acquire()
try:
# Create cursor, execute query and commit
cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor)
cursor.execute(query, params)
return cursor.lastrowid
finally:
# Close the cursor and release worker's lock
if cursor:
cursor.close()
worker.lock.release()
def fetch(self, query, params = (), all = False):
"""
Fetch a single value from db that matches given query
query -- Query to execute. You can bind parameters with %s
params -- Parameters list. First element replaces first %s and so on. Optional.
all -- Fetch one or all values. Used internally. Use fetchAll if you want to fetch all values.
"""
log.debug(query)
# Get a worker and acquire its lock
worker = self.getWorker()
worker.lock.acquire()
try:
# Create cursor, execute the query and fetch one/all result(s)
cursor = worker.connection.cursor(MySQLdb.cursors.DictCursor)
cursor.execute(query, params)
if all == True:
return cursor.fetchall()
else:
return cursor.fetchone()
finally:
# Close the cursor and release worker's lock
if cursor:
cursor.close()
worker.lock.release()
def fetchAll(self, query, params = ()):
"""
Fetch all values from db that matche given query.
Calls self.fetch with all = True.
query -- Query to execute. You can bind parameters with %s
params -- Parameters list. First element replaces first %s and so on. Optional.
"""
return self.fetch(query, params, True)

View File

@@ -1,69 +0,0 @@
import requests
from objects import glob
from helpers import generalFunctions
from urllib.parse import urlencode
from helpers import consoleHelper
from constants import bcolors
def sendDiscordMessage(channel, message, alertDev = False, prefix = "**pep.py**"):
"""
Send a message to a discord server.
This is used with ripple's schiavobot.
channel -- bunk, staff or general
message -- message to send
alertDev -- if True, hl developers group
prefix -- string to prepend to message
"""
if glob.discord == True:
for _ in range(0,20):
try:
finalMsg = "{prefix} {message}".format(prefix=prefix, message=message) if alertDev == False else "{prefix} {hl} - {message}".format(prefix=prefix, hl=glob.conf.config["discord"]["devgroup"], message=message)
requests.get("{}/{}?{}".format(glob.conf.config["discord"]["boturl"], channel, urlencode({ "message": finalMsg })))
break
except:
continue
def sendConfidential(message, alertDev = False):
"""
Send a message to #bunker
message -- message to send
"""
sendDiscordMessage("bunk", message, alertDev)
def sendStaff(message):
"""
Send a message to #staff
message -- message to send
"""
sendDiscordMessage("staff", message)
def sendGeneral(message):
"""
Send a message to #general
message -- message to send
"""
sendDiscordMessage("general", message)
def sendChatlog(message):
"""
Send a message to #chatlog
message -- message to send
"""
sendDiscordMessage("chatlog", message, prefix="")
def sendCM(message):
"""
Send a message to #communitymanagers
message -- message to send
"""
sendDiscordMessage("cm", message)

View File

@@ -1,128 +0,0 @@
"""Some functions that don't fit in any other file"""
from constants import mods
from time import gmtime, strftime
def stringToBool(s):
"""
Convert a string (True/true/1) to bool
s -- string/int value
return -- True/False
"""
return (s == "True" or s== "true" or s == "1" or s == 1)
def hexString(s):
"""
Output s' bytes in HEX
s -- string
return -- string with hex value
"""
return ":".join("{:02x}".format(ord(str(c))) for c in s)
def readableMods(__mods):
"""
Return a string with readable std mods.
Used to convert a mods number for oppai
__mods -- mods bitwise number
return -- readable mods string, eg HDDT
"""
r = ""
if __mods == 0:
return r
if __mods & mods.NoFail > 0:
r += "NF"
if __mods & mods.Easy > 0:
r += "EZ"
if __mods & mods.Hidden > 0:
r += "HD"
if __mods & mods.HardRock > 0:
r += "HR"
if __mods & mods.DoubleTime > 0:
r += "DT"
if __mods & mods.HalfTime > 0:
r += "HT"
if __mods & mods.Flashlight > 0:
r += "FL"
if __mods & mods.SpunOut > 0:
r += "SO"
return r
def getRank(gameMode, __mods, acc, c300, c100, c50, cmiss):
"""
Return a string with rank/grade for a given score.
Used mainly for "tillerino"
gameMode -- mode (0 = osu!, 1 = Taiko, 2 = CtB, 3 = osu!mania)
__mods -- mods bitwise number
acc -- accuracy
c300 -- 300 hit count
c100 -- 100 hit count
c50 -- 50 hit count
cmiss -- miss count
return -- rank/grade string
"""
total = c300 + c100 + c50 + cmiss
hdfl = (__mods & (mods.Hidden | mods.Flashlight | mods.FadeIn)) > 0
ss = "sshd" if hdfl else "ss"
s = "shd" if hdfl else "s"
if gameMode == 0 or gameMode == 1:
# osu!std / taiko
ratio300 = c300 / total
ratio50 = c50 / total
if ratio300 == 1:
return ss
if ratio300 > 0.9 and ratio50 <= 0.01 and cmiss == 0:
return s
if (ratio300 > 0.8 and cmiss == 0) or (ratio300 > 0.9):
return "a"
if (ratio300 > 0.7 and cmiss == 0) or (ratio300 > 0.8):
return "b"
if ratio300 > 0.6:
return "c"
return "d"
elif gameMode == 2:
# CtB
if acc == 100:
return ss
if acc > 98:
return s
if acc > 94:
return "a"
if acc > 90:
return "b"
if acc > 85:
return "c"
return "d"
elif gameMode == 3:
# osu!mania
if acc == 100:
return ss
if acc > 95:
return s
if acc > 90:
return "a"
if acc > 80:
return "b"
if acc > 70:
return "c"
return "d"
return "a"
def strContains(s, w):
return (' ' + w + ' ') in (' ' + s + ' ')
def getTimestamp():
"""
Return current time in YYYY-MM-DD HH:MM:SS format.
Used in logs.
"""
return strftime("%Y-%m-%d %H:%M:%S", gmtime())

View File

@@ -1,8 +1,9 @@
import urllib.request
import json import json
import urllib.request
from common.log import logUtils as log
from objects import glob from objects import glob
from helpers import logHelper as log
def getCountry(ip): def getCountry(ip):
""" """
@@ -11,7 +12,6 @@ def getCountry(ip):
ip -- IP Address ip -- IP Address
return -- Country code (2 letters) return -- Country code (2 letters)
""" """
try: try:
# Try to get country from Pikolo Aul's Go-Sanic ip API # Try to get country 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())["country"] result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["country"]
@@ -20,7 +20,6 @@ def getCountry(ip):
log.error("Error in get country") log.error("Error in get country")
return "XX" return "XX"
def getLocation(ip): def getLocation(ip):
""" """
Get latitude and longitude from IP address Get latitude and longitude from IP address
@@ -28,7 +27,6 @@ def getLocation(ip):
ip -- IP address ip -- IP address
return -- [latitude, longitude] return -- [latitude, longitude]
""" """
try: try:
# Try to get position from Pikolo Aul's Go-Sanic ip API # 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(",") result = json.loads(urllib.request.urlopen("{}/{}".format(glob.conf.config["localize"]["ipapiurl"], ip), timeout=3).read().decode())["loc"].split(",")

View File

@@ -1,140 +0,0 @@
from constants import bcolors
from helpers import discordBotHelper
from helpers import generalFunctions
from objects import glob
from helpers import userHelper
import time
import os
ENDL = "\n" if os.name == "posix" else "\r\n"
def logMessage(message, alertType = "INFO", messageColor = bcolors.ENDC, discord = None, alertDev = False, of = None, stdout = True):
"""
Logs a message to stdout/discord/file
message -- message to log
alertType -- can be any string. Standard types: INFO, WARNING and ERRORS. Defalt: INFO
messageColor -- message color (see constants.bcolors). Default = bcolots.ENDC (no color)
discord -- discord channel (bunker/cm/staff/general). Optional. Default = None
alertDev -- if True, devs will receive an hl on discord. Default: False
of -- if not None but a string, log the message to that file (inside .data folder). Eg: "warnings.txt" Default: None (don't log to file)
stdout -- if True, print the message to stdout. Default: True
"""
# Get type color from alertType
if alertType == "INFO":
typeColor = bcolors.GREEN
elif alertType == "WARNING":
typeColor = bcolors.YELLOW
elif alertType == "ERROR":
typeColor = bcolors.RED
elif alertType == "CHAT":
typeColor = bcolors.BLUE
elif alertType == "DEBUG":
typeColor = bcolors.PINK
else:
typeColor = bcolors.ENDC
# Message without colors
finalMessage = "[{time}] {type} - {message}".format(time=generalFunctions.getTimestamp(), type=alertType, message=message)
# Message with colors
finalMessageConsole = "{typeColor}[{time}] {type}{endc} - {messageColor}{message}{endc}".format(
time=generalFunctions.getTimestamp(),
type=alertType,
message=message,
typeColor=typeColor,
messageColor=messageColor,
endc=bcolors.ENDC)
# Log to console
if stdout == True:
print(finalMessageConsole)
# Log to discord if needed
if discord != None:
if discord == "bunker":
discordBotHelper.sendConfidential(message, alertDev)
elif discord == "cm":
discordBotHelper.sendCM(message)
elif discord == "staff":
discordBotHelper.sendStaff(message)
elif discord == "general":
discordBotHelper.sendGeneral(message)
# Log to file if needed
if of != None:
try:
glob.fLocks.lockFile(of)
with open(".data/{}".format(of), "a") as f:
f.write(finalMessage+ENDL)
finally:
glob.fLocks.unlockFile(of)
def warning(message, discord = None, alertDev = False):
"""
Log a warning to stdout, warnings.log (always) and discord (optional)
message -- warning message
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
"""
logMessage(message, "WARNING", bcolors.YELLOW, discord, alertDev, "warnings.txt")
def error(message, discord = None, alertDev = True):
"""
Log an error to stdout, errors.log (always) and discord (optional)
message -- error message
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
"""
logMessage(message, "ERROR", bcolors.RED, discord, alertDev, "errors.txt")
def info(message, discord = None, alertDev = False):
"""
Log an error to stdout (and info.log)
message -- info message
discord -- if not None, send message to that discord channel through schiavo. Optional. Default = None
alertDev -- if True, send al hl to devs on discord. Optional. Default = False.
"""
logMessage(message, "INFO", bcolors.ENDC, discord, alertDev, "info.txt")
def debug(message):
"""
Log a debug message to stdout and debug.log if server is running in debug mode
message -- debug message
"""
if glob.debug == True:
logMessage(message, "DEBUG", bcolors.PINK, of="debug.txt")
def chat(message):
"""
Log public messages to stdout and chatlog_public.txt
message -- chat message
"""
logMessage(message, "CHAT", bcolors.BLUE, of="chatlog_public.txt")
def pm(message):
"""
Log private messages to stdout and chatlog_private.txt
message -- chat message
"""
logMessage(message, "CHAT", bcolors.BLUE, of="chatlog_private.txt")
def rap(userID, message, discord=False, through="FokaBot"):
"""
Log a private message to Admin logs
userID -- userID of who made the action
message -- message without subject (eg: "is a meme" becomes "user is a meme")
discord -- if True, send message to discord
through -- "through" thing string. Optional. Default: "FokaBot"
"""
glob.db.execute("INSERT INTO rap_logs (id, userid, text, datetime, through) VALUES (NULL, %s, %s, %s, %s)", [userID, message, int(time.time()), through])
if discord == True:
username = userHelper.getUsername(userID)
logMessage("{} {}".format(username, message), discord=True)

View File

@@ -8,7 +8,6 @@ def uleb128Encode(num):
num -- int to encode num -- int to encode
return -- bytearray with encoded number return -- bytearray with encoded number
""" """
arr = bytearray() arr = bytearray()
length = 0 length = 0
@@ -17,14 +16,13 @@ def uleb128Encode(num):
while num > 0: while num > 0:
arr.append(num & 127) arr.append(num & 127)
num = num >> 7 num >>= 7
if num != 0: if num != 0:
arr[length] = arr[length] | 128 arr[length] |= 128
length+=1 length+=1
return arr return arr
def uleb128Decode(num): def uleb128Decode(num):
""" """
Decode uleb128 -> int Decode uleb128 -> int
@@ -32,57 +30,53 @@ def uleb128Decode(num):
num -- encoded uleb128 num -- encoded uleb128
return -- list. [total, length] return -- list. [total, length]
""" """
shift = 0 shift = 0
arr = [0,0] #total, length arr = [0,0] #total, length
while True: while True:
b = num[arr[1]] b = num[arr[1]]
arr[1]+=1 arr[1]+=1
arr[0] = arr[0] | (int(b & 127) << shift) arr[0] |= int(b & 127) << shift
if b & 128 == 0: if b & 128 == 0:
break break
shift += 7 shift += 7
return arr return arr
def unpackData(data, dataType):
def unpackData(__data, __dataType):
""" """
Unpacks data according to dataType Unpacks data according to dataType
__data -- bytes array to unpack data -- bytes array to unpack
__dataType -- data type. See dataTypes.py dataType -- data type. See dataTypes.py
return -- unpacked bytes return -- unpacked bytes
""" """
# Get right pack Type # Get right pack Type
if __dataType == dataTypes.uInt16: if dataType == dataTypes.UINT16:
unpackType = "<H" unpackType = "<H"
elif __dataType == dataTypes.sInt16: elif dataType == dataTypes.SINT16:
unpackType = "<h" unpackType = "<h"
elif __dataType == dataTypes.uInt32: elif dataType == dataTypes.UINT32:
unpackType = "<L" unpackType = "<L"
elif __dataType == dataTypes.sInt32: elif dataType == dataTypes.SINT32:
unpackType = "<l" unpackType = "<l"
elif __dataType == dataTypes.uInt64: elif dataType == dataTypes.UINT64:
unpackType = "<Q" unpackType = "<Q"
elif __dataType == dataTypes.sInt64: elif dataType == dataTypes.SINT64:
unpackType = "<q" unpackType = "<q"
elif __dataType == dataTypes.string: elif dataType == dataTypes.STRING:
unpackType = "<s" unpackType = "<s"
elif __dataType == dataTypes.ffloat: elif dataType == dataTypes.FFLOAT:
unpackType = "<f" unpackType = "<f"
else: else:
unpackType = "<B" unpackType = "<B"
# Unpack # Unpack
return struct.unpack(unpackType, bytes(__data))[0] return struct.unpack(unpackType, bytes(data))[0]
def packData(__data, dataType):
def packData(__data, __dataType):
""" """
Packs data according to dataType Packs data according to dataType
@@ -96,19 +90,19 @@ def packData(__data, __dataType):
pack = True # if True, use pack. False only with strings pack = True # if True, use pack. False only with strings
# Get right pack Type # Get right pack Type
if __dataType == dataTypes.bbytes: if dataType == dataTypes.BBYTES:
# Bytes, do not use pack, do manually # Bytes, do not use pack, do manually
pack = False pack = False
data = __data data = __data
elif __dataType == dataTypes.intList: elif dataType == dataTypes.INT_LIST:
# Pack manually # Pack manually
pack = False pack = False
# Add length # Add length
data = packData(len(__data), dataTypes.uInt16) data = packData(len(__data), dataTypes.UINT16)
# Add all elements # Add all elements
for i in __data: for i in __data:
data += packData(i, dataTypes.sInt32) data += packData(i, dataTypes.SINT32)
elif __dataType == dataTypes.string: elif dataType == dataTypes.STRING:
# String, do not use pack, do manually # String, do not use pack, do manually
pack = False pack = False
if len(__data) == 0: if len(__data) == 0:
@@ -119,33 +113,32 @@ def packData(__data, __dataType):
data += b"\x0B" data += b"\x0B"
data += uleb128Encode(len(__data)) data += uleb128Encode(len(__data))
data += str.encode(__data, "latin_1", "ignore") data += str.encode(__data, "latin_1", "ignore")
elif __dataType == dataTypes.uInt16: elif dataType == dataTypes.UINT16:
packType = "<H" packType = "<H"
elif __dataType == dataTypes.sInt16: elif dataType == dataTypes.SINT16:
packType = "<h" packType = "<h"
elif __dataType == dataTypes.uInt32: elif dataType == dataTypes.UINT32:
packType = "<L" packType = "<L"
elif __dataType == dataTypes.sInt32: elif dataType == dataTypes.SINT32:
packType = "<l" packType = "<l"
elif __dataType == dataTypes.uInt64: elif dataType == dataTypes.UINT64:
packType = "<Q" packType = "<Q"
elif __dataType == dataTypes.sInt64: elif dataType == dataTypes.SINT64:
packType = "<q" packType = "<q"
elif __dataType == dataTypes.string: elif dataType == dataTypes.STRING:
packType = "<s" packType = "<s"
elif __dataType == dataTypes.ffloat: elif dataType == dataTypes.FFLOAT:
packType = "<f" packType = "<f"
else: else:
packType = "<B" packType = "<B"
# Pack if needed # Pack if needed
if pack == True: if pack:
data += struct.pack(packType, __data) data += struct.pack(packType, __data)
return data return data
# TODO: Wat dangerous def buildPacket(__packet, __packetData=None):
def buildPacket(__packet, __packetData = []):
""" """
Build a packet Build a packet
@@ -154,8 +147,9 @@ def buildPacket(__packet, __packetData = []):
return -- packet bytes return -- packet bytes
""" """
# Set some variables # Set some variables
if __packetData is None:
__packetData = []
packetData = bytes() packetData = bytes()
packetLength = 0 packetLength = 0
packetBytes = bytes() packetBytes = bytes()
@@ -174,7 +168,6 @@ def buildPacket(__packet, __packetData = []):
packetBytes += packetData # packet data packetBytes += packetData # packet data
return packetBytes return packetBytes
def readPacketID(stream): def readPacketID(stream):
""" """
Read packetID from stream (0-1 bytes) Read packetID from stream (0-1 bytes)
@@ -182,9 +175,7 @@ def readPacketID(stream):
stream -- data stream stream -- data stream
return -- packet ID (int) return -- packet ID (int)
""" """
return unpackData(stream[0:2], dataTypes.UINT16)
return unpackData(stream[0:2], dataTypes.uInt16)
def readPacketLength(stream): def readPacketLength(stream):
""" """
@@ -193,11 +184,10 @@ def readPacketLength(stream):
stream -- data stream stream -- data stream
return -- packet length (int) return -- packet length (int)
""" """
return unpackData(stream[3:7], dataTypes.UINT32)
return unpackData(stream[3:7], dataTypes.uInt32)
def readPacketData(stream, structure = [], hasFirstBytes = True): def readPacketData(stream, structure=None, hasFirstBytes = True):
""" """
Read packet data from stream according to structure Read packet data from stream according to structure
@@ -208,12 +198,13 @@ def readPacketData(stream, structure = [], hasFirstBytes = True):
Optional. Default: True Optional. Default: True
return -- dictionary. key: name, value: read data return -- dictionary. key: name, value: read data
""" """
# Read packet ID (first 2 bytes) # Read packet ID (first 2 bytes)
if structure is None:
structure = []
data = {} data = {}
# Skip packet ID and packet length if needed # Skip packet ID and packet length if needed
if hasFirstBytes == True: if hasFirstBytes:
end = 7 end = 7
start = 7 start = 7
else: else:
@@ -224,22 +215,22 @@ def readPacketData(stream, structure = [], hasFirstBytes = True):
for i in structure: for i in structure:
start = end start = end
unpack = True unpack = True
if i[1] == dataTypes.intList: if i[1] == dataTypes.INT_LIST:
# sInt32 list. # sInt32 list.
# Unpack manually with for loop # Unpack manually with for loop
unpack = False unpack = False
# Read length (uInt16) # Read length (uInt16)
length = unpackData(stream[start:start+2], dataTypes.uInt16) length = unpackData(stream[start:start+2], dataTypes.UINT16)
# Read all int inside list # Read all int inside list
data[i[0]] = [] data[i[0]] = []
for j in range(0,length): for j in range(0,length):
data[i[0]].append(unpackData(stream[start+2+(4*j):start+2+(4*(j+1))], dataTypes.sInt32)) data[i[0]].append(unpackData(stream[start+2+(4*j):start+2+(4*(j+1))], dataTypes.SINT32))
# Update end # Update end
end = start+2+(4*length) end = start+2+(4*length)
elif i[1] == dataTypes.string: elif i[1] == dataTypes.STRING:
# String, don't unpack # String, don't unpack
unpack = False unpack = False
@@ -256,17 +247,17 @@ def readPacketData(stream, structure = [], hasFirstBytes = True):
# Read bytes # Read bytes
data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end]) data[i[0]] = ''.join(chr(j) for j in stream[start+1+length[1]:end])
elif i[1] == dataTypes.byte: elif i[1] == dataTypes.BYTE:
end = start+1 end = start+1
elif i[1] == dataTypes.uInt16 or i[1] == dataTypes.sInt16: elif i[1] == dataTypes.UINT16 or i[1] == dataTypes.SINT16:
end = start+2 end = start+2
elif i[1] == dataTypes.uInt32 or i[1] == dataTypes.sInt32: elif i[1] == dataTypes.UINT32 or i[1] == dataTypes.SINT32:
end = start+4 end = start+4
elif i[1] == dataTypes.uInt64 or i[1] == dataTypes.sInt64: elif i[1] == dataTypes.UINT64 or i[1] == dataTypes.SINT64:
end = start+8 end = start+8
# Unpack if needed # Unpack if needed
if unpack == True: if unpack:
data[i[0]] = unpackData(stream[start:end], i[1]) data[i[0]] = unpackData(stream[start:end], i[1])
return data return data

View File

@@ -1,36 +0,0 @@
from helpers import cryptHelper
import base64
import bcrypt
def checkOldPassword(password, salt, rightPassword):
"""
Check if password+salt corresponds to rightPassword
password -- input password
salt -- password's salt
rightPassword -- right password
return -- bool
"""
return (rightPassword == cryptHelper.crypt(password, "$2y$"+str(base64.b64decode(salt))))
def checkNewPassword(password, dbPassword):
"""
Check if a password (version 2) is right.
password -- input password
dbPassword -- the password in the database
return -- bool
"""
password = password.encode("utf8")
dbPassword = dbPassword.encode("utf8")
return bcrypt.hashpw(password, dbPassword) == dbPassword
def genBcrypt(password):
"""
Bcrypts a password.
password -- the password to hash.
return -- bytestring
"""
return bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt(10, b'2a'))

View File

@@ -1,89 +0,0 @@
import tornado
import tornado.web
import tornado.gen
from tornado.ioloop import IOLoop
from objects import glob
from raven.contrib.tornado import SentryMixin
from raven.contrib.tornado import AsyncSentryClient
import gevent
class asyncRequestHandler(tornado.web.RequestHandler):
"""
Tornado asynchronous request handler
create a class that extends this one (requestHelper.asyncRequestHandler)
use asyncGet() and asyncPost() instad of get() and post().
Done. I'm not kidding.
"""
@tornado.web.asynchronous
@tornado.gen.engine
def get(self, *args, **kwargs):
try:
yield tornado.gen.Task(runBackground, (self.asyncGet, tuple(args), dict(kwargs)))
except Exception as e:
yield tornado.gen.Task(self.captureException, exc_info=True)
finally:
if not self._finished:
self.finish()
@tornado.web.asynchronous
@tornado.gen.engine
def post(self, *args, **kwargs):
try:
yield tornado.gen.Task(runBackground, (self.asyncPost, tuple(args), dict(kwargs)))
except Exception as e:
yield tornado.gen.Task(self.captureException, exc_info=True)
finally:
if not self._finished:
self.finish()
def asyncGet(self, *args, **kwargs):
self.send_error(405)
self.finish()
def asyncPost(self, *args, **kwargs):
self.send_error(405)
self.finish()
def getRequestIP(self):
realIP = self.request.headers.get("X-Forwarded-For") if glob.cloudflare == True else self.request.headers.get("X-Real-IP")
if realIP != None:
return realIP
return self.request.remote_ip
def runBackground(data, callback):
"""
Run a function in the background.
Used to handle multiple requests at the same time
"""
func, args, kwargs = data
def _callback(result):
IOLoop.instance().add_callback(lambda: callback(result))
#glob.pool.apply_async(func, args, kwargs, _callback)
g = gevent.Greenlet(func, *args, **kwargs)
g.link(_callback)
g.start()
def checkArguments(arguments, requiredArguments):
"""
Check that every requiredArguments elements are in arguments
arguments -- full argument list, from tornado
requiredArguments -- required arguments list es: ["u", "ha"]
handler -- handler string name to print in exception. Optional
return -- True if all arguments are passed, none if not
"""
for i in requiredArguments:
if i not in arguments:
return False
return True
def printArguments(t):
"""
Print passed arguments, for debug purposes
t -- tornado object (self)
"""
print("ARGS::")
for i in t.request.arguments:
print ("{}={}".format(i, t.get_argument(i)))

View File

@@ -1,11 +1,27 @@
from objects import glob import math
from constants import serverPackets
import psutil
import os import os
import signal
import sys import sys
import threading import threading
import signal import time
from helpers import logHelper as log
import psutil
from common.constants import bcolors
from common.log import logUtils as log
from constants import serverPackets
from helpers import consoleHelper
from objects import glob
def dispose():
"""
Perform some clean up. Called on shutdown.
:return:
"""
print("> Disposing server... ")
glob.fileBuffers.flushAll()
consoleHelper.printColored("Goodbye!", bcolors.GREEN)
def runningUnderUnix(): def runningUnderUnix():
""" """
@@ -13,11 +29,9 @@ def runningUnderUnix():
return --- True if running under UNIX, otherwise False return --- True if running under UNIX, otherwise False
""" """
return True if os.name == "posix" else False return True if os.name == "posix" else False
def scheduleShutdown(sendRestartTime, restart, message = "", delay=20):
def scheduleShutdown(sendRestartTime, restart, message = ""):
""" """
Schedule a server shutdown/restart Schedule a server shutdown/restart
@@ -25,17 +39,16 @@ def scheduleShutdown(sendRestartTime, restart, message = ""):
restart -- if True, server will restart. if False, server will shudown 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 message -- if set, send that message to every client to warn about the shutdown/restart
""" """
# Console output # Console output
log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+20)) log.info("Pep.py will {} in {} seconds!".format("restart" if restart else "shutdown", sendRestartTime+delay))
log.info("Sending server restart packets in {} seconds...".format(sendRestartTime)) log.info("Sending server restart packets in {} seconds...".format(sendRestartTime))
# Send notification if set # Send notification if set
if message != "": if message != "":
glob.tokens.enqueueAll(serverPackets.notification(message)) glob.streams.broadcast("main", serverPackets.notification(message))
# Schedule server restart packet # Schedule server restart packet
threading.Timer(sendRestartTime, glob.tokens.enqueueAll, [serverPackets.banchoRestart(50000)]).start() threading.Timer(sendRestartTime, glob.streams.broadcast, ["main", serverPackets.banchoRestart(delay*2*1000)]).start()
glob.restarting = True glob.restarting = True
# Restart/shutdown # Restart/shutdown
@@ -44,44 +57,51 @@ def scheduleShutdown(sendRestartTime, restart, message = ""):
else: else:
action = shutdownServer action = shutdownServer
# Schedule actual server shutdown/restart 20 seconds after server restart packet, so everyone gets it # Schedule actual server shutdown/restart some seconds after server restart packet, so everyone gets it
threading.Timer(sendRestartTime+20, action).start() threading.Timer(sendRestartTime+delay, action).start()
def restartServer(): def restartServer():
"""Restart pep.py script""" """Restart pep.py script"""
log.info("Restarting pep.py...") log.info("Restarting pep.py...")
dispose()
os.execv(sys.executable, [sys.executable] + sys.argv) os.execv(sys.executable, [sys.executable] + sys.argv)
def shutdownServer(): def shutdownServer():
"""Shutdown pep.py""" """Shutdown pep.py"""
log.info("> Shutting down pep.py...") log.info("Shutting down pep.py...")
dispose()
sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT sig = signal.SIGKILL if runningUnderUnix() else signal.CTRL_C_EVENT
os.kill(os.getpid(), sig) os.kill(os.getpid(), sig)
def getSystemInfo(): def getSystemInfo():
""" """
Get a dictionary with some system/server info 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)}
data = {}
# Get if server is running under unix/nt
data["unix"] = runningUnderUnix()
# General stats # General stats
data["connectedUsers"] = len(glob.tokens.tokens) delta = time.time()-glob.startTime
data["matches"] = len(glob.matches.matches) days = math.floor(delta/86400)
delta -= days*86400
hours = math.floor(delta/3600)
delta -= hours*3600
minutes = math.floor(delta/60)
delta -= minutes*60
seconds = math.floor(delta)
data["uptime"] = "{}d {}h {}m {}s".format(days, hours, minutes, seconds)
data["cpuUsage"] = psutil.cpu_percent() data["cpuUsage"] = psutil.cpu_percent()
data["totalMemory"] = "{0:.2f}".format(psutil.virtual_memory()[0]/1074000000) memory = psutil.virtual_memory()
data["usedMemory"] = "{0:.2f}".format(psutil.virtual_memory()[3]/1074000000) data["totalMemory"] = "{0:.2f}".format(memory.total/1074000000)
data["usedMemory"] = "{0:.2f}".format(memory.active/1074000000)
# Unix only stats # Unix only stats
if data["unix"] == True: if data["unix"]:
data["loadAverage"] = os.getloadavg() data["loadAverage"] = os.getloadavg()
else: else:
data["loadAverage"] = (0,0,0) data["loadAverage"] = (0,0,0)

View File

@@ -1,621 +0,0 @@
from helpers import passwordHelper
from constants import gameModes
from helpers import generalFunctions
from objects import glob
from helpers import logHelper as log
import time
from constants import privileges
def getID(username):
"""
Get username's user ID
db -- database connection
username -- user
return -- user id or False
"""
# Get user ID from db
userID = glob.db.fetch("SELECT id FROM users WHERE username = %s", [username])
# Make sure the query returned something
if userID == None:
return False
# Return user ID
return userID["id"]
def checkLogin(userID, password):
"""
Check userID's login with specified password
db -- database connection
userID -- user id
password -- plain md5 password
return -- True or False
"""
# Get password data
passwordData = glob.db.fetch("SELECT password_md5, salt, password_version FROM users WHERE id = %s", [userID])
# Make sure the query returned something
if passwordData == None:
return False
# Return valid/invalid based on the password version.
if passwordData["password_version"] == 2:
return passwordHelper.checkNewPassword(password, passwordData["password_md5"])
if passwordData["password_version"] == 1:
ok = passwordHelper.checkOldPassword(password, passwordData["salt"], passwordData["password_md5"])
if not ok: return False
newpass = passwordHelper.genBcrypt(password)
glob.db.execute("UPDATE users SET password_md5=%s, salt='', password_version='2' WHERE id = %s", [newpass, userID])
def exists(userID):
"""
Check if userID exists
userID -- user ID to check
return -- bool
"""
result = glob.db.fetch("SELECT id FROM users WHERE id = %s", [userID])
if result == None:
return False
else:
return True
def getSilenceEnd(userID):
"""
Get userID's **ABSOLUTE** silence end UNIX time
Remember to subtract time.time() to get the actual silence time
userID -- userID
return -- UNIX time
"""
return glob.db.fetch("SELECT silence_end FROM users WHERE id = %s", [userID])["silence_end"]
def silence(userID, seconds, silenceReason, author = 999):
"""
Silence someone
userID -- userID
seconds -- silence length in seconds
silenceReason -- Silence reason shown on website
author -- userID of who silenced the user. Default: 999
"""
# db qurey
silenceEndTime = int(time.time())+seconds
glob.db.execute("UPDATE users SET silence_end = %s, silence_reason = %s WHERE id = %s", [silenceEndTime, silenceReason, userID])
# Loh
targetUsername = getUsername(userID)
# TODO: exists check im drunk rn i need to sleep (stampa piede ubriaco confirmed)
if seconds > 0:
log.rap(author, "has silenced {} for {} seconds for the following reason: \"{}\"".format(targetUsername, seconds, silenceReason), True)
else:
log.rap(author, "has removed {}'s silence".format(targetUsername), True)
def getRankedScore(userID, gameMode):
"""
Get userID's ranked score relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- ranked score
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT ranked_score_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["ranked_score_"+modeForDB]
def getTotalScore(userID, gameMode):
"""
Get userID's total score relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- total score
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT total_score_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["total_score_"+modeForDB]
def getAccuracy(userID, gameMode):
"""
Get userID's average accuracy relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- accuracy
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT avg_accuracy_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["avg_accuracy_"+modeForDB]
def getGameRank(userID, gameMode):
"""
Get userID's **in-game rank** (eg: #1337) relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- game rank
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
result = glob.db.fetch("SELECT position FROM leaderboard_"+modeForDB+" WHERE user = %s", [userID])
if result == None:
return 0
else:
return result["position"]
def getPlaycount(userID, gameMode):
"""
Get userID's playcount relative to gameMode
userID -- userID
gameMode -- int value, see gameModes
return -- playcount
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT playcount_"+modeForDB+" FROM users_stats WHERE id = %s", [userID])["playcount_"+modeForDB]
def getUsername(userID):
"""
Get userID's username
userID -- userID
return -- username
"""
return glob.db.fetch("SELECT username FROM users WHERE id = %s", [userID])["username"]
def getFriendList(userID):
"""
Get userID's friendlist
userID -- userID
return -- list with friends userIDs. [0] if no friends.
"""
# Get friends from db
friends = glob.db.fetchAll("SELECT user2 FROM users_relationships WHERE user1 = %s", [userID])
if friends == None or len(friends) == 0:
# We have no friends, return 0 list
return [0]
else:
# Get only friends
friends = [i["user2"] for i in friends]
# Return friend IDs
return friends
def addFriend(userID, friendID):
"""
Add friendID to userID's friend list
userID -- user
friendID -- new friend
"""
# Make sure we aren't adding us to our friends
if userID == friendID:
return
# check user isn't already a friend of ours
if glob.db.fetch("SELECT id FROM users_relationships WHERE user1 = %s AND user2 = %s", [userID, friendID]) != None:
return
# Set new value
glob.db.execute("INSERT INTO users_relationships (user1, user2) VALUES (%s, %s)", [userID, friendID])
def removeFriend(userID, friendID):
"""
Remove friendID from userID's friend list
userID -- user
friendID -- old friend
"""
# Delete user relationship. We don't need to check if the relationship was there, because who gives a shit,
# if they were not friends and they don't want to be anymore, be it. ¯\_(ツ)_/¯
glob.db.execute("DELETE FROM users_relationships WHERE user1 = %s AND user2 = %s", [userID, friendID])
def getCountry(userID):
"""
Get userID's country **(two letters)**.
Use countryHelper.getCountryID with what that function returns
to get osu! country ID relative to that user
userID -- user
return -- country code (two letters)
"""
return glob.db.fetch("SELECT country FROM users_stats WHERE id = %s", [userID])["country"]
def getPP(userID, gameMode):
"""
Get userID's PP relative to gameMode
userID -- user
return -- PP
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
return glob.db.fetch("SELECT pp_{} FROM users_stats WHERE id = %s".format(modeForDB), [userID])["pp_{}".format(modeForDB)]
def setCountry(userID, country):
"""
Set userID's country (two letters)
userID -- userID
country -- country letters
"""
glob.db.execute("UPDATE users_stats SET country = %s WHERE id = %s", [country, userID])
def getShowCountry(userID):
"""
Get userID's show country status
userID -- userID
return -- True if country is shown, False if it's hidden
"""
country = glob.db.fetch("SELECT show_country FROM users_stats WHERE id = %s", [userID])
if country == None:
return False
return generalFunctions.stringToBool(country)
def logIP(userID, ip):
"""
User IP log
USED FOR MULTIACCOUNT DETECTION
"""
glob.db.execute("""INSERT INTO ip_user (userid, ip, occurencies) VALUES (%s, %s, 1)
ON DUPLICATE KEY UPDATE occurencies = occurencies + 1""", [userID, ip])
def saveBanchoSession(userID, ip):
"""
Save userid and ip of this token in bancho_sessions table.
Used to cache logins on LETS requests
"""
log.debug("Saving bancho session ({}::{}) in db".format(userID, ip))
glob.db.execute("INSERT INTO bancho_sessions (id, userid, ip) VALUES (NULL, %s, %s)", [userID, ip])
def deleteBanchoSessions(userID, ip):
"""Delete this bancho session from DB"""
log.debug("Deleting bancho session ({}::{}) from db".format(userID, ip))
try:
glob.db.execute("DELETE FROM bancho_sessions WHERE userid = %s AND ip = %s", [userID, ip])
except:
log.warning("Token for user: {} ip: {} doesn't exist".format(userID, ip))
def is2FAEnabled(userID):
"""Returns True if 2FA is enable for this account"""
result = glob.db.fetch("SELECT id FROM 2fa_telegram WHERE userid = %s LIMIT 1", [userID])
return True if result is not None else False
def check2FA(userID, ip):
"""Returns True if this IP is untrusted"""
if is2FAEnabled(userID) == False:
return False
result = glob.db.fetch("SELECT id FROM ip_user WHERE userid = %s AND ip = %s", [userID, ip])
return True if result is None else False
def getUserStats(userID, gameMode):
"""
Get all user stats relative to gameMode with only two queries
userID --
gameMode -- gameMode number
return -- dictionary with results
"""
modeForDB = gameModes.getGameModeForDB(gameMode)
# Get stats
stats = glob.db.fetch("""SELECT
ranked_score_{gm} AS rankedScore,
avg_accuracy_{gm} AS accuracy,
playcount_{gm} AS playcount,
total_score_{gm} AS totalScore,
pp_{gm} AS pp
FROM users_stats WHERE id = %s LIMIT 1""".format(gm=modeForDB), [userID])
# Get game rank
result = glob.db.fetch("SELECT position FROM leaderboard_{} WHERE user = %s LIMIT 1".format(modeForDB), [userID])
if result == None:
stats["gameRank"] = 0
else:
stats["gameRank"] = result["position"]
# Return stats + game rank
return stats
def isAllowed(userID):
"""
Check if userID is not banned or restricted
userID -- id of the user
return -- True if not banned or restricted, otherwise false.
"""
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s", [userID])
if result != None:
return (result["privileges"] & privileges.USER_NORMAL) and (result["privileges"] & privileges.USER_PUBLIC)
else:
return False
def isRestricted(userID):
"""
Check if userID is restricted
userID -- id of the user
return -- True if not restricted, otherwise false.
"""
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s", [userID])
if result != None:
return (result["privileges"] & privileges.USER_NORMAL) and not (result["privileges"] & privileges.USER_PUBLIC)
else:
return False
def isBanned(userID):
"""
Check if userID is banned
userID -- id of the user
return -- True if not banned, otherwise false.
"""
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s", [userID])
if result != None:
return not (result["privileges"] & 3 > 0)
else:
return True
def ban(userID):
"""
Ban userID
userID -- id of user
"""
banDateTime = int(time.time())
glob.db.execute("UPDATE users SET privileges = privileges & %s, ban_datetime = %s WHERE id = %s", [ ~(privileges.USER_NORMAL | privileges.USER_PUBLIC | privileges.USER_PENDING_VERIFICATION) , banDateTime, userID])
def unban(userID):
"""
Unban userID
userID -- id of user
"""
glob.db.execute("UPDATE users SET privileges = privileges | %s, ban_datetime = 0 WHERE id = %s", [ (privileges.USER_NORMAL | privileges.USER_PUBLIC) , userID])
def restrict(userID):
"""
Put userID in restricted mode
userID -- id of user
"""
banDateTime = int(time.time())
glob.db.execute("UPDATE users SET privileges = privileges & %s, ban_datetime = %s WHERE id = %s", [~privileges.USER_PUBLIC, banDateTime, userID])
def unrestrict(userID):
"""
Remove restricted mode from userID.
Same as unban().
userID -- id of user
"""
unban(userID)
def getPrivileges(userID):
"""
Return privileges for userID
userID -- id of user
return -- privileges number
"""
result = glob.db.fetch("SELECT privileges FROM users WHERE id = %s", [userID])
if result != None:
return result["privileges"]
else:
return 0
def setPrivileges(userID, priv):
"""
Set userID's privileges in db
userID -- id of user
priv -- privileges number
"""
glob.db.execute("UPDATE users SET privileges = %s WHERE id = %s", [priv, userID])
def isInPrivilegeGroup(userID, groupName):
groupPrivileges = glob.db.fetch("SELECT privileges FROM privileges_groups WHERE name = %s", [groupName])
if groupPrivileges == None:
return False
groupPrivileges = groupPrivileges["privileges"]
userToken = glob.tokens.getTokenFromUserID(userID)
if userToken != None:
userPrivileges = userToken.privileges
else:
userPrivileges = getPrivileges(userID)
return (userPrivileges == groupPrivileges) or (userPrivileges == (groupPrivileges | privileges.USER_DONOR))
def appendNotes(userID, notes, addNl = True):
"""
Append "notes" to current userID's "notes for CM"
userID -- id of user
notes -- text to append
addNl -- if True, prepend \n to notes. Optional. Default: True.
"""
if addNl == True:
notes = "\n"+notes
glob.db.execute("UPDATE users SET notes=CONCAT(COALESCE(notes, ''),%s) WHERE id = %s", [notes, userID])
def logHardware(userID, hashes, activation = False):
"""
Hardware log
USED FOR MULTIACCOUNT DETECTION
Peppy's botnet (client data) structure (new line = "|", already split)
[0] osu! version
[1] plain mac addressed, separated by "."
[2] mac addresses hash set
[3] unique ID
[4] disk ID
return -- True if hw is not banned, otherwise false
"""
# Make sure the strings are not empty
for i in hashes[2:5]:
if i == "":
log.warning("Invalid hash set ({}) for user {} in HWID check".format(hashes, userID), "bunk")
return False
# Run some HWID checks on that user if he is not restricted
if isRestricted(userID) == False:
# Get username
username = getUsername(userID)
# Get the list of banned or restricted users that have logged in from this or similar HWID hash set
banned = glob.db.fetchAll("""SELECT users.id as userid, hw_user.occurencies, users.username FROM hw_user
LEFT JOIN users ON users.id = hw_user.userid
WHERE hw_user.userid != %(userid)s
AND (IF(%(mac)s!='b4ec3c4334a0249dae95c284ec5983df', hw_user.mac = %(mac)s, 1) AND hw_user.unique_id = %(uid)s AND hw_user.disk_id = %(diskid)s)
AND (users.privileges & 3 != 3)""", {
"userid": userID,
"mac": hashes[2],
"uid": hashes[3],
"diskid": hashes[4],
})
for i in banned:
# Get the total numbers of logins
total = glob.db.fetch("SELECT COUNT(*) AS count FROM hw_user WHERE userid = %s LIMIT 1", [userID])
# and make sure it is valid
if total == None:
continue
total = total["count"]
# Calculate 10% of total
perc = (total*10)/100
if i["occurencies"] >= perc:
# If the banned user has logged in more than 10% of the times from this user, restrict this user
restrict(userID)
appendNotes(userID, "-- Logged in from HWID ({hwid}) used more than 10% from user {banned} ({bannedUserID}), who is banned/restricted.".format(
hwid=hashes[2:5],
banned=i["username"],
bannedUserID=i["userid"]
))
log.warning("**{user}** ({userID}) has been restricted because he has logged in from HWID _({hwid})_ used more than 10% from banned/restricted user **{banned}** ({bannedUserID}), **possible multiaccount**.".format(
user=username,
userID=userID,
hwid=hashes[2:5],
banned=i["username"],
bannedUserID=i["userid"]
), "cm")
# Update hash set occurencies
glob.db.execute("""
INSERT INTO hw_user (id, userid, mac, unique_id, disk_id, occurencies) VALUES (NULL, %s, %s, %s, %s, 1)
ON DUPLICATE KEY UPDATE occurencies = occurencies + 1
""", [userID, hashes[2], hashes[3], hashes[4]])
# Optionally, set this hash as 'used for activation'
if activation == True:
glob.db.execute("UPDATE hw_user SET activated = 1 WHERE userid = %s AND mac = %s AND unique_id = %s AND disk_id = %s", [userID, hashes[2], hashes[3], hashes[4]])
# Access granted, abbiamo impiegato 3 giorni
# We grant access even in case of login from banned HWID
# because we call restrict() above so there's no need to deny the access.
return True
def resetPendingFlag(userID, success=True):
"""
Remove pending flag from an user.
userID -- ID of the user
success -- if True, set USER_PUBLIC and USER_NORMAL flags too
"""
glob.db.execute("UPDATE users SET privileges = privileges & %s WHERE id = %s LIMIT 1", [~privileges.USER_PENDING_VERIFICATION, userID])
if success == True:
glob.db.execute("UPDATE users SET privileges = privileges | %s WHERE id = %s LIMIT 1", [(privileges.USER_PUBLIC | privileges.USER_NORMAL), userID])
def verifyUser(userID, hashes):
# Check for valid hash set
for i in hashes[2:5]:
if i == "":
log.warning("Invalid hash set ({}) for user {} while verifying the account".format(str(hashes), userID), "bunk")
return False
# Get username
username = getUsername(userID)
# Make sure there are no other accounts activated with this exact mac/unique id/hwid
match = glob.db.fetchAll("SELECT userid FROM hw_user WHERE (IF(%(mac)s != 'b4ec3c4334a0249dae95c284ec5983df', mac = %(mac)s, 1) AND unique_id = %(uid)s AND disk_id = %(diskid)s) AND userid != %(userid)s AND activated = 1 LIMIT 1", {
"mac": hashes[2],
"uid": hashes[3],
"diskid": hashes[4],
"userid": userID
})
if match:
# This is a multiaccount, restrict other account and ban this account
# Get original userID and username (lowest ID)
originalUserID = match[0]["userid"]
originalUsername = getUsername(originalUserID)
# Ban this user and append notes
ban(userID) # this removes the USER_PENDING_VERIFICATION flag too
appendNotes(userID, "-- {}'s multiaccount ({}), found HWID match while verifying account ({})".format(originalUsername, originalUserID, hashes[2:5]))
appendNotes(originalUserID, "-- Has created multiaccount {} ({})".format(username, userID))
# Restrict the original
restrict(originalUserID)
# Discord message
log.warning("User **{originalUsername}** ({originalUserID}) has been restricted because he has created multiaccount **{username}** ({userID}). The multiaccount has been banned.".format(
originalUsername=originalUsername,
originalUserID=originalUserID,
username=username,
userID=userID
), "cm")
# Disallow login
return False
else:
# No matches found, set USER_PUBLIC and USER_NORMAL flags and reset USER_PENDING_VERIFICATION flag
resetPendingFlag(userID)
log.info("User **{}** ({}) has verified his account with hash set _{}_".format(username, userID, hashes[2:5]), "cm")
# Allow login
return True
def hasVerifiedHardware(userID):
"""
userID -- id of the user
return -- True if hwid activation data is in db, otherwise false
"""
data = glob.db.fetch("SELECT id FROM hw_user WHERE userid = %s AND activated = 1 LIMIT 1", [userID])
if data != None:
return True
return False

View File

@@ -6,19 +6,21 @@ by Joel Rosdahl, licensed under the GNU GPL 2 License.
Most of the reference code from miniircd was used for the low-level logic. Most of the reference code from miniircd was used for the low-level logic.
The high-level code has been rewritten to make it compatible with pep.py. The high-level code has been rewritten to make it compatible with pep.py.
""" """
import sys
import traceback
import socket
import select
import time
import re
import hashlib import hashlib
from helpers import logHelper as log import re
import select
import socket
import sys
import time
import traceback
from objects import glob
from helpers import chatHelper as chat
import raven import raven
from common.log import logUtils as log
from helpers import chatHelper as chat
from objects import glob
class Client: class Client:
""" """
IRC Client object IRC Client object
@@ -42,7 +44,8 @@ class Client:
self.server = server self.server = server
self.socket = sock self.socket = sock
(self.ip, self.port) = sock.getpeername() (self.ip, self.port) = sock.getpeername()
self.username = "" self.IRCUsername = ""
self.banchoUsername = ""
self.supposedUsername = "" self.supposedUsername = ""
self.joinedChannels = [] self.joinedChannels = []
@@ -90,7 +93,7 @@ class Client:
channel -- optional channel -- optional
""" """
if nickname == "": if nickname == "":
nickname = self.username nickname = self.IRCUsername
if channel != "": if channel != "":
channel = " "+channel channel = " "+channel
self.reply("{code:03d} {nickname}{channel} :{message}".format(code=code, nickname=nickname, channel=channel, message=message)) self.reply("{code:03d} {nickname}{channel} :{message}".format(code=code, nickname=nickname, channel=channel, message=message))
@@ -130,8 +133,8 @@ class Client:
self.server.removeClient(self, quitmsg) self.server.removeClient(self, quitmsg)
# Bancho logout # Bancho logout
if callLogout == True: if callLogout:
chat.IRCDisconnect(self.username) chat.IRCDisconnect(self.IRCUsername)
def readSocket(self): def readSocket(self):
@@ -143,7 +146,7 @@ class Client:
quitmsg = "EOT" quitmsg = "EOT"
except socket.error as x: except socket.error as x:
# Error while reading data, this client will be disconnected # Error while reading data, this client will be disconnected
data = "" data = bytes()
quitmsg = x quitmsg = x
if data: if data:
@@ -201,7 +204,7 @@ class Client:
log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent])) log.debug("[IRC] [{}:{}] <- {}".format(self.ip, self.port, self.__writebuffer[:sent]))
self.__writebuffer = self.__writebuffer[sent:] self.__writebuffer = self.__writebuffer[sent:]
except socket.error as x: except socket.error as x:
self.disconnect(x) self.disconnect(str(x))
def checkAlive(self): def checkAlive(self):
@@ -252,7 +255,7 @@ class Client:
tokenHash = m.hexdigest() tokenHash = m.hexdigest()
supposedUsername = glob.db.fetch("SELECT users.username FROM users LEFT JOIN irc_tokens ON users.id = irc_tokens.userid WHERE irc_tokens.token = %s LIMIT 1", [tokenHash]) supposedUsername = glob.db.fetch("SELECT users.username FROM users LEFT JOIN irc_tokens ON users.id = irc_tokens.userid WHERE irc_tokens.token = %s LIMIT 1", [tokenHash])
if supposedUsername: if supposedUsername:
self.supposedUsername = supposedUsername["username"] self.supposedUsername = chat.fixUsernameForIRC(supposedUsername["username"])
self.__handleCommand = self.registerHandler self.__handleCommand = self.registerHandler
else: else:
# Wrong IRC Token # Wrong IRC Token
@@ -270,29 +273,32 @@ class Client:
nick = arguments[0] nick = arguments[0]
# Make sure this is the first time we set our nickname # Make sure this is the first time we set our nickname
if self.username != "": if self.IRCUsername != "":
self.reply("432 * %s :Erroneous nickname" % nick) self.reply("432 * %s :Erroneous nickname" % nick)
return return
# Make sure the IRC token was correct: # Make sure the IRC token was correct:
# (self.supposedUsername is already fixed for IRC)
if nick.lower() != self.supposedUsername.lower(): if nick.lower() != self.supposedUsername.lower():
self.reply("464 :Password incorrect") self.reply("464 :Password incorrect")
return return
# Make sure we are not connected to Bancho # Make sure we are not connected to Bancho
token = glob.tokens.getTokenFromUsername(nick) token = glob.tokens.getTokenFromUsername(chat.fixUsernameForBancho(nick), True)
if token != None: if token is not None:
self.reply("433 * {} :Nickname is already in use".format(nick)) self.reply("433 * {} :Nickname is already in use".format(nick))
return return
# Make sure we are not already connected from IRC with that name
for _, value in self.server.clients.items():
if value.username == self.username and value != self:
self.reply("433 * {} :Nickname is already in use".format(nick))
return
# Everything seems fine, set username (nickname) # Everything seems fine, set username (nickname)
self.username = nick self.IRCUsername = nick # username for IRC
self.banchoUsername = chat.fixUsernameForBancho(self.IRCUsername) # username for bancho
# Disconnect other IRC clients from the same user
for _, value in self.server.clients.items():
if value.IRCUsername.lower() == self.IRCUsername.lower() and value != self:
print("DISCONNECTERINOOOOOOOOOOOOOOOOOOOOO")
value.disconnect(quitmsg="Connected from another client")
return
elif command == "USER": elif command == "USER":
# Ignore USER command, we use nickname only # Ignore USER command, we use nickname only
return return
@@ -305,9 +311,9 @@ class Client:
return return
# If we now have a valid username, connect to bancho and send IRC welcome stuff # If we now have a valid username, connect to bancho and send IRC welcome stuff
if self.username != "": if self.IRCUsername != "":
# Bancho connection # Bancho connection
chat.IRCConnect(self.username) chat.IRCConnect(self.banchoUsername)
# IRC reply # IRC reply
self.replyCode(1, "Welcome to the Internet Relay Network") self.replyCode(1, "Welcome to the Internet Relay Network")
@@ -320,7 +326,7 @@ class Client:
def quitHandler(self, command, arguments): def quitHandler(self, command, arguments):
"""QUIT command handler""" """QUIT command handler"""
self.disconnect(self.username if len(arguments) < 1 else arguments[0]) self.disconnect(self.IRCUsername if len(arguments) < 1 else arguments[0])
def joinHandler(self, command, arguments): def joinHandler(self, command, arguments):
"""JOIN command handler""" """JOIN command handler"""
@@ -329,8 +335,8 @@ class Client:
return return
# Get bancho token object # Get bancho token object
token = glob.tokens.getTokenFromUsername(self.username) token = glob.tokens.getTokenFromUsername(self.banchoUsername)
if token == None: if token is None:
return return
# TODO: Part all channels # TODO: Part all channels
@@ -354,13 +360,13 @@ class Client:
continue continue
# Attempt to join the channel # Attempt to join the channel
response = chat.IRCJoinChannel(self.username, channel) response = chat.IRCJoinChannel(self.banchoUsername, channel)
if response == 0: if response == 0:
# Joined successfully # Joined successfully
self.joinedChannels.append(channel) self.joinedChannels.append(channel)
# Let everyone in this channel know that we've joined # Let everyone in this channel know that we've joined
self.messageChannel(channel, "{} JOIN".format(self.username), channel, True) self.messageChannel(channel, "{} JOIN".format(self.IRCUsername), channel, True)
# Send channel description (topic) # Send channel description (topic)
description = glob.channels.channels[channel].description description = glob.channels.channels[channel].description
@@ -374,12 +380,12 @@ class Client:
usernames = [] usernames = []
for user in users: for user in users:
token = glob.tokens.getTokenFromUserID(user) token = glob.tokens.getTokenFromUserID(user)
if token == None: if token is None:
continue continue
usernames.append(token.username) usernames.append(chat.fixUsernameForIRC(token.username))
usernames = " ".join(usernames) usernames = " ".join(usernames)
# Send IRC users lis # Send IRC users list
self.replyCode(353, usernames, channel="= {}".format(channel)) self.replyCode(353, usernames, channel="= {}".format(channel))
self.replyCode(366, "End of NAMES list", channel=channel) self.replyCode(366, "End of NAMES list", channel=channel)
elif response == 403: elif response == 403:
@@ -394,8 +400,8 @@ class Client:
return return
# Get bancho token object # Get bancho token object
token = glob.tokens.getTokenFromUsername(self.username) token = glob.tokens.getTokenFromUsername(self.banchoUsername)
if token == None: if token is None:
return return
# Get channels to part list # Get channels to part list
@@ -409,7 +415,7 @@ class Client:
continue continue
# Attempt to part the channel # Attempt to part the channel
response = chat.IRCPartChannel(self.username, channel) response = chat.IRCPartChannel(self.banchoUsername, channel)
if response == 0: if response == 0:
# No errors, remove channel from joinedChannels # No errors, remove channel from joinedChannels
self.joinedChannels.remove(channel) self.joinedChannels.remove(channel)
@@ -429,36 +435,40 @@ class Client:
if len(arguments) == 1: if len(arguments) == 1:
self.replyCode(412, "No text to send") self.replyCode(412, "No text to send")
return return
recipient = arguments[0] recipientIRC = arguments[0]
message = arguments[1] message = arguments[1]
# Send the message to bancho and reply # Send the message to bancho and reply
response = chat.sendMessage(self.username, recipient, message, toIRC=False) if not recipientIRC.startswith("#"):
print("PMPMPM!!!!!!!!!!")
recipientBancho = chat.fixUsernameForBancho(recipientIRC)
else:
recipientBancho = recipientIRC
response = chat.sendMessage(self.banchoUsername, recipientBancho, message, toIRC=False)
if response == 404: if response == 404:
self.replyCode(404, "Cannot send to channel", channel=recipient) self.replyCode(404, "Cannot send to channel", channel=recipientIRC)
return return
elif response == 403: elif response == 403:
self.replyCode(403, "No such channel", channel=recipient) self.replyCode(403, "No such channel", channel=recipientIRC)
return return
elif response == 401: elif response == 401:
self.replyCode(401, "No such nick/channel", channel=recipient) self.replyCode(401, "No such nick/channel", channel=recipientIRC)
return return
# Send the message to IRC and bancho # Send the message to IRC and bancho
if recipient.startswith("#"): if recipientIRC.startswith("#"):
# Public message (IRC) # Public message (IRC)
if recipient not in glob.channels.channels: if recipientIRC not in glob.channels.channels:
self.replyCode(401, "No such nick/channel", channel=recipient) self.replyCode(401, "No such nick/channel", channel=recipientIRC)
return return
for _, value in self.server.clients.items(): for _, value in self.server.clients.items():
if recipient in value.joinedChannels and value != self: if recipientIRC in value.joinedChannels and value != self:
value.message(":{} PRIVMSG {} :{}".format(self.username, recipient, message)) value.message(":{} PRIVMSG {} :{}".format(self.IRCUsername, recipientIRC, message))
#self.messageChannel(recipient, command, "{} :{}".format(recipient, message))
else: else:
# Private message (IRC) # Private message (IRC)
for _, value in self.server.clients.items(): for _, value in self.server.clients.items():
if value.username == recipient: if value.IRCUsername == recipientIRC:
value.message(":{} PRIVMSG {} :{}".format(self.username, recipient, message)) value.message(":{} PRIVMSG {} :{}".format(self.IRCUsername, recipientIRC, message))
def motdHandler(self, command, arguments): def motdHandler(self, command, arguments):
"""MOTD command handler""" """MOTD command handler"""
@@ -515,21 +525,22 @@ class Client:
class Server: class Server:
def __init__(self, port): def __init__(self, port):
self.host = socket.getfqdn("127.0.0.1")[:63] #self.host = socket.getfqdn("127.0.0.1")[:63]
self.host = glob.conf.config["irc"]["hostname"]
self.port = port 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 :("] 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): def forceDisconnection(self, username, isBanchoUsername=True):
""" """
Disconnect someone from IRC if connected Disconnect someone from IRC if connected
username -- victim username -- victim
""" """
for _, value in self.clients.items(): for _, value in self.clients.items():
if value.username == username: if (value.IRCUsername == username and not isBanchoUsername) or (value.banchoUsername == username and isBanchoUsername):
value.disconnect(callLogout=False) value.disconnect(callLogout=False)
break# or dictionary changes size during iteration break # or dictionary changes size during iteration
def banchoJoinChannel(self, username, channel): def banchoJoinChannel(self, username, channel):
""" """
@@ -538,6 +549,7 @@ class Server:
username -- username of bancho user username -- username of bancho user
channel -- joined channel name channel -- joined channel name
""" """
username = chat.fixUsernameForIRC(username)
for _, value in self.clients.items(): for _, value in self.clients.items():
if channel in value.joinedChannels: if channel in value.joinedChannels:
value.message(":{} JOIN {}".format(username, channel)) value.message(":{} JOIN {}".format(username, channel))
@@ -549,6 +561,7 @@ class Server:
username -- username of bancho user username -- username of bancho user
channel -- joined channel name channel -- joined channel name
""" """
username = chat.fixUsernameForIRC(username)
for _, value in self.clients.items(): for _, value in self.clients.items():
if channel in value.joinedChannels: if channel in value.joinedChannels:
value.message(":{} PART {}".format(username, channel)) value.message(":{} PART {}".format(username, channel))
@@ -561,15 +574,17 @@ class Server:
to -- receiver username to -- receiver username
message -- text of the message message -- text of the message
""" """
fro = chat.fixUsernameForIRC(fro)
to = chat.fixUsernameForIRC(to)
if to.startswith("#"): if to.startswith("#"):
# Public message # Public message
for _, value in self.clients.items(): for _, value in self.clients.items():
if to in value.joinedChannels and value.username != fro: if to in value.joinedChannels and value.IRCUsername != fro:
value.message(":{} PRIVMSG {} :{}".format(fro, to, message)) value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
else: else:
# Private message # Private message
for _, value in self.clients.items(): for _, value in self.clients.items():
if value.username == to and value.username != fro: if value.IRCUsername == to and value.IRCUsername != fro:
value.message(":{} PRIVMSG {} :{}".format(fro, to, message)) value.message(":{} PRIVMSG {} :{}".format(fro, to, message))
@@ -586,7 +601,7 @@ class Server:
def start(self): def start(self):
"""Start IRC server main loop""" """Start IRC server main loop"""
# Sentry # Sentry
if glob.sentry == True: if glob.sentry:
sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"]) sentryClient = raven.Client(glob.conf.config["sentry"]["ircdns"])
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -637,7 +652,7 @@ class Server:
lastAliveCheck = now lastAliveCheck = now
except: except:
log.error("[IRC] Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc())) log.error("[IRC] Unknown error!\n```\n{}\n{}```".format(sys.exc_info(), traceback.format_exc()))
if glob.sentry == True: if glob.sentry:
sentryClient.captureException() sentryClient.captureException()
def main(port=6667): def main(port=6667):

View File

@@ -1,5 +1,7 @@
# TODO: Rewrite this shit
from common import generalUtils
from objects import glob from objects import glob
from helpers import generalFunctions
class banchoConfig: class banchoConfig:
""" """
@@ -8,35 +10,34 @@ class banchoConfig:
config = {"banchoMaintenance": False, "freeDirect": True, "menuIcon": "", "loginNotification": ""} config = {"banchoMaintenance": False, "freeDirect": True, "menuIcon": "", "loginNotification": ""}
def __init__(self, __loadFromDB = True): def __init__(self, loadFromDB = True):
""" """
Initialize a banchoConfig object (and load bancho_settings from db) Initialize a banchoConfig object (and load bancho_settings from db)
[__loadFromDB -- if True, load values from db. If False, don't load values. Default: True] loadFromDB -- if True, load values from db. If False, don't load values. Optional.
""" """
if loadFromDB:
if __loadFromDB:
try: try:
self.loadSettings() self.loadSettings()
except: except:
raise raise
def loadSettings(self): def loadSettings(self):
""" """
(re)load bancho_settings from DB and set values in config array (re)load bancho_settings from DB and set values in config array
""" """
self.config["banchoMaintenance"] = generalUtils.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'")["value_int"])
self.config["banchoMaintenance"] = generalFunctions.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'bancho_maintenance'")["value_int"]) self.config["freeDirect"] = generalUtils.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'free_direct'")["value_int"])
self.config["freeDirect"] = generalFunctions.stringToBool(glob.db.fetch("SELECT value_int FROM bancho_settings WHERE name = 'free_direct'")["value_int"])
self.config["menuIcon"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'menu_icon'")["value_string"] self.config["menuIcon"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'menu_icon'")["value_string"]
self.config["loginNotification"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'login_notification'")["value_string"] self.config["loginNotification"] = glob.db.fetch("SELECT value_string FROM bancho_settings WHERE name = 'login_notification'")["value_string"]
def setMaintenance(self, __maintenance):
def setMaintenance(self, maintenance):
""" """
Turn on/off bancho maintenance mode. Write new value to db too Turn on/off bancho maintenance mode. Write new value to db too
__maintenance -- if True, turn on maintenance mode. If false, turn it off maintenance -- if True, turn on maintenance mode. If false, turn it off
""" """
self.config["banchoMaintenance"] = maintenance
self.config["banchoMaintenance"] = __maintenance glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(maintenance)])
glob.db.execute("UPDATE bancho_settings SET value_int = %s WHERE name = 'bancho_maintenance'", [int(__maintenance)])

View File

@@ -5,22 +5,21 @@ class channel:
A chat channel A chat channel
""" """
def __init__(self, __name, __description, __publicRead, __publicWrite, temp, hidden): def __init__(self, name, description, publicRead, publicWrite, temp, hidden):
""" """
Create a new chat channel object Create a new chat channel object
__name -- channel name name -- channel name
__description -- channel description description -- channel description
__publicRead -- bool, if true channel can be read by everyone, if false it can be read only by mods/admins 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 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 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 hidden -- if True, channel won't be shown in channels list
""" """
self.name = name
self.name = __name self.description = description
self.description = __description self.publicRead = publicRead
self.publicRead = __publicRead self.publicWrite = publicWrite
self.publicWrite = __publicWrite
self.moderated = False self.moderated = False
self.temp = temp self.temp = temp
self.connectedUsers = [999] # Fokabot is always connected to every channels (otherwise it doesn't show up in IRC users list) self.connectedUsers = [999] # Fokabot is always connected to every channels (otherwise it doesn't show up in IRC users list)
@@ -33,34 +32,29 @@ class channel:
elif self.name.startswith("#multi_"): elif self.name.startswith("#multi_"):
self.clientName = "#multiplayer" self.clientName = "#multiplayer"
def userJoin(self, userID):
def userJoin(self, __userID):
""" """
Add a user to connected users Add a user to connected users
__userID -- user ID that joined the channel userID -- user ID that joined the channel
""" """
if userID not in self.connectedUsers:
self.connectedUsers.append(userID)
if __userID not in self.connectedUsers: def userPart(self, userID):
self.connectedUsers.append(__userID)
def userPart(self, __userID):
""" """
Remove a user from connected users Remove a user from connected users
__userID -- user ID that left the channel userID -- user ID that left the channel
""" """
if userID in self.connectedUsers:
if __userID in self.connectedUsers: self.connectedUsers.remove(userID)
self.connectedUsers.remove(__userID)
# Remove temp channels if empty or there's only fokabot connected # Remove temp channels if empty or there's only fokabot connected
l = len(self.connectedUsers) l = len(self.connectedUsers)
if self.temp == True and ((l == 0) or (l == 1 and 999 in self.connectedUsers)): if self.temp == True and ((l == 0) or (l == 1 and 999 in self.connectedUsers)):
glob.channels.removeChannel(self.name) glob.channels.removeChannel(self.name)
def getConnectedUsers(self): def getConnectedUsers(self):
""" """
Get connected user IDs list Get connected user IDs list
@@ -69,7 +63,6 @@ class channel:
""" """
return self.connectedUsers return self.connectedUsers
def getConnectedUsersCount(self): def getConnectedUsersCount(self):
""" """
Count connected users Count connected users

View File

@@ -1,6 +1,7 @@
from objects import glob from common.log import logUtils as log
from objects import channel from objects import channel
from helpers import logHelper as log from objects import glob
class channelList: class channelList:
""" """
@@ -8,10 +9,8 @@ class channelList:
channels -- dictionary. key: channel name, value: channel object channels -- dictionary. key: channel name, value: channel object
""" """
channels = {} channels = {}
def loadChannels(self): def loadChannels(self):
""" """
Load chat channels from db and add them to channels dictionary Load chat channels from db and add them to channels dictionary
@@ -27,7 +26,6 @@ class channelList:
publicWrite = True if i["public_write"] == 1 else False publicWrite = True if i["public_write"] == 1 else False
self.addChannel(i["name"], i["description"], publicRead, publicWrite) self.addChannel(i["name"], i["description"], publicRead, publicWrite)
def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False): def addChannel(self, name, description, publicRead, publicWrite, temp = False, hidden = False):
""" """
Add a channel object to channels dictionary Add a channel object to channels dictionary
@@ -42,7 +40,6 @@ class channelList:
self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden) self.channels[name] = channel.channel(name, description, publicRead, publicWrite, temp, hidden)
log.info("Created channel {}".format(name)) log.info("Created channel {}".format(name))
def addTempChannel(self, name): def addTempChannel(self, name):
""" """
Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel Add a temporary channel (like #spectator or #multiplayer), gets deleted when there's no one in the channel

View File

@@ -16,7 +16,7 @@ class chatFilters:
for line in data: for line in data:
# Get old/new word and save it in dictionary # Get old/new word and save it in dictionary
lineSplit = line.split("=") lineSplit = line.split("=")
self.filters[lineSplit[0]] = lineSplit[1].replace("\n", "") self.filters[lineSplit[0].lower()] = lineSplit[1].replace("\n", "")
def filterMessage(self, message): def filterMessage(self, message):
# Split words by spaces # Split words by spaces
@@ -24,9 +24,11 @@ class chatFilters:
# Check each word # Check each word
for word in messageTemp: for word in messageTemp:
lowerWord = word.lower()
# If the word is filtered, replace it # If the word is filtered, replace it
if word in self.filters: if lowerWord in self.filters:
message = message.replace(word, self.filters[word]) message = message.replace(word, self.filters[lowerWord])
# Return filtered message # Return filtered message
return message return message

View File

@@ -1,20 +0,0 @@
import threading
class fileLocks:
def __init__(self):
# Dictionary containing threading.Lock s
self.locks = {}
def lockFile(self, fileName):
if fileName in self.locks:
# Acquire existing lock
self.locks[fileName].acquire()
else:
# Create new lock and acquire it
self.locks[fileName] = threading.Lock()
self.locks[fileName].acquire()
def unlockFile(self, fileName):
if fileName in self.locks:
# Release lock if it exists
self.locks[fileName].release()

View File

@@ -1,32 +1,25 @@
"""FokaBot related functions""" """FokaBot related functions"""
from helpers import userHelper
from objects import glob
from constants import actions
from constants import serverPackets
from constants import fokabotCommands
import re import re
from helpers import generalFunctions
from common import generalUtils
from common.constants import actions
from common.ripple import userUtils
from constants import fokabotCommands
from constants import serverPackets
from objects import glob
# Tillerino np regex, compiled only once to increase performance # Tillerino np regex, compiled only once to increase performance
npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)") npRegex = re.compile("^https?:\\/\\/osu\\.ppy\\.sh\\/b\\/(\\d*)")
def connect(): def connect():
"""Add FokaBot to connected users and send userpanel/stats packet to everyone""" """Add FokaBot to connected users and send userpanel/stats packet to everyone"""
token = glob.tokens.addToken(999) token = glob.tokens.addToken(999)
token.actionID = actions.idle token.actionID = actions.IDLE
glob.tokens.enqueueAll(serverPackets.userPanel(999)) glob.streams.broadcast("main", serverPackets.userPanel(999))
####glob.tokens.enqueueAll(serverPackets.userStats(999)) glob.streams.broadcast("main", serverPackets.userStats(999))
# NOTE: Debug thing to set all users as connected
#users = glob.db.fetchAll("SELECT id FROM users")
#for i in users:
# t = glob.tokens.addToken(i["id"])
# t.actionID = actions.idle
def disconnect(): def disconnect():
"""Remove FokaBot from connected users""" """Remove FokaBot from connected users"""
glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999)) glob.tokens.deleteToken(glob.tokens.getTokenFromUserID(999))
def fokabotResponse(fro, chan, message): def fokabotResponse(fro, chan, message):
@@ -39,17 +32,16 @@ def fokabotResponse(fro, chan, message):
return -- fokabot's response string or False return -- fokabot's response string or False
""" """
for i in fokabotCommands.commands: for i in fokabotCommands.commands:
# Loop though all commands # Loop though all commands
#if i["trigger"] in message: #if i["trigger"] in message:
if generalFunctions.strContains(message, i["trigger"]): if generalUtils.strContains(message, i["trigger"]):
# message has triggered a command # message has triggered a command
# Make sure the user has right permissions # Make sure the user has right permissions
if i["privileges"] != None: if i["privileges"] is not None:
# Rank = x # Rank = x
if userHelper.getPrivileges(userHelper.getID(fro)) & i["privileges"] == 0: if userUtils.getPrivileges(userUtils.getID(fro)) & i["privileges"] == 0:
return False return False
# Check argument number # Check argument number
@@ -58,7 +50,7 @@ def fokabotResponse(fro, chan, message):
return "Wrong syntax: {} {}".format(i["trigger"], i["syntax"]) return "Wrong syntax: {} {}".format(i["trigger"], i["syntax"])
# Return response or execute callback # Return response or execute callback
if i["callback"] == None: if i["callback"] is None:
return i["response"] return i["response"]
else: else:
return i["callback"](fro, chan, message[1:]) return i["callback"](fro, chan, message[1:])

View File

@@ -1,9 +1,14 @@
"""Global objects and variables""" """Global objects and variables"""
from objects import tokenList import time
from common.ddog import datadogClient
from common.files import fileBuffer, fileLocks
from objects import channelList from objects import channelList
from objects import matchList from objects import matchList
from objects import fileLocks from objects import streamList
from objects import tokenList
from common.web import schiavo
try: try:
with open("version") as f: with open("version") as f:
@@ -13,6 +18,8 @@ try:
except: except:
VERSION = "¯\_(xd)_/¯" VERSION = "¯\_(xd)_/¯"
DATADOG_PREFIX = "peppy"
application = None
db = None db = None
conf = None conf = None
banchoConf = None banchoConf = None
@@ -21,14 +28,26 @@ channels = channelList.channelList()
matches = matchList.matchList() matches = matchList.matchList()
restarting = False restarting = False
fLocks = fileLocks.fileLocks() fLocks = fileLocks.fileLocks()
fileBuffers = fileBuffer.buffersList()
schiavo = schiavo.schiavo()
dog = datadogClient.datadogClient()
verifiedCache = {} verifiedCache = {}
cloudflare = False cloudflare = False
chatFilters = None chatFilters = None
userIDCache = {}
pool = None
ircServer = None
busyThreads = 0
debug = False debug = False
outputRequestTime = False outputRequestTime = False
outputPackets = False outputPackets = False
discord = False
gzip = False gzip = False
localize = False localize = False
sentry = False sentry = False
irc = False
startTime = int(time.time())
streams = streamList.streamList()

View File

@@ -1,24 +0,0 @@
'''
import threading
class task:
def __init__(self, function, args = (), kwargs = {}):
self.function = function
self.args = args
self.kwargs = kwargs
class logThread:
def __init__(self):
self.thread = threading.Thread()
self.queue = []
def enqueue(self, function, args = (), kwargs = {}):
self.queue.append(task(function, args, kwargs))
def run(self):
for i in self.queue:
self.thread = threading.Thread(i.function, i.args, i.kwargs)
self.thread.run()
self.thread.join()
self.queue = []
'''

View File

@@ -1,174 +1,161 @@
# TODO: Enqueue all import copy
from constants import gameModes from common.log import logUtils as log
from constants import dataTypes
from constants import matchModModes
from constants import matchScoringTypes from constants import matchScoringTypes
from constants import matchTeamTypes from constants import matchTeamTypes
from constants import matchModModes
from constants import slotStatuses
from objects import glob
from constants import serverPackets
from constants import dataTypes
from constants import matchTeams from constants import matchTeams
from helpers import logHelper as log from constants import serverPackets
from constants import slotStatuses
from helpers import chatHelper as chat from helpers import chatHelper as chat
from objects import glob
class slot:
def __init__(self):
self.status = slotStatuses.free
self.team = 0
self.userID = -1
self.user = None
self.mods = 0
self.loaded = False
self.skip = False
self.complete = False
class match: class match:
"""Multiplayer match object""" """Multiplayer match object"""
matchID = 0 def __init__(self, matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
inProgress = False
mods = 0
matchName = ""
matchPassword = ""
beatmapName = ""
beatmapID = 0
beatmapMD5 = ""
slots = [] # list of dictionaries {"status": 0, "team": 0, "userID": -1, "mods": 0, "loaded": False, "skip": False, "complete": False}
hostUserID = 0
gameMode = gameModes.std
matchScoringType = matchScoringTypes.score
matchTeamType = matchTeamTypes.headToHead
matchModMode = matchModModes.normal
seed = 0
def __init__(self, __matchID, __matchName, __matchPassword, __beatmapID, __beatmapName, __beatmapMD5, __gameMode, __hostUserID):
""" """
Create a new match object Create a new match object
__matchID -- match progressive identifier matchID -- match progressive identifier
__matchName -- match name, string matchName -- match name, string
__matchPassword -- match md5 password. Leave empty for no password matchPassword -- match md5 password. Leave empty for no password
__beatmapID -- beatmap ID beatmapID -- beatmap ID
__beatmapName -- beatmap name, string beatmapName -- beatmap name, string
__beatmapMD5 -- beatmap md5 hash, string beatmapMD5 -- beatmap md5 hash, string
__gameMode -- game mode ID. See gameModes.py gameMode -- game mode ID. See gameModes.py
__hostUserID -- user id of the host hostUserID -- user id of the host
""" """
self.matchID = __matchID self.matchID = matchID
self.streamName = "multi/{}".format(self.matchID)
self.playingStreamName = "{}/playing".format(self.streamName)
self.inProgress = False self.inProgress = False
self.mods = 0 self.mods = 0
self.matchName = __matchName self.matchName = matchName
self.matchPassword = __matchPassword self.matchPassword = matchPassword
self.beatmapID = __beatmapID self.beatmapID = beatmapID
self.beatmapName = __beatmapName self.beatmapName = beatmapName
self.beatmapMD5 = __beatmapMD5 self.beatmapMD5 = beatmapMD5
self.hostUserID = __hostUserID self.hostUserID = hostUserID
self.gameMode = __gameMode self.gameMode = gameMode
self.matchScoringTypes = matchScoringTypes.score # default values self.matchScoringType = matchScoringTypes.score # default values
self.matchTeamType = matchTeamTypes.headToHead # default value self.matchTeamType = matchTeamTypes.headToHead # default value
self.matchModMode = matchModModes.normal # default value self.matchModMode = matchModModes.normal # default value
self.seed = 0 self.seed = 0
self.matchDataCache = bytes()
# Create all slots and reset them # Create all slots and reset them
self.slots = [] self.slots = []
for _ in range(0,16): for _ in range(0,16):
self.slots.append({"status": slotStatuses.free, "team": 0, "userID": -1, "mods": 0, "loaded": False, "skip": False, "complete": False}) self.slots.append(slot())
# Create streams
glob.streams.add(self.streamName)
glob.streams.add(self.playingStreamName)
# Create #multiplayer channel # Create #multiplayer channel
glob.channels.addTempChannel("#multi_{}".format(self.matchID)) glob.channels.addTempChannel("#multi_{}".format(self.matchID))
def getMatchData(self): def getMatchData(self):
""" """
Return binary match data structure for packetHelper Return binary match data structure for packetHelper
""" """
# General match info # General match info
# TODO: Test without safe copy, the error might have been caused by outdated python bytecode cache
safeMatch = copy.deepcopy(self)
struct = [ struct = [
[self.matchID, dataTypes.uInt16], [safeMatch.matchID, dataTypes.UINT16],
[int(self.inProgress), dataTypes.byte], [int(safeMatch.inProgress), dataTypes.BYTE],
[0, dataTypes.byte], [0, dataTypes.BYTE],
[self.mods, dataTypes.uInt32], [safeMatch.mods, dataTypes.UINT32],
[self.matchName, dataTypes.string], [safeMatch.matchName, dataTypes.STRING],
[self.matchPassword, dataTypes.string], [safeMatch.matchPassword, dataTypes.STRING],
[self.beatmapName, dataTypes.string], [safeMatch.beatmapName, dataTypes.STRING],
[self.beatmapID, dataTypes.uInt32], [safeMatch.beatmapID, dataTypes.UINT32],
[self.beatmapMD5, dataTypes.string], [safeMatch.beatmapMD5, dataTypes.STRING],
] ]
# Slots status IDs, always 16 elements # Slots status IDs, always 16 elements
for i in range(0,16): for i in range(0,16):
struct.append([self.slots[i]["status"], dataTypes.byte]) struct.append([safeMatch.slots[i].status, dataTypes.BYTE])
# Slot teams, always 16 elements # Slot teams, always 16 elements
for i in range(0,16): for i in range(0,16):
struct.append([self.slots[i]["team"], dataTypes.byte]) struct.append([safeMatch.slots[i].team, dataTypes.BYTE])
# Slot user ID. Write only if slot is occupied # Slot user ID. Write only if slot is occupied
for i in range(0,16): for i in range(0,16):
uid = self.slots[i]["userID"] if safeMatch.slots[i].user is not None and safeMatch.slots[i].user in glob.tokens.tokens:
if uid > -1: struct.append([glob.tokens.tokens[safeMatch.slots[i].user].userID, dataTypes.UINT32])
struct.append([uid, dataTypes.uInt32])
# Other match data # Other match data
struct.extend([ struct.extend([
[self.hostUserID, dataTypes.sInt32], [safeMatch.hostUserID, dataTypes.SINT32],
[self.gameMode, dataTypes.byte], [safeMatch.gameMode, dataTypes.BYTE],
[self.matchScoringType, dataTypes.byte], [safeMatch.matchScoringType, dataTypes.BYTE],
[self.matchTeamType, dataTypes.byte], [safeMatch.matchTeamType, dataTypes.BYTE],
[self.matchModMode, dataTypes.byte], [safeMatch.matchModMode, dataTypes.BYTE],
]) ])
# Slot mods if free mod is enabled # Slot mods if free mod is enabled
if self.matchModMode == matchModModes.freeMod: if safeMatch.matchModMode == matchModModes.freeMod:
for i in range(0,16): for i in range(0,16):
struct.append([self.slots[i]["mods"], dataTypes.uInt32]) struct.append([safeMatch.slots[i].mods, dataTypes.UINT32])
# Seed idk # Seed idk
struct.append([self.seed, dataTypes.uInt32]) # TODO: Implement this, it should be used for mania "random" mod
struct.append([safeMatch.seed, dataTypes.UINT32])
return struct return struct
def setHost(self, newHost): def setHost(self, newHost):
""" """
Set room host to newHost and send him host packet Set room host to newHost and send him host packet
newHost -- new host userID newHost -- new host userID
""" """
slotID = self.getUserSlotID(newHost)
if slotID is None or self.slots[slotID].user not in glob.tokens.tokens:
return
token = glob.tokens.tokens[self.slots[slotID].user]
self.hostUserID = newHost self.hostUserID = newHost
token.enqueue(serverPackets.matchTransferHost())
self.sendUpdates()
log.info("MPROOM{}: {} is now the host".format(self.matchID, token.username))
# Send host packet to new host def setSlot(self, slotID, status = None, team = None, user = "", mods = None, loaded = None, skip = None, complete = None):
token = glob.tokens.getTokenFromUserID(newHost) #self.setSlot(i, slotStatuses.notReady, 0, user, 0)
if token != None: if status is not None:
token.enqueue(serverPackets.matchTransferHost()) self.slots[slotID].status = status
log.info("MPROOM{}: {} is now the host".format(self.matchID, newHost)) if team is not None:
self.slots[slotID].team = team
def setSlot(self, slotID, slotStatus = None, slotTeam = None, slotUserID = None, slotMods = None, slotLoaded = None, slotSkip = None, slotComplete = None): if user is not "":
""" self.slots[slotID].user = user
Set a slot to a specific userID and status
slotID -- id of that slot (0-15) if mods is not None:
slotStatus -- see slotStatuses.py self.slots[slotID].mods = mods
slotTeam -- team id
slotUserID -- user ID of user in that slot
slotMods -- mods enabled in that slot. 0 if not free mod.
slotLoaded -- loaded status True/False
slotSkip -- skip status True/False
slotComplete -- completed status True/False
If Null is passed, that value won't be edited if loaded is not None:
""" self.slots[slotID].loaded = loaded
if slotStatus != None:
self.slots[slotID]["status"] = slotStatus
if slotTeam != None: if skip is not None:
self.slots[slotID]["team"] = slotTeam self.slots[slotID].skip = skip
if slotUserID != None:
self.slots[slotID]["userID"] = slotUserID
if slotMods != None:
self.slots[slotID]["mods"] = slotMods
if slotLoaded != None:
self.slots[slotID]["loaded"] = slotLoaded
if slotSkip != None:
self.slots[slotID]["skip"] = slotSkip
if slotComplete != None:
self.slots[slotID]["complete"] = slotComplete
if complete is not None:
self.slots[slotID].complete = complete
def setSlotMods(self, slotID, mods): def setSlotMods(self, slotID, mods):
""" """
@@ -178,11 +165,10 @@ class match:
mods -- new mods mods -- new mods
""" """
# Set new slot data and send update # Set new slot data and send update
self.setSlot(slotID, None, None, None, mods) self.setSlot(slotID, mods=mods)
self.sendUpdate() self.sendUpdates()
log.info("MPROOM{}: Slot{} mods changed to {}".format(self.matchID, slotID, mods)) log.info("MPROOM{}: Slot{} mods changed to {}".format(self.matchID, slotID, mods))
def toggleSlotReady(self, slotID): def toggleSlotReady(self, slotID):
""" """
Switch slotID ready/not ready status Switch slotID ready/not ready status
@@ -191,14 +177,14 @@ class match:
slotID -- slot number slotID -- slot number
""" """
# Update ready status and setnd update # Update ready status and setnd update
oldStatus = self.slots[slotID]["status"] oldStatus = self.slots[slotID].status
if oldStatus == slotStatuses.ready: if oldStatus == slotStatuses.ready:
newStatus = slotStatuses.notReady newStatus = slotStatuses.notReady
else: else:
newStatus = slotStatuses.ready newStatus = slotStatuses.ready
self.setSlot(slotID, newStatus, None, None, None) self.setSlot(slotID, newStatus)
self.sendUpdate() self.sendUpdates()
log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID]["status"])) log.info("MPROOM{}: Slot{} changed ready status to {}".format(self.matchID, slotID, self.slots[slotID].status))
def toggleSlotLock(self, slotID): def toggleSlotLock(self, slotID):
""" """
@@ -207,26 +193,21 @@ class match:
slotID -- slot number slotID -- slot number
""" """
# Get token of user in that slot (if there's someone)
if self.slots[slotID]["userID"] > -1:
token = glob.tokens.getTokenFromUserID(self.slots[slotID]["userID"])
else:
token = None
# Check if slot is already locked # Check if slot is already locked
if self.slots[slotID]["status"] == slotStatuses.locked: if self.slots[slotID].status == slotStatuses.locked:
newStatus = slotStatuses.free newStatus = slotStatuses.free
else: 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:
glob.tokens.tokens[self.slots[slotID].user].enqueue(serverPackets.updateMatch(self.matchID))
# Set new slot status # Set new slot status
self.setSlot(slotID, newStatus, 0, -1, 0) self.setSlot(slotID, status=newStatus, team=0, user=None, mods=0)
if token != None:
# Send updated settings to kicked user, so he returns to lobby
token.enqueue(serverPackets.updateMatch(self.matchID))
# Send updates to everyone else # Send updates to everyone else
self.sendUpdate() 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): def playerLoaded(self, userID):
@@ -236,37 +217,30 @@ class match:
userID -- ID of user userID -- ID of user
""" """
slotID = self.getUserSlotID(userID) slotID = self.getUserSlotID(userID)
if slotID == None: if slotID is None:
return return
# Set loaded to True # Set loaded to True
self.slots[slotID]["loaded"] = True self.slots[slotID].loaded = True
log.info("MPROOM{}: User {} loaded".format(self.matchID, userID)) log.info("MPROOM{}: User {} loaded".format(self.matchID, userID))
# Check all loaded # Check all loaded
total = 0 total = 0
loaded = 0 loaded = 0
for i in range(0,16): for i in range(0,16):
if self.slots[i]["status"] == slotStatuses.playing: if self.slots[i].status == slotStatuses.playing:
total+=1 total+=1
if self.slots[i]["loaded"] == True: if self.slots[i].loaded:
loaded+=1 loaded+=1
if total == loaded: if total == loaded:
self.allPlayersLoaded() self.allPlayersLoaded()
def allPlayersLoaded(self): def allPlayersLoaded(self):
"""Send allPlayersLoaded packet to every playing usr in match""" """Send allPlayersLoaded packet to every playing usr in match"""
for i in range(0,16): glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersLoaded())
if self.slots[i]["userID"] > -1 and self.slots[i]["status"] == slotStatuses.playing:
token = glob.tokens.getTokenFromUserID(self.slots[i]["userID"])
if token != None:
token.enqueue(serverPackets.allPlayersLoaded())
log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID)) log.info("MPROOM{}: All players loaded! Match starting...".format(self.matchID))
def playerSkip(self, userID): def playerSkip(self, userID):
""" """
Set a player skip status to True Set a player skip status to True
@@ -274,28 +248,24 @@ class match:
userID -- ID of user userID -- ID of user
""" """
slotID = self.getUserSlotID(userID) slotID = self.getUserSlotID(userID)
if slotID == None: if slotID is None:
return return
# Set skip to True # Set skip to True
self.slots[slotID]["skip"] = True self.slots[slotID].skip = True
log.info("MPROOM{}: User {} skipped".format(self.matchID, userID)) log.info("MPROOM{}: User {} skipped".format(self.matchID, userID))
# Send skip packet to every playing useR # Send skip packet to every playing user
for i in range(0,16): #glob.streams.broadcast(self.playingStreamName, serverPackets.playerSkipped(glob.tokens.tokens[self.slots[slotID].user].userID))
uid = self.slots[i]["userID"] glob.streams.broadcast(self.playingStreamName, serverPackets.playerSkipped(slotID))
if self.slots[i]["status"] == slotStatuses.playing and uid > -1:
token = glob.tokens.getTokenFromUserID(uid)
if token != None:
token.enqueue(serverPackets.playerSkipped(uid))
# Check all skipped # Check all skipped
total = 0 total = 0
skipped = 0 skipped = 0
for i in range(0,16): for i in range(0,16):
if self.slots[i]["status"] == slotStatuses.playing: if self.slots[i].status == slotStatuses.playing:
total+=1 total+=1
if self.slots[i]["skip"] == True: if self.slots[i].skip:
skipped+=1 skipped+=1
if total == skipped: if total == skipped:
@@ -303,12 +273,7 @@ class match:
def allPlayersSkipped(self): def allPlayersSkipped(self):
"""Send allPlayersSkipped packet to every playing usr in match""" """Send allPlayersSkipped packet to every playing usr in match"""
for i in range(0,16): glob.streams.broadcast(self.playingStreamName, serverPackets.allPlayersSkipped())
if self.slots[i]["userID"] > -1 and self.slots[i]["status"] == slotStatuses.playing:
token = glob.tokens.getTokenFromUserID(self.slots[i]["userID"])
if token != None:
token.enqueue(serverPackets.allPlayersSkipped())
log.info("MPROOM{}: All players have skipped!".format(self.matchID)) log.info("MPROOM{}: All players have skipped!".format(self.matchID))
def playerCompleted(self, userID): def playerCompleted(self, userID):
@@ -318,9 +283,9 @@ class match:
userID -- ID of user userID -- ID of user
""" """
slotID = self.getUserSlotID(userID) slotID = self.getUserSlotID(userID)
if slotID == None: if slotID is None:
return return
self.setSlot(slotID, None, None, None, None, None, None, True) self.setSlot(slotID, complete=True)
# Console output # Console output
log.info("MPROOM{}: User {} has completed his play".format(self.matchID, userID)) log.info("MPROOM{}: User {} has completed his play".format(self.matchID, userID))
@@ -329,9 +294,9 @@ class match:
total = 0 total = 0
completed = 0 completed = 0
for i in range(0,16): for i in range(0,16):
if self.slots[i]["status"] == slotStatuses.playing: if self.slots[i].status == slotStatuses.playing:
total+=1 total+=1
if self.slots[i]["complete"] == True: if self.slots[i].complete:
completed+=1 completed+=1
if total == completed: if total == completed:
@@ -345,41 +310,36 @@ class match:
# Reset slots # Reset slots
for i in range(0,16): for i in range(0,16):
if self.slots[i]["userID"] > -1 and self.slots[i]["status"] == slotStatuses.playing: if self.slots[i].user is not None and self.slots[i].status == slotStatuses.playing:
self.slots[i]["status"] = slotStatuses.notReady self.slots[i].status = slotStatuses.notReady
self.slots[i]["loaded"] = False self.slots[i].loaded = False
self.slots[i]["skip"] = False self.slots[i].skip = False
self.slots[i]["complete"] = False self.slots[i].complete = False
# Send match update # Send match update
self.sendUpdate() self.sendUpdates()
# Send match complete # Send match complete
for i in range(0,16): glob.streams.broadcast(self.streamName, serverPackets.matchComplete())
if self.slots[i]["userID"] > -1:
token = glob.tokens.getTokenFromUserID(self.slots[i]["userID"]) # Destroy playing stream
if token != None: glob.streams.remove(self.playingStreamName)
token.enqueue(serverPackets.matchComplete())
# Console output # Console output
log.info("MPROOM{}: Match completed".format(self.matchID)) log.info("MPROOM{}: Match completed".format(self.matchID))
def getUserSlotID(self, userID): def getUserSlotID(self, userID):
""" """
Get slot ID occupied by userID 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): for i in range(0,16):
if self.slots[i]["userID"] == userID: 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:
return i return i
return None return None
def userJoin(self, userID): def userJoin(self, user):
""" """
Add someone to users in match Add someone to users in match
@@ -387,36 +347,40 @@ class match:
return -- True if join success, False if fail (room is full) 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)
# Find first free slot # Find first free slot
for i in range(0,16): for i in range(0,16):
if self.slots[i]["status"] == slotStatuses.free: if self.slots[i].status == slotStatuses.free:
# Occupy slot # Occupy slot
self.setSlot(i, slotStatuses.notReady, 0, userID, 0) self.setSlot(i, slotStatuses.notReady, 0, user.token, 0)
# Send updated match data # Send updated match data
self.sendUpdate() self.sendUpdates()
# Console output # Console output
log.info("MPROOM{}: {} joined the room".format(self.matchID, userID)) log.info("MPROOM{}: {} joined the room".format(self.matchID, user.username))
return True return True
return False return False
def userLeft(self, userID): def userLeft(self, user):
""" """
Remove someone from users in match Remove someone from users in match
userID -- user if of the user userID -- user if of the user
""" """
# Make sure the user is in room # Make sure the user is in room
slotID = self.getUserSlotID(userID) slotID = self.getUserSlotID(user.userID)
if slotID == None: if slotID is None:
return return
# Set that slot to free # Set that slot to free
self.setSlot(slotID, slotStatuses.free, 0, -1, 0) self.setSlot(slotID, slotStatuses.free, 0, None, 0)
# Check if everyone left # Check if everyone left
if self.countUsers() == 0: if self.countUsers() == 0:
@@ -426,20 +390,18 @@ class match:
return return
# Check if host left # Check if host left
if userID == self.hostUserID: if user.userID == self.hostUserID:
# Give host to someone else # Give host to someone else
for i in range(0,16): for i in range(0,16):
uid = self.slots[i]["userID"] if self.slots[i].user is not None and self.slots[i].user in glob.tokens.tokens:
if uid > -1: self.setHost(glob.tokens.tokens[self.slots[i].user].userID)
self.setHost(uid)
break break
# Send updated match data # Send updated match data
self.sendUpdate() self.sendUpdates()
# Console output # Console output
log.info("MPROOM{}: {} left the room".format(self.matchID, userID)) log.info("MPROOM{}: {} left the room".format(self.matchID, user.username))
def userChangeSlot(self, userID, newSlotID): def userChangeSlot(self, userID, newSlotID):
""" """
@@ -448,27 +410,27 @@ class match:
userID -- user that changed slot userID -- user that changed slot
newSlotID -- slot id of new slot newSlotID -- slot id of new slot
""" """
# Make sure the user is in room # Make sure the user is in room
oldSlotID = self.getUserSlotID(userID) oldSlotID = self.getUserSlotID(userID)
if oldSlotID == None: if oldSlotID is None:
return return
# Make sure there is no one inside new slot # Make sure there is no one inside new slot
if self.slots[newSlotID]["userID"] > -1: if self.slots[newSlotID].user is not None and self.slots[newSlotID].status != slotStatuses.free:
return return
# Get old slot data # Get old slot data
oldData = self.slots[oldSlotID].copy() #oldData = dill.copy(self.slots[oldSlotID])
oldData = copy.deepcopy(self.slots[oldSlotID])
# Free old slot # Free old slot
self.setSlot(oldSlotID, slotStatuses.free, 0, -1, 0) self.setSlot(oldSlotID, slotStatuses.free, 0, None, 0, False, False, False)
# Occupy new slot # Occupy new slot
self.setSlot(newSlotID, oldData["status"], oldData["team"], userID, oldData["mods"]) self.setSlot(newSlotID, oldData.status, oldData.team, oldData.user, oldData.mods)
# Send updated match data # Send updated match data
self.sendUpdate() self.sendUpdates()
# Console output # Console output
log.info("MPROOM{}: {} moved to slot {}".format(self.matchID, userID, newSlotID)) log.info("MPROOM{}: {} moved to slot {}".format(self.matchID, userID, newSlotID))
@@ -480,22 +442,21 @@ class match:
newPassword -- new password string newPassword -- new password string
""" """
self.matchPassword = newPassword self.matchPassword = newPassword
#if newPassword != "":
# self.matchPassword = generalUtils.stringMd5(newPassword)
#else:
# self.matchPassword = ""
# Send password change to every user in match # Send password change to every user in match
for i in range(0,16): glob.streams.broadcast(self.streamName, serverPackets.changeMatchPassword(self.matchPassword))
if self.slots[i]["userID"] > -1:
token = glob.tokens.getTokenFromUserID(self.slots[i]["userID"])
if token != None:
token.enqueue(serverPackets.changeMatchPassword(self.matchPassword))
# Send new match settings too # Send new match settings too
self.sendUpdate() self.sendUpdates()
# Console output # Console output
log.info("MPROOM{}: Password changed to {}".format(self.matchID, self.matchPassword)) log.info("MPROOM{}: Password changed to {}".format(self.matchID, self.matchPassword))
def changeMods(self, mods):
def changeMatchMods(self, mods):
""" """
Set match global mods Set match global mods
@@ -503,7 +464,7 @@ class match:
""" """
# Set new mods and send update # Set new mods and send update
self.mods = mods self.mods = mods
self.sendUpdate() self.sendUpdates()
log.info("MPROOM{}: Mods changed to {}".format(self.matchID, self.mods)) log.info("MPROOM{}: Mods changed to {}".format(self.matchID, self.mods))
def userHasBeatmap(self, userID, has = True): def userHasBeatmap(self, userID, has = True):
@@ -515,14 +476,14 @@ class match:
""" """
# Make sure the user is in room # Make sure the user is in room
slotID = self.getUserSlotID(userID) slotID = self.getUserSlotID(userID)
if slotID == None: if slotID is None:
return return
# Set slot # Set slot
self.setSlot(slotID, slotStatuses.noMap if not has else slotStatuses.notReady) self.setSlot(slotID, slotStatuses.noMap if not has else slotStatuses.notReady)
# Send updates # Send updates
self.sendUpdate() self.sendUpdates()
def transferHost(self, slotID): def transferHost(self, slotID):
""" """
@@ -531,16 +492,14 @@ class match:
slotID -- ID of slot slotID -- ID of slot
""" """
# Make sure there is someone in that slot # Make sure there is someone in that slot
uid = self.slots[slotID]["userID"] if self.slots[slotID].user is None or self.slots[slotID].user not in glob.tokens.tokens:
if uid == -1:
return return
# Transfer host # Transfer host
self.setHost(uid) self.setHost(glob.tokens.tokens[self.slots[slotID].user].userID)
# Send updates # Send updates
self.sendUpdate() self.sendUpdates()
def playerFailed(self, userID): def playerFailed(self, userID):
""" """
@@ -550,21 +509,15 @@ class match:
""" """
# Make sure the user is in room # Make sure the user is in room
slotID = self.getUserSlotID(userID) slotID = self.getUserSlotID(userID)
if slotID == None: if slotID is None:
return return
# Send packet to everyone # Send packet to everyone
for i in range(0,16): glob.streams.broadcast(self.playingStreamName, serverPackets.playerFailed(slotID))
uid = self.slots[i]["userID"]
if uid > -1:
token = glob.tokens.getTokenFromUserID(uid)
if token != None:
token.enqueue(serverPackets.playerFailed(slotID))
# Console output # Console output
log.info("MPROOM{}: {} has failed!".format(self.matchID, userID)) log.info("MPROOM{}: {} has failed!".format(self.matchID, userID))
def invite(self, fro, to): def invite(self, fro, to):
""" """
Fro invites to in this match. Fro invites to in this match.
@@ -576,7 +529,7 @@ class match:
# Get tokens # Get tokens
froToken = glob.tokens.getTokenFromUserID(fro) froToken = glob.tokens.getTokenFromUserID(fro)
toToken = glob.tokens.getTokenFromUserID(to) toToken = glob.tokens.getTokenFromUserID(to)
if froToken == None or toToken == None: if froToken is None or toToken is None:
return return
# FokaBot is too busy # FokaBot is too busy
@@ -587,19 +540,16 @@ class match:
message = "Come join my multiplayer match: \"[osump://{}/{} {}]\"".format(self.matchID, self.matchPassword.replace(" ", "_"), self.matchName) message = "Come join my multiplayer match: \"[osump://{}/{} {}]\"".format(self.matchID, self.matchPassword.replace(" ", "_"), self.matchName)
chat.sendMessage(token=froToken, to=toToken.username, message=message) chat.sendMessage(token=froToken, to=toToken.username, message=message)
def countUsers(self): def countUsers(self):
""" """
Return how many players are in that match Return how many players are in that match
return -- number of users return -- number of users
""" """
c = 0 c = 0
for i in range(0,16): for i in range(0,16):
if self.slots[i]["userID"] > -1: if self.slots[i].user is not None:
c+=1 c+=1
return c return c
def changeTeam(self, userID): def changeTeam(self, userID):
@@ -610,29 +560,21 @@ class match:
""" """
# Make sure the user is in room # Make sure the user is in room
slotID = self.getUserSlotID(userID) slotID = self.getUserSlotID(userID)
if slotID == None: if slotID is None:
return return
# Update slot and send update # 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.setSlot(slotID, None, newTeam)
self.sendUpdate() self.sendUpdates()
def sendUpdates(self):
self.matchDataCache = serverPackets.updateMatch(self.matchID)
def sendUpdate(self): if self.matchDataCache is not None:
# Send to users in room glob.streams.broadcast(self.streamName, self.matchDataCache)
for i in range(0,16): glob.streams.broadcast("lobby", self.matchDataCache)
if self.slots[i]["userID"] > -1: else:
token = glob.tokens.getTokenFromUserID(self.slots[i]["userID"]) log.error("MPROOM{}: Can't send match update packet, match data is None!!!".format(self.matchID))
if token != None:
token.enqueue(serverPackets.updateMatch(self.matchID))
# Send to users in lobby
for i in glob.matches.usersInLobby:
token = glob.tokens.getTokenFromUserID(i)
if token != None:
token.enqueue(serverPackets.updateMatch(self.matchID))
def checkTeams(self): def checkTeams(self):
""" """
@@ -640,19 +582,46 @@ class match:
return -- True if valid, False if invalid return -- True if valid, False if invalid
""" """
if match.matchTeamType != matchTeamTypes.teamVs or matchTeamTypes != matchTeamTypes.tagTeamVs: if self.matchTeamType != matchTeamTypes.teamVs or self.matchTeamType != matchTeamTypes.tagTeamVs:
# Teams are always valid if we have no teams # Teams are always valid if we have no teams
return True return True
# We have teams, check if they are valid # We have teams, check if they are valid
firstTeam = -1 firstTeam = -1
for i in range(0,16): for i in range(0,16):
if self.slots[i]["userID"] > -1 and (self.slots[i]["status"]&slotStatuses.noMap) == 0: if self.slots[i].user is not None and (self.slots[i].status & slotStatuses.noMap) == 0:
if firstTeam == -1: if firstTeam == -1:
firstTeam = self.slots[i]["team"] firstTeam = self.slots[i].team
elif firstTeam != self.slots[i]["teams"]: elif firstTeam != self.slots[i].team:
log.info("MPROOM{}: Teams are valid".format(self.matchID)) log.info("MPROOM{}: Teams are valid".format(self.matchID))
return True return True
log.warning("MPROOM{}: Invalid teams!".format(self.matchID)) log.warning("MPROOM{}: Invalid teams!".format(self.matchID))
return False return False
def start(self):
# Make sure we have enough players
if self.countUsers() < 2 or not self.checkTeams():
return
# Create playing channel
glob.streams.add(self.playingStreamName)
# Change inProgress value
match.inProgress = True
# 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
self.slots[i].loaded = False
self.slots[i].skip = False
self.slots[i].complete = False
glob.tokens.tokens[self.slots[i].user].joinStream(self.playingStreamName)
# Send match start packet
glob.streams.broadcast(self.playingStreamName, serverPackets.matchStart(self.matchID))
# Send updates
self.sendUpdates()

View File

@@ -1,80 +1,47 @@
from objects import match from objects import match
from objects import glob from objects import glob
from constants import serverPackets from constants import serverPackets
from common.log import logUtils as log
class matchList: class matchList:
matches = {}
usersInLobby = []
lastID = 1
def __init__(self): def __init__(self):
"""Initialize a matchList object""" """Initialize a matchList object"""
self.matches = {} self.matches = {}
self.usersInLobby = []
self.lastID = 1 self.lastID = 1
def createMatch(self, __matchName, __matchPassword, __beatmapID, __beatmapName, __beatmapMD5, __gameMode, __hostUserID): def createMatch(self, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID):
""" """
Add a new match to matches list Add a new match to matches list
__matchName -- match name, string matchName -- match name, string
__matchPassword -- match md5 password. Leave empty for no password matchPassword -- match md5 password. Leave empty for no password
__beatmapID -- beatmap ID beatmapID -- beatmap ID
__beatmapName -- beatmap name, string beatmapName -- beatmap name, string
__beatmapMD5 -- beatmap md5 hash, string beatmapMD5 -- beatmap md5 hash, string
__gameMode -- game mode ID. See gameModes.py gameMode -- game mode ID. See gameModes.py
__hostUserID -- user id of who created the match hostUserID -- user id of who created the match
return -- match ID return -- match ID
""" """
# Add a new match to matches list # Add a new match to matches list and create its stream
matchID = self.lastID matchID = self.lastID
self.lastID+=1 self.lastID+=1
self.matches[matchID] = match.match(matchID, __matchName, __matchPassword, __beatmapID, __beatmapName, __beatmapMD5, __gameMode, __hostUserID) self.matches[matchID] = match.match(matchID, matchName, matchPassword, beatmapID, beatmapName, beatmapMD5, gameMode, hostUserID)
return matchID return matchID
def disposeMatch(self, matchID):
def lobbyUserJoin(self, __userID):
""" """
Add userID to users in lobby Destroy match object with id = matchID
__userID -- user who joined mp lobby matchID -- ID of match to dispose
""" """
# Make sure the user is not already in mp lobby
if __userID not in self.usersInLobby:
# We don't need to join #lobby, client will automatically send a packet for it
self.usersInLobby.append(__userID)
def lobbyUserPart(self, __userID):
"""
Remove userID from users in lobby
__userID -- user who left mp lobby
"""
# Make sure the user is in mp lobby
if __userID in self.usersInLobby:
# Part lobby and #lobby channel
self.usersInLobby.remove(__userID)
def disposeMatch(self, __matchID):
"""
Destroy match object with id = __matchID
__matchID -- ID of match to dispose
"""
# Make sure the match exists # Make sure the match exists
if __matchID not in self.matches: if matchID not in self.matches:
return return
# Remove match object # Remove match object and stream
self.matches.pop(__matchID) match = self.matches.pop(matchID)
glob.streams.remove(match.streamName)
glob.streams.remove(match.playingStreamName)
# Send match dispose packet to everyone in lobby # Send match dispose packet to everyone in lobby
for i in self.usersInLobby: glob.streams.broadcast("lobby", serverPackets.disposeMatch(matchID))
token = glob.tokens.getTokenFromUserID(i)
if token != None:
token.enqueue(serverPackets.disposeMatch(__matchID))

View File

@@ -1,66 +1,50 @@
from constants import actions import threading
from constants import gameModes import time
from helpers import userHelper import uuid
from common.constants import gameModes, actions
from common.log import logUtils as log
from common.ripple import userUtils
from constants import serverPackets from constants import serverPackets
from events import logoutEvent from events import logoutEvent
from helpers import logHelper as log
from objects import glob
import uuid
import time
import threading
from helpers import logHelper as log
from helpers import chatHelper as chat from helpers import chatHelper as chat
from objects import glob
class token: class token:
"""
Osu Token object
token -- token string def __init__(self, userID, token_ = None, ip ="", irc = False, timeOffset = 0, tournament = False):
userID -- userID associated to that token
username -- username relative to userID (cache)
actionID -- current user action (see actions.py)
actionText -- current user action text
actionMd5 -- md5 relative to user action
actionMods -- current acton mods
gameMode -- current user game mode
location -- [latitude,longitude]
queue -- packets queue
joinedChannels -- list. Contains joined channel names
spectating -- userID of spectating user. 0 if not spectating.
spectators -- list. Contains userIDs of spectators
country -- osu country code. Use countryHelper to convert from letter country code to osu country code
pingTime -- latest packet received UNIX time
loginTime -- login UNIX time
latestTillerino -- beatmap ID of latest song from tillerino bot
"""
def __init__(self, __userID, token = None, ip = "", irc = False, timeOffset = 0):
""" """
Create a token object and set userID and token Create a token object and set userID and token
__userID -- user associated to this token userID -- user associated to this token
token -- if passed, set token to that value token -- if passed, set token to that value
if not passed, token will be generated if not passed, token will be generated
ip -- client ip. optional. ip -- client ip. optional.
irc -- if True, set this token as IRC client. optional. irc -- if True, set this token as IRC client. optional.
timeOffset -- the time offset from UTC for this user. optional.
""" """
# Set stuff # Set stuff
self.userID = __userID self.userID = userID
self.username = userHelper.getUsername(self.userID) self.username = userUtils.getUsername(self.userID)
self.privileges = userHelper.getPrivileges(self.userID) self.privileges = userUtils.getPrivileges(self.userID)
self.admin = userHelper.isInPrivilegeGroup(self.userID, "developer") or userHelper.isInPrivilegeGroup(self.userID, "community manager") self.admin = userUtils.isInPrivilegeGroup(self.userID, "developer") or userUtils.isInPrivilegeGroup(self.userID, "community manager")
self.irc = irc self.irc = irc
self.restricted = userHelper.isRestricted(self.userID) self.restricted = userUtils.isRestricted(self.userID)
self.loginTime = int(time.time()) self.loginTime = int(time.time())
self.pingTime = self.loginTime self.pingTime = self.loginTime
self.timeOffset = timeOffset self.timeOffset = timeOffset
self.lock = threading.Lock() # Sync primitive self.lock = threading.Lock() # Sync primitive
self.streams = []
self.tournament = tournament
# Default variables # Default variables
self.spectators = [] self.spectators = []
self.spectating = 0
# TODO: Move those two vars to a class
self.spectating = None
self.spectatingUserID = 0 # we need this in case we the host gets DCed
self.location = [0,0] self.location = [0,0]
self.joinedChannels = [] self.joinedChannels = []
self.ip = ip self.ip = ip
@@ -71,17 +55,17 @@ class token:
self.tillerino = [0,0,-1.0] # beatmap, mods, acc self.tillerino = [0,0,-1.0] # beatmap, mods, acc
self.silenceEndTime = 0 self.silenceEndTime = 0
self.queue = bytes() self.queue = bytes()
self.osuDirectAlert = False # NOTE: Remove this when osu!direct will be fixed
# Spam protection # Spam protection
self.spamRate = 0 self.spamRate = 0
# Stats cache # Stats cache
self.actionID = actions.idle self.actionID = actions.IDLE
self.actionText = "" self.actionText = ""
self.actionMd5 = "" self.actionMd5 = ""
self.actionMods = 0 self.actionMods = 0
self.gameMode = gameModes.std self.gameMode = gameModes.STD
self.beatmapID = 0
self.rankedScore = 0 self.rankedScore = 0
self.accuracy = 0.0 self.accuracy = 0.0
self.playcount = 0 self.playcount = 0
@@ -90,8 +74,8 @@ class token:
self.pp = 0 self.pp = 0
# Generate/set token # Generate/set token
if token != None: if token_ is not None:
self.token = token self.token = token_
else: else:
self.token = str(uuid.uuid4()) self.token = str(uuid.uuid4())
@@ -100,22 +84,22 @@ class token:
# If we have a valid ip, save bancho session in DB so we can cache LETS logins # If we have a valid ip, save bancho session in DB so we can cache LETS logins
if ip != "": if ip != "":
userHelper.saveBanchoSession(self.userID, self.ip) userUtils.saveBanchoSession(self.userID, self.ip)
# If we are restricted, send message from FokaBot to user # Join main stream
# NOTE: Sent later self.joinStream("main")
#if self.restricted == True:
# self.setRestricted()
def enqueue(self, bytes_):
def enqueue(self, __bytes):
""" """
Add bytes (packets) to queue Add bytes (packets) to queue
__bytes -- (packet) bytes to enqueue bytes -- (packet) bytes to enqueue
""" """
if self.irc == False: if not self.irc:
self.queue += __bytes 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): def resetQueue(self):
@@ -123,95 +107,146 @@ class token:
self.queue = bytes() self.queue = bytes()
def joinChannel(self, __channel): def joinChannel(self, channel):
"""Add __channel to joined channels list """
Add channel to joined channels list
__channel -- channel name""" channel -- channel name
"""
if channel not in self.joinedChannels:
self.joinedChannels.append(channel)
if __channel not in self.joinedChannels: def partChannel(self, channel):
self.joinedChannels.append(__channel) """
Remove channel from joined channels list
channel -- channel name
"""
if channel in self.joinedChannels:
self.joinedChannels.remove(channel)
def partChannel(self, __channel): def setLocation(self, location):
"""Remove __channel from joined channels list """
Set location (latitude and longitude)
__channel -- channel name"""
if __channel in self.joinedChannels:
self.joinedChannels.remove(__channel)
def setLocation(self, __location):
"""Set location (latitude and longitude)
__location -- [latitude, longitude]"""
self.location = __location
location -- [latitude, longitude]
"""
self.location = location
def getLatitude(self): def getLatitude(self):
"""Get latitude """
Get latitude
return -- latitude"""
return -- latitude
"""
return self.location[0] return self.location[0]
def getLongitude(self): def getLongitude(self):
"""Get longitude """
Get longitude
return -- longitude""" return -- longitude
"""
return self.location[1] return self.location[1]
def startSpectating(self, host):
"""
Set the spectating user to userID
def startSpectating(self, __userID): user -- user object
"""Set the spectating user to __userID """
# Stop spectating old client
self.stopSpectating()
__userID -- target userID""" # Set new spectator host
self.spectating = __userID self.spectating = host.token
self.spectatingUserID = host.userID
# Add us to host's spectator list
host.spectators.append(self.token)
# Create and join spectator stream
streamName = "spect/{}".format(host.userID)
glob.streams.add(streamName)
self.joinStream(streamName)
host.joinStream(streamName)
# Send spectator join packet to host
host.enqueue(serverPackets.addSpectator(self.userID))
# Create and join #spectator (#spect_userid) channel
glob.channels.addTempChannel("#spect_{}".format(host.userID))
chat.joinChannel(token=self, channel="#spect_{}".format(host.userID))
if len(host.spectators) == 1:
# First spectator, send #spectator join to host too
chat.joinChannel(token=host, channel="#spect_{}".format(host.userID))
# Send fellow spectator join to all clients
glob.streams.broadcast(streamName, serverPackets.fellowSpectatorJoined(self.userID))
# Get current spectators list
for i in host.spectators:
if i != self.token and i in glob.tokens.tokens:
self.enqueue(serverPackets.fellowSpectatorJoined(glob.tokens.tokens[i].userID))
# Log
log.info("{} is spectating {}".format(self.username, host.username))
def stopSpectating(self): def stopSpectating(self):
"""Set the spectating user to 0, aka no user""" # Remove our userID from host's spectators
self.spectating = 0 if self.spectating is None:
return
if self.spectating in glob.tokens.tokens:
hostToken = glob.tokens.tokens[self.spectating]
else:
hostToken = None
streamName = "spect/{}".format(self.spectatingUserID)
# Remove us from host's spectators list,
# leave spectator stream
# and end the spectator left packet to host
self.leaveStream(streamName)
if hostToken is not None:
hostToken.spectators.remove(self.token)
hostToken.enqueue(serverPackets.removeSpectator(self.userID))
def addSpectator(self, __userID): # and to all other spectators
"""Add __userID to our spectators for i in hostToken.spectators:
if i in glob.tokens.tokens:
glob.tokens.tokens[i].enqueue(serverPackets.fellowSpectatorLeft(self.userID))
userID -- new spectator userID""" # If nobody is spectating the host anymore, close #spectator channel
# and remove host from spect stream too
if len(hostToken.spectators) == 0:
chat.partChannel(token=hostToken, channel="#spect_{}".format(hostToken.userID), kick=True)
hostToken.leaveStream(streamName)
# Add userID to spectators if not already in # Part #spectator channel
if __userID not in self.spectators: chat.partChannel(token=self, channel="#spect_{}".format(self.spectatingUserID), kick=True)
self.spectators.append(__userID)
# Console output
log.info("{} is no longer spectating {}".format(self.username, self.spectatingUserID))
def removeSpectator(self, __userID): # Set our spectating user to 0
"""Remove __userID from our spectators self.spectating = None
self.spectatingUserID = 0
userID -- old spectator userID""" def setCountry(self, countryID):
"""
# Remove spectator Set country to countryID
if __userID in self.spectators:
self.spectators.remove(__userID)
def setCountry(self, __countryID):
"""Set country to __countryID
__countryID -- numeric country ID. See countryHelper.py"""
self.country = __countryID
countryID -- numeric country ID. See countryHelper.py
"""
self.country = countryID
def getCountry(self): def getCountry(self):
"""Get numeric country ID """
Get numeric country ID
return -- numeric country ID. See countryHelper.py"""
return -- numeric country ID. See countryHelper.py
"""
return self.country return self.country
def updatePingTime(self): def updatePingTime(self):
"""Update latest ping time""" """Update latest ping time"""
self.pingTime = int(time.time()) self.pingTime = int(time.time())
@@ -220,23 +255,77 @@ class token:
"""Set a new away message""" """Set a new away message"""
self.awayMessage = __awayMessage self.awayMessage = __awayMessage
def joinMatch(self, __matchID):
"""
Set match to matchID
__matchID -- new match ID def joinMatch(self, matchID):
""" """
self.matchID = __matchID Set match to matchID, join match stream and channel
def partMatch(self): matchID -- new match ID
"""Set match to -1""" """
# Make sure the match exists
if matchID not in glob.matches.matches:
return
# Match exists, get object
match = glob.matches.matches[matchID]
# Stop spectating
self.stopSpectating()
# Leave other matches
if self.matchID > -1 and self.matchID != matchID:
self.leaveMatch()
# Try to join match
joined = match.userJoin(self)
if not joined:
self.enqueue(serverPackets.matchJoinFail())
return
# Set matchID, join stream, channel and send packet
self.matchID = matchID
self.joinStream(match.streamName)
chat.joinChannel(token=self, channel="#multi_{}".format(self.matchID))
self.enqueue(serverPackets.matchJoinSuccess(matchID))
def leaveMatch(self):
"""
Leave joined match, match stream and match channel
:return:
"""
# Make sure we are in a match
if self.matchID == -1:
return
# Part #multiplayer channel and streams (/ and /playing)
chat.partChannel(token=self, channel="#multi_{}".format(self.matchID), kick=True)
self.leaveStream("multi/{}".format(self.matchID))
self.leaveStream("multi/{}/playing".format(self.matchID)) # optional
# Make sure the match exists
if self.matchID not in glob.matches.matches:
return
# The match exists, get object
match = glob.matches.matches[self.matchID]
# Set slot to free
match.userLeft(self)
# Set usertoken match to -1
self.matchID = -1 self.matchID = -1
def kick(self, message="You have been kicked from the server. Please login again."): def kick(self, message="You have been kicked from the server. Please login again.", reason="kick"):
"""Kick this user from the server""" """
Kick this user from the server
message -- Notification message to send to this user. Optional.
"""
# Send packet to target # Send packet to target
log.info("{} has been disconnected. (kick)".format(self.username)) log.info("{} has been disconnected. ({})".format(self.username, reason))
self.enqueue(serverPackets.notification(message)) if message != "":
self.enqueue(serverPackets.notification(message))
self.enqueue(serverPackets.loginFailed()) self.enqueue(serverPackets.loginFailed())
# Logout event # Logout event
@@ -252,13 +341,13 @@ class token:
""" """
# Silence in db and token # Silence in db and token
self.silenceEndTime = int(time.time())+seconds self.silenceEndTime = int(time.time())+seconds
userHelper.silence(self.userID, seconds, reason, author) userUtils.silence(self.userID, seconds, reason, author)
# Send silence packet to target # Send silence packet to target
self.enqueue(serverPackets.silenceEndTime(seconds)) self.enqueue(serverPackets.silenceEndTime(seconds))
# Send silenced packet to everyone else # Send silenced packet to everyone else
glob.tokens.enqueueAll(serverPackets.userSilenced(self.userID)) glob.streams.broadcast("main", serverPackets.userSilenced(self.userID))
def spamProtection(self, increaseSpamRate = True): def spamProtection(self, increaseSpamRate = True):
""" """
@@ -267,7 +356,7 @@ class token:
increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True increaseSpamRate -- pass True if the user has sent a new message. Optional. Default: True
""" """
# Increase the spam rate if needed # Increase the spam rate if needed
if increaseSpamRate == True: if increaseSpamRate:
self.spamRate += 1 self.spamRate += 1
# Silence the user if needed # Silence the user if needed
@@ -293,9 +382,9 @@ class token:
def updateCachedStats(self): def updateCachedStats(self):
"""Update all cached stats for this token""" """Update all cached stats for this token"""
stats = userHelper.getUserStats(self.userID, self.gameMode) stats = userUtils.getUserStats(self.userID, self.gameMode)
log.debug(str(stats)) log.debug(str(stats))
if stats == None: if stats is None:
log.warning("Stats query returned None") log.warning("Stats query returned None")
return return
self.rankedScore = stats["rankedScore"] self.rankedScore = stats["rankedScore"]
@@ -312,9 +401,9 @@ class token:
force -- If True, get restricted value from db. force -- If True, get restricted value from db.
If false, get the cached one. Optional. Default: False If false, get the cached one. Optional. Default: False
""" """
if force == True: if force:
self.restricted = userHelper.isRestricted(self.userID) self.restricted = userUtils.isRestricted(self.userID)
if self.restricted == True: if self.restricted:
self.setRestricted() self.setRestricted()
def setRestricted(self): def setRestricted(self):
@@ -324,3 +413,17 @@ class token:
""" """
self.restricted = True self.restricted = True
chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.") chat.sendMessage("FokaBot",self.username, "Your account is currently in restricted mode. Please visit ripple's website for more information.")
def joinStream(self, name):
glob.streams.join(name, token=self.token)
if name not in self.streams:
self.streams.append(name)
def leaveStream(self, name):
glob.streams.leave(name, token=self.token)
if name in self.streams:
self.streams.remove(name)
def leaveAllStreams(self):
for i in self.streams:
self.leaveStream(i)

57
objects/stream.py Normal file
View File

@@ -0,0 +1,57 @@
from common.log import logUtils as log
from objects import glob
class stream:
def __init__(self, name):
"""
Initialize a stream object
:param name: stream name
"""
self.name = name
self.clients = []
def addClient(self, client=None, token=None):
"""
Add a client to this stream if not already in
:param client: client (osuToken) object
:param token: client uuid string
:return:
"""
if client is None and token is None:
return
if client is not None:
token = client.token
if token not in self.clients:
log.info("{} has joined stream {}".format(token, self.name))
self.clients.append(token)
def removeClient(self, client=None, token=None):
"""
Remove a client from this stream if in
:param client: client (osuToken) object
:param token: client uuid string
:return:
"""
if client is None and token is None:
return
if client is not None:
token = client.token
if token in self.clients:
log.info("{} has left stream {}".format(token, self.name))
self.clients.remove(token)
def broadcast(self, data):
"""
Send some data to all clients connected to this stream
:param data: data to send
:return:
"""
for i in self.clients:
if i in glob.tokens.tokens:
glob.tokens.tokens[i].enqueue(data)
else:
self.removeClient(token=i)

79
objects/streamList.py Normal file
View File

@@ -0,0 +1,79 @@
from objects import stream
from objects import glob
class streamList:
def __init__(self):
self.streams = {}
def add(self, name):
"""
Create a new stream list if it doesn't already exist
:param name: stream name
:return:
"""
if name not in self.streams:
self.streams[name] = stream.stream(name)
def remove(self, name):
"""
Removes an existing stream and kick every user in it
:param name: stream name
:return:
"""
if name in self.streams:
for i in self.streams[name].clients:
if i in glob.tokens.tokens:
glob.tokens.tokens[i].leaveStream(name)
self.streams.pop(name)
def join(self, streamName, client=None, token=None):
"""
Add a client to a stream
:param streamName: stream name
:param client: client (osuToken) object
:param token: client uuid string
:return:
"""
if streamName not in self.streams:
return
self.streams[streamName].addClient(client=client, token=token)
def leave(self, streamName, client=None, token=None):
"""
Remove a client from a stream
:param streamName: stream name
:param client: client (osuToken) object
:param token: client uuid string
:return:
"""
if streamName not in self.streams:
return
self.streams[streamName].removeClient(client=client, token=token)
def broadcast(self, streamName, data):
"""
Send some data to all clients in a stream
:param streamName: stream name
:param data: data to send
:return:
"""
if streamName not in self.streams:
return
self.streams[streamName].broadcast(data)
'''def getClients(self, streamName):
"""
Get all clients in a stream
:param streamName: name of the stream
:return:
"""
if streamName not in self.streams:
return
return self.streams[streamName].clients'''

View File

@@ -1,10 +1,13 @@
from objects import osuToken
from objects import glob
import time
import threading import threading
import time
from common.ripple import userUtils
from common.log import logUtils as log
from constants import serverPackets
from events import logoutEvent from events import logoutEvent
from helpers import logHelper as log from objects import glob
from helpers import userHelper from objects import osuToken
class tokenList: class tokenList:
""" """
@@ -19,7 +22,7 @@ class tokenList:
""" """
self.tokens = {} self.tokens = {}
def addToken(self, userID, ip = "", irc = False, timeOffset=0): def addToken(self, userID, ip = "", irc = False, timeOffset=0, tournament=False):
""" """
Add a token object to tokens list Add a token object to tokens list
@@ -27,8 +30,7 @@ class tokenList:
irc -- if True, set this token as IRC client irc -- if True, set this token as IRC client
return -- token object return -- token object
""" """
newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset, tournament=tournament)
newToken = osuToken.token(userID, ip=ip, irc=irc, timeOffset=timeOffset)
self.tokens[newToken.token] = newToken self.tokens[newToken.token] = newToken
return newToken return newToken
@@ -38,25 +40,21 @@ class tokenList:
token -- token string token -- token string
""" """
if token in self.tokens: if token in self.tokens:
# Delete session from DB # Delete session from DB
if self.tokens[token].ip != "": if self.tokens[token].ip != "":
userHelper.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip) userUtils.deleteBanchoSessions(self.tokens[token].userID, self.tokens[token].ip)
# Pop token from list # Pop token from list
self.tokens.pop(token) self.tokens.pop(token)
def getUserIDFromToken(self, token): def getUserIDFromToken(self, token):
""" """
Get user ID from a token Get user ID from a token
token -- token to find token -- token to find
return -- false if not found, userID if found
return: false if not found, userID if found
""" """
# Make sure the token exists # Make sure the token exists
if token not in self.tokens: if token not in self.tokens:
return False return False
@@ -64,44 +62,43 @@ class tokenList:
# Get userID associated to that token # Get userID associated to that token
return self.tokens[token].userID return self.tokens[token].userID
def getTokenFromUserID(self, userID, ignoreIRC=False):
def getTokenFromUserID(self, userID):
""" """
Get token from a user ID Get token from a user ID
userID -- user ID to find userID -- user ID to find
return -- False if not found, token object if found return -- False if not found, token object if found
""" """
# Make sure the token exists # Make sure the token exists
for _, value in self.tokens.items(): for _, value in self.tokens.items():
if value.userID == userID: if value.userID == userID:
if ignoreIRC and value.irc:
continue
return value return value
# Return none if not found # Return none if not found
return None return None
def getTokenFromUsername(self, username, ignoreIRC=False):
def getTokenFromUsername(self, username):
""" """
Get token from a username Get token from a username
username -- username to find username -- username to find
return -- False if not found, token object if found return -- False if not found, token object if found
""" """
# lowercase # lowercase
who = username.lower() who = username.lower()
# Make sure the token exists # Make sure the token exists
for _, value in self.tokens.items(): for _, value in self.tokens.items():
if value.username.lower() == who: if value.username.lower() == who:
if ignoreIRC and value.irc:
continue
return value return value
# Return none if not found # Return none if not found
return None return None
def deleteOldTokens(self, userID): def deleteOldTokens(self, userID):
""" """
Delete old userID's tokens if found Delete old userID's tokens if found
@@ -113,12 +110,7 @@ class tokenList:
for key, value in list(self.tokens.items()): for key, value in list(self.tokens.items()):
if value.userID == userID: if value.userID == userID:
# Delete this token from the dictionary # Delete this token from the dictionary
self.tokens[key].kick("You have logged in from somewhere else. You can't connect to Bancho/IRC from more than one device at the same time.") self.tokens[key].kick("You have logged in from somewhere else. You can't connect to Bancho/IRC from more than one device at the same time.", "kicked, multiple clients")
#self.tokens.pop(key)
# break or items() function throws errors
#break
def multipleEnqueue(self, packet, who, but = False): def multipleEnqueue(self, packet, who, but = False):
""" """
@@ -128,7 +120,6 @@ class tokenList:
who -- userIDs array who -- userIDs array
but -- if True, enqueue to everyone but users in who array but -- if True, enqueue to everyone but users in who array
""" """
for _, value in self.tokens.items(): for _, value in self.tokens.items():
shouldEnqueue = False shouldEnqueue = False
if value.userID in who and not but: if value.userID in who and not but:
@@ -145,25 +136,24 @@ class tokenList:
packet -- packet bytes to enqueue packet -- packet bytes to enqueue
""" """
for _, value in self.tokens.items(): for _, value in self.tokens.items():
value.enqueue(packet) value.enqueue(packet)
def usersTimeoutCheckLoop(self, __timeoutTime = 100, __checkTime = 100): def usersTimeoutCheckLoop(self, timeoutTime = 100, checkTime = 100):
""" """
Deletes all timed out users. Deletes all timed out users.
If called once, will recall after __checkTime seconds and so on, forever If called once, will recall after checkTime seconds and so on, forever
CALL THIS FUNCTION ONLY ONCE! CALL THIS FUNCTION ONLY ONCE!
__timeoutTime - seconds of inactivity required to disconnect someone (Default: 100) timeoutTime - seconds of inactivity required to disconnect someone (Default: 100)
__checkTime - seconds between loops (Default: 100) checkTime - seconds between loops (Default: 100)
""" """
log.debug("Checking timed out clients")
timedOutTokens = [] # timed out users timedOutTokens = [] # timed out users
timeoutLimit = time.time()-__timeoutTime timeoutLimit = int(time.time())-timeoutTime
for key, value in self.tokens.items(): for key, value in self.tokens.items():
# Check timeout (fokabot is ignored) # Check timeout (fokabot is ignored)
if value.pingTime < timeoutLimit and value.userID != 999 and value.irc == False: if value.pingTime < timeoutLimit and value.userID != 999 and value.irc == False and value.tournament == False:
# That user has timed out, add to disconnected tokens # That user has timed out, add to disconnected tokens
# We can't delete it while iterating or items() throws an error # We can't delete it while iterating or items() throws an error
timedOutTokens.append(key) timedOutTokens.append(key)
@@ -171,18 +161,18 @@ class tokenList:
# Delete timed out users from self.tokens # Delete timed out users from self.tokens
# i is token string (dictionary key) # i is token string (dictionary key)
for i in timedOutTokens: for i in timedOutTokens:
log.debug("{} timed out!!".format(self.tokens[i].username))
self.tokens[i].enqueue(serverPackets.notification("Your connection to the server timed out."))
logoutEvent.handle(self.tokens[i], None) logoutEvent.handle(self.tokens[i], None)
# Schedule a new check (endless loop) # Schedule a new check (endless loop)
threading.Timer(__checkTime, self.usersTimeoutCheckLoop, [__timeoutTime, __checkTime]).start() threading.Timer(checkTime, self.usersTimeoutCheckLoop, [timeoutTime, checkTime]).start()
def spamProtectionResetLoop(self): def spamProtectionResetLoop(self):
""" """
Reset spam rate every 10 seconds. Reset spam rate every 10 seconds.
CALL THIS FUNCTION ONLY ONCE! CALL THIS FUNCTION ONLY ONCE!
""" """
#log.debug("Resetting spam protection...")
# Reset spamRate for every token # Reset spamRate for every token
for _, value in self.tokens.items(): for _, value in self.tokens.items():
value.spamRate = 0 value.spamRate = 0

372
pep.py
View File

@@ -1,40 +1,39 @@
"""Hello, pep.py here, ex-owner of ripple and prime minister of Ripwot.""" """Hello, pep.py here, ex-owner of ripple and prime minister of Ripwot."""
import sys
import os import os
import sys
import threading import threading
from multiprocessing.pool import ThreadPool
# Tornado import tornado.gen
import tornado.httpserver
import tornado.ioloop import tornado.ioloop
import tornado.web import tornado.web
import tornado.httpserver
import tornado.gen
from gevent import monkey as brit_monkey
brit_monkey.patch_all()
# Raven
from raven.contrib.tornado import AsyncSentryClient from raven.contrib.tornado import AsyncSentryClient
# pep.py files from common import generalUtils
from constants import bcolors from common.constants import bcolors
from helpers import configHelper from common.db import dbConnector
from objects import glob from common.ddog import datadogClient
from objects import fokabot from common.log import logUtils as log
from objects import banchoConfig from common.ripple import userUtils
from objects import chatFilters from common.web import schiavo
from helpers import consoleHelper from handlers import apiFokabotMessageHandler
from helpers import databaseHelperNew
from helpers import generalFunctions
from helpers import logHelper as log
from handlers import mainHandler
from handlers import apiIsOnlineHandler from handlers import apiIsOnlineHandler
from handlers import apiOnlineUsersHandler from handlers import apiOnlineUsersHandler
from handlers import apiServerStatusHandler from handlers import apiServerStatusHandler
from handlers import ciTriggerHandler
from handlers import apiVerifiedStatusHandler from handlers import apiVerifiedStatusHandler
from handlers import fokabotMessageHandler from handlers import ciTriggerHandler
from handlers import mainHandler
from handlers import heavyHandler
from helpers import configHelper
from helpers import consoleHelper
from helpers import systemHelper as system
from irc import ircserver from irc import ircserver
from objects import banchoConfig
from objects import chatFilters
from objects import fokabot
from objects import glob
def make_app(): def make_app():
return tornado.web.Application([ return tornado.web.Application([
@@ -44,161 +43,200 @@ def make_app():
(r"/api/v1/serverStatus", apiServerStatusHandler.handler), (r"/api/v1/serverStatus", apiServerStatusHandler.handler),
(r"/api/v1/ciTrigger", ciTriggerHandler.handler), (r"/api/v1/ciTrigger", ciTriggerHandler.handler),
(r"/api/v1/verifiedStatus", apiVerifiedStatusHandler.handler), (r"/api/v1/verifiedStatus", apiVerifiedStatusHandler.handler),
(r"/api/v1/fokabotMessage", fokabotMessageHandler.handler) (r"/api/v1/fokabotMessage", apiFokabotMessageHandler.handler),
(r"/stress", heavyHandler.handler)
]) ])
if __name__ == "__main__": if __name__ == "__main__":
# Server start
consoleHelper.printServerStartHeader(True)
# Read config.ini
consoleHelper.printNoNl("> Loading config file... ")
glob.conf = configHelper.config("config.ini")
if glob.conf.default == True:
# We have generated a default config.ini, quit server
consoleHelper.printWarning()
consoleHelper.printColored("[!] config.ini not found. A default one has been generated.", bcolors.YELLOW)
consoleHelper.printColored("[!] Please edit your config.ini and run the server again.", bcolors.YELLOW)
sys.exit()
# If we haven't generated a default config.ini, check if it's valid
if glob.conf.checkConfig() == False:
consoleHelper.printError()
consoleHelper.printColored("[!] Invalid config.ini. Please configure it properly", bcolors.RED)
consoleHelper.printColored("[!] Delete your config.ini to generate a default one", bcolors.RED)
sys.exit()
else:
consoleHelper.printDone()
# Connect to db
try: try:
consoleHelper.printNoNl("> Connecting to MySQL db") # Server start
glob.db = databaseHelperNew.db(glob.conf.config["db"]["host"], glob.conf.config["db"]["username"], glob.conf.config["db"]["password"], glob.conf.config["db"]["database"], int(glob.conf.config["db"]["workers"])) consoleHelper.printServerStartHeader(True)
consoleHelper.printNoNl(" ")
consoleHelper.printDone()
except:
# Exception while connecting to db
consoleHelper.printError()
consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED)
raise
# Load bancho_settings # Read config.ini
try: consoleHelper.printNoNl("> Loading config file... ")
consoleHelper.printNoNl("> Loading bancho settings from DB... ") glob.conf = configHelper.config("config.ini")
glob.banchoConf = banchoConfig.banchoConfig()
consoleHelper.printDone()
except:
consoleHelper.printError()
consoleHelper.printColored("[!] Error while loading bancho_settings. Please make sure the table in DB has all the required rows", bcolors.RED)
raise
# Delete old bancho sessions if glob.conf.default:
consoleHelper.printNoNl("> Deleting cached bancho sessions from DB... ") # We have generated a default config.ini, quit server
glob.tokens.deleteBanchoSessions() consoleHelper.printWarning()
consoleHelper.printDone() consoleHelper.printColored("[!] config.ini not found. A default one has been generated.", bcolors.YELLOW)
consoleHelper.printColored("[!] Please edit your config.ini and run the server again.", bcolors.YELLOW)
sys.exit()
try: # If we haven't generated a default config.ini, check if it's valid
consoleHelper.printNoNl("> Loading chat filters... ") if not glob.conf.checkConfig():
glob.chatFilters = chatFilters.chatFilters() consoleHelper.printError()
consoleHelper.printDone() consoleHelper.printColored("[!] Invalid config.ini. Please configure it properly", bcolors.RED)
except: consoleHelper.printColored("[!] Delete your config.ini to generate a default one", bcolors.RED)
consoleHelper.printError() sys.exit()
consoleHelper.printColored("[!] Error while loading chat filters. Make sure there is a filters.txt file present", bcolors.RED)
raise
# Create data folder if needed
consoleHelper.printNoNl("> Checking folders... ")
paths = [".data"]
for i in paths:
if not os.path.exists(i):
os.makedirs(i, 0o770)
consoleHelper.printDone()
# Initialize chat channels
print("> Initializing chat channels... ")
glob.channels.loadChannels()
consoleHelper.printDone()
# Start fokabot
consoleHelper.printNoNl("> Connecting FokaBot... ")
fokabot.connect()
consoleHelper.printDone()
# Initialize user timeout check loop
consoleHelper.printNoNl("> Initializing user timeout check loop... ")
glob.tokens.usersTimeoutCheckLoop()
consoleHelper.printDone()
# Initialize spam protection reset loop
consoleHelper.printNoNl("> Initializing spam protection reset loop... ")
glob.tokens.spamProtectionResetLoop()
consoleHelper.printDone()
# Localize warning
glob.localize = generalFunctions.stringToBool(glob.conf.config["localize"]["enable"])
if glob.localize == False:
consoleHelper.printColored("[!] Warning! Users localization is disabled!", bcolors.YELLOW)
# Discord
glob.discord = generalFunctions.stringToBool(glob.conf.config["discord"]["enable"])
if glob.discord == False:
consoleHelper.printColored("[!] Warning! Discord logging is disabled!", bcolors.YELLOW)
# Gzip
glob.gzip = generalFunctions.stringToBool(glob.conf.config["server"]["gzip"])
glob.gziplevel = int(glob.conf.config["server"]["gziplevel"])
if glob.gzip == False:
consoleHelper.printColored("[!] Warning! Gzip compression is disabled!", bcolors.YELLOW)
# Debug mode
glob.debug = generalFunctions.stringToBool(glob.conf.config["debug"]["enable"])
glob.outputPackets = generalFunctions.stringToBool(glob.conf.config["debug"]["packets"])
glob.outputRequestTime = generalFunctions.stringToBool(glob.conf.config["debug"]["time"])
if glob.debug == True:
consoleHelper.printColored("[!] Warning! Server running in debug mode!", bcolors.YELLOW)
# Make app
application = make_app()
# Set up sentry
try:
glob.sentry = generalFunctions.stringToBool(glob.conf.config["sentry"]["enable"])
if glob.sentry == True:
application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodns"], release=glob.VERSION)
else: else:
consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW) consoleHelper.printDone()
except:
consoleHelper.printColored("[!] Error while starting sentry client! Please check your config.ini and run the server again", bcolors.RED)
# Cloudflare memes # Create data folder if needed
glob.cloudflare = generalFunctions.stringToBool(glob.conf.config["server"]["cloudflare"]) consoleHelper.printNoNl("> Checking folders... ")
paths = [".data"]
for i in paths:
if not os.path.exists(i):
os.makedirs(i, 0o770)
consoleHelper.printDone()
# IRC start message and console output # Connect to db
glob.irc = generalFunctions.stringToBool(glob.conf.config["irc"]["enable"])
if glob.irc == True:
# IRC port
try: try:
ircPort = int(glob.conf.config["irc"]["port"]) consoleHelper.printNoNl("> Connecting to MySQL database...")
glob.db = dbConnector.db(glob.conf.config["db"]["host"], glob.conf.config["db"]["username"], glob.conf.config["db"]["password"], glob.conf.config["db"]["database"], int(glob.conf.config["db"]["workers"]))
consoleHelper.printNoNl(" ")
consoleHelper.printDone()
except: except:
consoleHelper.printColored("[!] Invalid IRC port! Please check your config.ini and run the server again", bcolors.RED) # Exception while connecting to db
log.logMessage("IRC server started!", discord=True, of="info.txt", stdout=False) consoleHelper.printError()
consoleHelper.printColored("> IRC server listening on 127.0.0.1:{}...".format(ircPort), bcolors.GREEN) consoleHelper.printColored("[!] Error while connection to database. Please check your config.ini and run the server again", bcolors.RED)
threading.Thread(target=lambda: ircserver.main(port=ircPort)).start() raise
else:
consoleHelper.printColored("[!] Warning! IRC server is disabled!", bcolors.YELLOW)
# Server port # Load bancho_settings
try: try:
serverPort = int(glob.conf.config["server"]["port"]) consoleHelper.printNoNl("> Loading bancho settings from DB... ")
except: glob.banchoConf = banchoConfig.banchoConfig()
consoleHelper.printColored("[!] Invalid server port! Please check your config.ini and run the server again", bcolors.RED) consoleHelper.printDone()
except:
consoleHelper.printError()
consoleHelper.printColored("[!] Error while loading bancho_settings. Please make sure the table in DB has all the required rows", bcolors.RED)
raise
# Server start message and console output # Delete old bancho sessions
log.logMessage("Server started!", discord=True, of="info.txt", stdout=False) consoleHelper.printNoNl("> Deleting cached bancho sessions from DB... ")
consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN) glob.tokens.deleteBanchoSessions()
consoleHelper.printDone()
# Start tornado # Create threads pool
application.listen(serverPort) try:
tornado.ioloop.IOLoop.instance().start() consoleHelper.printNoNl("> Creating threads pool... ")
glob.pool = ThreadPool(int(glob.conf.config["server"]["threads"]))
consoleHelper.printDone()
except:
consoleHelper.printError()
consoleHelper.printColored("[!] Error while creating threads pool. Please check your config.ini and run the server again", bcolors.RED)
try:
consoleHelper.printNoNl("> Loading chat filters... ")
glob.chatFilters = chatFilters.chatFilters()
consoleHelper.printDone()
except:
consoleHelper.printError()
consoleHelper.printColored("[!] Error while loading chat filters. Make sure there is a filters.txt file present", bcolors.RED)
raise
# Initialize chat channels
print("> Initializing chat channels... ")
glob.channels.loadChannels()
consoleHelper.printDone()
# Initialize stremas
consoleHelper.printNoNl("> Creating packets streams... ")
glob.streams.add("main")
glob.streams.add("lobby")
consoleHelper.printDone()
# Start fokabot
consoleHelper.printNoNl("> Connecting FokaBot... ")
fokabot.connect()
consoleHelper.printDone()
# Initialize user timeout check loop
consoleHelper.printNoNl("> Initializing user timeout check loop... ")
glob.tokens.usersTimeoutCheckLoop()
consoleHelper.printDone()
# Initialize spam protection reset loop
consoleHelper.printNoNl("> Initializing spam protection reset loop... ")
glob.tokens.spamProtectionResetLoop()
consoleHelper.printDone()
# Cache user ids
consoleHelper.printNoNl("> Caching user IDs... ")
userUtils.cacheUserIDs()
consoleHelper.printDone()
# Localize warning
glob.localize = generalUtils.stringToBool(glob.conf.config["localize"]["enable"])
if not glob.localize:
consoleHelper.printColored("[!] Warning! Users localization is disabled!", bcolors.YELLOW)
# Discord
if generalUtils.stringToBool(glob.conf.config["discord"]["enable"]):
glob.schiavo = schiavo.schiavo(glob.conf.config["discord"]["boturl"])
else:
consoleHelper.printColored("[!] Warning! Discord logging is disabled!", bcolors.YELLOW)
# Gzip
glob.gzip = generalUtils.stringToBool(glob.conf.config["server"]["gzip"])
glob.gziplevel = int(glob.conf.config["server"]["gziplevel"])
if not glob.gzip:
consoleHelper.printColored("[!] Warning! Gzip compression is disabled!", bcolors.YELLOW)
# Debug mode
glob.debug = generalUtils.stringToBool(glob.conf.config["debug"]["enable"])
glob.outputPackets = generalUtils.stringToBool(glob.conf.config["debug"]["packets"])
glob.outputRequestTime = generalUtils.stringToBool(glob.conf.config["debug"]["time"])
if glob.debug:
consoleHelper.printColored("[!] Warning! Server running in debug mode!", bcolors.YELLOW)
# Make app
glob.application = make_app()
# Set up sentry
try:
glob.sentry = generalUtils.stringToBool(glob.conf.config["sentry"]["enable"])
if glob.sentry:
glob.application.sentry_client = AsyncSentryClient(glob.conf.config["sentry"]["banchodns"], release=glob.VERSION)
else:
consoleHelper.printColored("[!] Warning! Sentry logging is disabled!", bcolors.YELLOW)
except:
consoleHelper.printColored("[!] Error while starting sentry client! Please check your config.ini and run the server again", bcolors.RED)
# Set up datadog
try:
if generalUtils.stringToBool(glob.conf.config["datadog"]["enable"]):
glob.dog = datadogClient.datadogClient(
glob.conf.config["datadog"]["apikey"],
glob.conf.config["datadog"]["appkey"],
[
datadogClient.periodicCheck("online_users", lambda: len(glob.tokens.tokens)),
datadogClient.periodicCheck("multiplayer_matches", lambda: len(glob.matches.matches)),
])
else:
consoleHelper.printColored("[!] Warning! Datadog stats tracking is disabled!", bcolors.YELLOW)
except:
consoleHelper.printColored("[!] Error while starting Datadog client! Please check your config.ini and run the server again", bcolors.RED)
# Cloudflare memes
glob.cloudflare = generalUtils.stringToBool(glob.conf.config["server"]["cloudflare"])
# IRC start message and console output
glob.irc = generalUtils.stringToBool(glob.conf.config["irc"]["enable"])
if glob.irc:
# IRC port
try:
ircPort = int(glob.conf.config["irc"]["port"])
except:
consoleHelper.printColored("[!] Invalid IRC port! Please check your config.ini and run the server again", bcolors.RED)
log.logMessage("**pep.py** IRC server started!", discord="bunker", of="info.txt", stdout=False)
consoleHelper.printColored("> IRC server listening on 127.0.0.1:{}...".format(ircPort), bcolors.GREEN)
threading.Thread(target=lambda: ircserver.main(port=ircPort)).start()
else:
consoleHelper.printColored("[!] Warning! IRC server is disabled!", bcolors.YELLOW)
# Server port
try:
serverPort = int(glob.conf.config["server"]["port"])
except:
consoleHelper.printColored("[!] Invalid server port! Please check your config.ini and run the server again", bcolors.RED)
# Server start message and console output
log.logMessage("**pep.py** Server started!", discord="bunker", of="info.txt", stdout=False)
consoleHelper.printColored("> Tornado listening for HTTP(s) clients on 127.0.0.1:{}...".format(serverPort), bcolors.GREEN)
# Start tornado
glob.application.listen(serverPort)
tornado.ioloop.IOLoop.instance().start()
finally:
system.dispose()

6
requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
requests
tornado
mysqlclient
psutil
raven
bcrypt

View File

@@ -1,4 +0,0 @@
D:
cd D:\DevStuff\newripple\pep.py
python pep.py
pause

View File

@@ -0,0 +1 @@
1.9.0