def getFriendStandings(chat: Chat, contestId, sendIfEmpty=True): friends = cf.getListFriends(chat) if len(friends) == 0: if sendIfEmpty: chat.sendMessage(( "You have no friends :(\n" "Please add your API key in the settings or add friends with `/add_friend`." )) logger.debug("user has no friends -> empty standings") return False standings = cf.getStandings(contestId, friends) if standings == False: logger.debug("failed to get standings for " + str(friends)) return False msg = getContestHeader(standings["contest"]) problemNames = [p["index"] for p in standings["problems"]] ratingChanges = getRatingChanges(contestId) ranking = Ranking.Ranking(standings["rows"], ratingChanges, len(problemNames)) tableRows = ranking.getRows(standings["contest"]['phase'] == 'SYSTEM_TEST') if not sendIfEmpty and len(tableRows) == 0: return False table = Table(problemNames, tableRows) msg += table.formatTable(chat.width) return msg
def _sendAllSummary(self, contest): # cache rating for all users chats = [Chat.getChat(c) for c in db.getAllChatPartners()] # The getUserInfo command returns False if there is a unknown user in the list # the user is then removed by the CF error handling routine. A retry is neccessary though. retries = 20 while retries > 0: handles = [c.handle for c in chats if c.handle] infos = cf.getUserInfos(handles) if infos: for info in infos: self.userRatings[info['handle']] = info.get('rating', -1) break retries -= 1 time.sleep(5) logger.debug( f"sending summary for contest {contest['id']}. Cached {len(self.userRatings)} ratings in {20-retries+1} try." ) for chat in chats: msg = self._getContestAnalysis(contest, chat) if len(msg) > 0: # only send if analysis is not empty msg = contest['name'] + " has finished:\n" + msg chat.sendMessage(msg) standings.sendContestStandings(chat, contest['id'], sendIfEmpty=False)
def getRatingChanges(contestId): with cfPredictorLock: if time.time() > cfPredictorLastRequest[contestId] + 20: logger.debug('request rating changes from cf-predictor') cfPredictorLastRequest[contestId] = time.time() try: startT = time.time() r = requests.get(cfPredictorUrl + str(contestId), timeout=10) perfLogger.info( "cf predictor request {:.3f}s".format(time.time() - startT)) except requests.exceptions.Timeout as errt: logger.error("Timeout on CF-predictor.") return handleToRatingChanges[contestId] except Exception as e: logger.critical( 'Failed to request cf-predictor: \nexception: %s\n', e, exc_info=True) return handleToRatingChanges[contestId] if r.status_code != requests.codes.ok: logger.error("CF-Predictor request failed with code " + str(r.status_code) + ", " + str(r.reason)) return handleToRatingChanges[contestId] logger.debug('rating changes received') r = r.json() if r['status'] != 'OK': return handleToRatingChanges[contestId] r = r['result'] handleToRatingChanges[contestId] = {} for row in r: handleToRatingChanges[contestId][row['handle']] = ( row['oldRating'], row['newRating']) cfPredictorLastRequest[contestId] = time.time() return handleToRatingChanges[contestId]
def handleCFError(request, r, chat): if r['status'] == 'FAILED': #delete nonexisting friends startS = "handles: User with handle " endS = " not found" if r['comment'].startswith(startS) and r['comment'].endswith(endS): handle = r['comment'][len(startS):-len(endS)] db.deleteFriend(handle) return #remove wrong authentification if 'Incorrect API key' in r['comment'] or 'Incorrect signature' in r[ 'comment']: chat.apikey = None chat.secret = None chat.sendMessage( "Your API-key did not work 😢. Please add a valid key and secret in the settings." ) return if "contestId: Contest with id" in r[ 'comment'] and "has not started" in r['comment']: return # TODO fetch new contest start time if "contestId: Contest with id" in r['comment'] and "not found" in r[ 'comment']: logger.debug("codeforces error: " + r['comment']) return logger.critical("codeforces error: " + str(r['comment']) + "\n" + "this request caused the error:\n" + (str(request)[:200]), exc_info=True)
def _analyseRow(self, contestId, row, ranking, firstRead): handle = row["party"]["members"][0]["handle"] pointsList = self._points[contestId][handle] for taski in range(len(row["problemResults"])): task = row["problemResults"][taski] taskName = ranking["problems"][taski]["index"] if task["points"] > 0 and taski not in pointsList: if not firstRead: self._notifyTaskSolved(handle, taskName, task["rejectedAttemptCount"], task["bestSubmissionTimeSeconds"], row["rank"] != 0) if ranking["contest"][ 'phase'] == 'FINISHED': # if contest is running, standings are updated automatically self._updateStandings(contestId, db.getWhoseFriendsListed(handle)) pointsList.append(taski) if task['type'] == 'PRELIMINARY' and ( taski not in self._notFinal[contestId][handle]): logger.debug('adding non-final task ' + str(taski) + ' for user ' + str(handle)) self._notFinal[contestId][handle].append(taski) if task['type'] == 'FINAL' and ( taski in self._notFinal[contestId][handle]): logger.debug('finalizing non-final task ' + str(taski) + ' for user ' + str(handle)) self._notFinal[contestId][handle].remove(taski) self._notifyTaskTested(handle, taskName, task['points'] > 0) self._updateStandings(contestId, db.getWhoseFriendsListed(handle)) if int(task['points'] ) == 0: #failed on system tests, now not solved pointsList.remove(taski)
def _doTask(self): logger.debug('loading current contests') allContests = sendRequest('contest.list', {'gym': 'false'}) if allContests is False: logger.error('failed to load current contest - maybe cf is not up') else: with contestListLock: selectImportantContests(allContests) logger.debug('loding contests finished')
def deleteFriend(handle): logger.debug("deleting friends with handle " + handle) query = "DELETE FROM friends WHERE friend = %s" insertDB(query, (handle, )) query = "SELECT chatId FROM tokens WHERE handle = %s" chatIds = [r[0] for r in queryDB(query, (handle, ))] logger.debug(f"deleting chat handle {handle} for chats {chatIds}") for chatId in chatIds: Chat.getChat(chatId).handle = None # write through to DB
def handleAddSecret(chat, secret): if not secret.isalnum(): chat.sendMessage( "Your API-secret is incorrect, it may only contain alphanumerical letters. Please try again:" ) return chat.secret = secret bot.setOpenCommandFunc(chat.chatId, None) logger.debug('new secret added for user ' + str(chat.chatId)) chat.sendMessage("Key added. Now fetching your codeforces friends...") cf.updateFriends(chat) bot.sendSetupFinished(chat)
def _doTask(self, firstRead=False): logger.debug('started analysing standings') friends = db.getAllFriends() threads = [] for contestId in cf.getCurrentContestsId(): t = util.createThread(target=self._analyseContest, args=(contestId, friends, firstRead), name="analyseContest" + str(contestId)) t.start() threads.append(t) for t in threads: t.join() logger.debug('finished analysing standings')
def getUserInfos(userNameArr): batchSize = 200 split = [ userNameArr[batchSize * i:batchSize * (i + 1)] for i in range((len(userNameArr) + batchSize - 1) // batchSize) ] res = [] for part in split: usrList = ';'.join(part) logger.debug('requesting info of ' + str(len(part)) + ' users ') r = sendRequest('user.info', {'handles': usrList}) if r is False: return False res.extend(r) return res
def updateStandingsForChat(contest, chat: Chat): with standingsSentLock: if contest not in standingsSent[ chat.chatId]: # only used as speed-up, checked again later return msg = getFriendStandings(chat, contest) if msg is False: return logger.debug('update standings for ' + str(chat.chatId) + '!') with standingsSentLock: if contest not in standingsSent[chat.chatId]: return msgId, oldMsg = standingsSent[chat.chatId][contest] if tg.shortenMessage(oldMsg) != tg.shortenMessage(msg): updateStandingsSent(chat.chatId, contest, msgId, msg) chat.editMessageTextLater( msgId, contest, lambda chat, contest: getFriendStandings(chat, contest))
def updateStandings(contestId): global aktuelleContests logger.debug('updating standings for contest ' + str(contestId) + ' for all users') stNew = sendRequest('contest.standings', { 'contestId': contestId, 'showUnofficial': True }) if stNew and "contest" in stNew: with standingsLock[contestId]: globalStandings[contestId] = { "time": time.time(), "standings": stNew } with contestListLock: aktuelleContests = [ stNew["contest"] if stNew["contest"]["id"] == c["id"] else c for c in aktuelleContests ] logger.debug('standings received') else: logger.error('standings not updated')
def updateFriends(chat): p = {'onlyOnline': 'false'} logger.debug('request friends of chat with chat_id ' + str(chat.chatId)) f = sendRequest("user.friends", p, True, chat) logger.debug('requesting friends finished') if f != False: db.addFriends(chat.chatId, f, chat.notifyLevel) logger.debug('friends updated for chat ' + str(chat.chatId))
def deleteUser(chatId): logger.debug("deleting all data of user with chatId " + str(chatId)) query = "DELETE FROM friends WHERE chatId = %s" logger.debug("deleting all friend entries: " + query) insertDB(query, (chatId, )) query = "DELETE FROM tokens WHERE chatId = %s" logger.debug("deleting all token entries: " + query) insertDB(query, (chatId, ))
def _doTask(self): logger.debug('starting to update all friends') for chatId in db.getAllChatPartners(): updateFriends(Chat.getChat(chatId)) logger.debug('updating all friends finished')
def deleteFriendOfUser(handle, chatId): logger.debug("deleting friend with handle " + handle + " from user " + str(chatId)) query = "DELETE FROM friends WHERE friend = %s AND chatId = %s" insertDB(query, (handle, chatId))