import tornado.gen import tornado.web from common.log import logUtils as log from common.ripple import userUtils from common.sentry import sentry from common.web import requestsManager from constants import exceptions from objects import glob MODULE_NAME = "comments" class handler(requestsManager.asyncRequestHandler): CLIENT_WHO = {"normal": "", "player": "player", "admin": "bat", "donor": "subscriber"} @tornado.web.asynchronous @tornado.gen.engine @sentry.captureTornado def asyncPost(self): try: # Required arguments check if not requestsManager.checkArguments(self.request.arguments, ("u", "p", "a")): raise exceptions.invalidArgumentsException(MODULE_NAME) # Get arguments username = self.get_argument("u") password = self.get_argument("p") action = self.get_argument("a").strip().lower() # IP for session check ip = self.getRequestIP() # Login and ban check userID = userUtils.getID(username) if userID == 0: raise exceptions.loginFailedException(MODULE_NAME, userID) if not userUtils.checkLogin(userID, password, ip): raise exceptions.loginFailedException(MODULE_NAME, username) if userUtils.check2FA(userID, ip): raise exceptions.need2FAException(MODULE_NAME, userID, ip) if userUtils.isBanned(userID): raise exceptions.userBannedException(MODULE_NAME, username) # Action (depends on 'action' parameter, not on HTTP method) if action == "get": self.write(self._getComments()) elif action == "post": self._addComment() except (exceptions.loginFailedException, exceptions.need2FAException, exceptions.userBannedException): self.write("error: no") @staticmethod def clientWho(y): return handler.CLIENT_WHO[y["who"]] + ( ("|{}".format(y["special_format"])) if y["special_format"] is not None else "" ) def _getComments(self): output = "" try: beatmapID = int(self.get_argument("b", default=0)) beatmapSetID = int(self.get_argument("s", default=0)) scoreID = int(self.get_argument("r", default=0)) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) if beatmapID <= 0: return log.info("Requested comments for beatmap id {}".format(beatmapID)) # Merge beatmap, beatmapset and score comments for x in ( {"db_type": "beatmap_id", "client_type": "map", "value": beatmapID}, {"db_type": "beatmapset_id", "client_type": "song", "value": beatmapSetID}, {"db_type": "score_id", "client_type": "replay", "value": scoreID}, ): # Add this set of comments only if the client has set the value if x["value"] <= 0: continue # Fetch these comments comments = glob.db.fetchAll( "SELECT * FROM comments WHERE {} = %s ORDER BY `time`".format(x["db_type"]), (x["value"],) ) # Output comments output += "\n".join([ "{y[time]}\t{client_name}\t{client_who}\t{y[comment]}".format( y=y, client_name=x["client_type"], client_who=self.clientWho(y) ) for y in comments ]) + "\n" return output def _addComment(self): username = self.get_argument("u") target = self.get_argument("target", default=None) specialFormat = self.get_argument("f", default=None) userID = userUtils.getID(username) # Technically useless if userID < 0: return # Get beatmap/set/score ids try: beatmapID = int(self.get_argument("b", default=0)) beatmapSetID = int(self.get_argument("s", default=0)) scoreID = int(self.get_argument("r", default=0)) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) # Add a comment, removing all illegal characters and trimming after 128 characters comment = self.get_argument("comment").replace("\r", "").replace("\t", "").replace("\n", "")[:128] try: time_ = int(self.get_argument("starttime")) except ValueError: raise exceptions.invalidArgumentsException(MODULE_NAME) # Type of comment who = "normal" if target == "replay" and glob.db.fetch( "SELECT COUNT(*) AS c FROM scores WHERE id = %s AND userid = %s AND completed = 3", (scoreID, userID) )["c"] > 0: # From player, on their score who = "player" elif userUtils.isInAnyPrivilegeGroup(userID, ("super admin", "developer", "community manager", "bat")): # From BAT/Admin who = "admin" elif userUtils.isInPrivilegeGroup(userID, "premium"): # Akatsuki Premium Member who = "donor" if target == "song": # Set comment if beatmapSetID <= 0: return value = beatmapSetID column = "beatmapset_id" elif target == "map": # Beatmap comment if beatmapID <= 0: return value = beatmapID column = "beatmap_id" elif target == "replay": # Score comment if scoreID <= 0: return value = scoreID column = "score_id" else: # Invalid target return # Make sure the user hasn't submitted another comment on the same map/set/song in a 5 seconds range if glob.db.fetch( "SELECT COUNT(*) AS c FROM comments WHERE user_id = %s AND {} = %s AND `time` BETWEEN %s AND %s".format( column ), (userID, value, time_ - 5000, time_ + 5000) )["c"] > 0: return # Store the comment glob.db.execute( "INSERT INTO comments ({}, user_id, comment, `time`, who, special_format) " "VALUES (%s, %s, %s, %s, %s, %s)".format(column), (value, userID, comment, time_, who, specialFormat) ) log.info("Submitted {} ({}) comment, user {}: '{}'".format(column, value, userID, comment))