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 handleSettingsCallback(chat: Chat, data, callback): if data != "": logger.critical("Invalid callback settings data: " + data) else: chat.editMessageText(callback['message']['message_id'], "What do you want to change?", getReplyMarkup(getSettingsButtons()))
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 _run(self): while True: found = False with self._lock: for i in range(len(self._q)): if self._q[i].qsize() > 0: (timeStamp, callbackFun) = self._q[i].get() found = True break if not found: self._lock.wait() if found: startT = time.time() try: callbackFun() timeInSpooler = startT - timeStamp timeForFun = time.time() - startT if timeInSpooler > 0.001 or timeForFun > 0.001: perfLogger.info( "time in spooler: {:.3f}s; time for fun: {:.3f}s". format(timeInSpooler, timeForFun)) except Exception as e: logger.critical('%s spooler error %s', self._name, e, exc_info=True) sleepT = startT + self._timeInterval - time.time() if sleepT > 0: time.sleep(sleepT)
def handleSetupCallback(chat, data, callback): if data == "": showSetupPage(chat, data, callback) else: funs = { 'timezone': handleChangeTimezone, 'handle': handleSetUserHandlePrompt, 'apikey': handleSetAuthorization, } if data not in funs: logger.critical("wrong setup data: " + str(data)) else: return funs[data](chat)
def run(self): lastTime = -1 while True: waitTime = lastTime + self._updateInterval - time.time() if waitTime > 0: time.sleep(waitTime) try: startT = time.time() self._doTask() if self._logPerf: perfLogger.info("service: {:.3f}s".format(time.time()-startT)) except Exception as e: logger.critical('Run error %s', e, exc_info=True) lastTime = time.time()
def handleWidthChange(chat: Chat, data, callback): def getMsg(width): table = Table( ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"], []) text = ( "Configure how many columns you want to display. Choose the maximum " "value which still displays the table correctly.\n" "Note: This setting is global for the chat, make sure it works on all of " "your devices.\n") text += table.formatTable(chat.width) buttons = [ [{ "text": "-", "callback_data": "width:-" }, { "text": "+", "callback_data": "width:+" }], [{ "text": "👈 Back to General Settings", "callback_data": "general:" }], ] return text, buttons warningText = None if data == '': pass # initial call, don't change elif data == '+': if chat.width == 12: warningText = "❗️You reached the maximum table width❗️" else: chat.width = chat.width + 1 elif data == '-': if chat.width == 4: warningText = "❗️You reached the minimum table width❗️" else: chat.width = chat.width - 1 else: logger.critical("unrecognized data at handle width: " + str(data)) if warningText: return warningText else: text, buttons = getMsg(chat.width) chat.editMessageText(callback["message"]["message_id"], text, settings.getReplyMarkup(buttons))
def _analyseContest(self, contestId, friends, firstRead): ranking = cf.getStandings(contestId, friends, forceRequest=True) if ranking is False: if firstRead: logger.critical( "------------ ranking not fetched during firstRead ----------------------------" ) logger.critical( "Aborting to avoid resending of solved notifications ...") os._exit(1) return results = ranking['rows'] for row in results: self._analyseRow(contestId, row, ranking, firstRead) if ranking['contest']['phase'] != 'FINISHED' and not firstRead: self._updateStandings(contestId, db.getAllChatPartners())
def handleChatCallback(chat: Chat, data, callback): answerToast = None if data == "": buttons, title = getMenu(chat) elif data.startswith("config-page"): page = int(data[len("config-page"):]) buttons, title = getButtonRows(chat, page) elif data.startswith("toggle-"): answerToast, buttons, title = toggleFriendsSettings( chat, data[len("toggle-"):]) elif data == "handlepress": return elif data == "decNotifyLvl": if chat.notifyLevel == 0: return "You already disabled all friend notifications" else: db.updateToNotifyLevel( chat.chatId, chat.notifyLevel - 1, chat.notifyLevel, ) chat.notifyLevel -= 1 buttons, title = getMenu(chat) elif data == "incNotifyLvl": if chat.notifyLevel >= len(NOTIFY_LEVEL_DESC) - 1: return "You already enabled all friend notifications" else: db.updateToNotifyLevel(chat.chatId, chat.notifyLevel + 1, chat.notifyLevel) chat.notifyLevel += 1 buttons, title = getMenu(chat) elif data == "hoverNotifyLvl": return elif data == "reset": db.updateToNotifyLevel(chat.chatId, chat.notifyLevel, reset=True) buttons, title = getMenu(chat) else: logger.critical("no valid bahaviour option for notify settings: " + data) replyMarkup = settings.getReplyMarkup(buttons) msgId = callback['message']['message_id'] chat.editMessageText(msgId, title, replyMarkup) return answerToast
def handleCallbackQuery(callback): chat = ChatFunctions.getChat(str(callback['message']['chat']['id'])) data = callback['data'] if not ":" in data: logger.critical("Invalid callback data: " + data) return pref, suff = data.split(":", 1) funs = { "settings": handleSettingsCallback, "general": general_settings.handleSetupCallback, "behavior": behavior_settings.handleChatCallback, "friend_notf": notify_settings.handleChatCallback, "width": widthSelector.handleWidthChange, } if pref not in funs: logger.critical("Invalid callback prefix: " + pref + ", data: " + suff) else: retMsg = funs[pref](chat, suff, callback) tg.requestSpooler.put( lambda: tg.sendAnswerCallback(chat.chatId, callback['id'], retMsg), priority=0)
def handleChatCallback(chat, data, callback): answerText = None with chatsLock: if data == "polite": chat.polite = not chat.polite if chat.polite: answerText = "👿 This is what I call weakness…" else: answerText = "😈 Welcome back to the dark side." elif data == "reply": chat.reply = not chat.reply elif data == "reminder2h": chat.reminder2h = not chat.reminder2h elif data == "reminder1d": chat.reminder1d = not chat.reminder1d elif data == "reminder3d": chat.reminder3d = not chat.reminder3d elif data != "": logger.critical("no valid bahaviour option: " + data) buttons = getChatSettingsButtons(chat) replyMarkup = settings.getReplyMarkup(buttons) chat.editMessageText(callback['message']['message_id'], "Change the behavior of the bot:", replyMarkup) return answerText
def sendRequest(method, params, authorized=False, chat=None): rnd = random.randint(0, 100000) rnd = str(rnd).zfill(6) tailPart = method + '?' if authorized: try: if chat == None or chat.apikey == None or chat.secret == None: # maybe we don't have apikey so we cannot request friends or smt return False params['apiKey'] = str(chat.apikey) params['time'] = str(int(time.time())) except Exception as e: logger.critical("%s", e, exc_info=True) return False for key, val in sorted(params.items()): tailPart += str(key) + "=" + urllib.parse.quote(str(val)) + "&" tailPart = tailPart[:-1] if authorized: hsh = util.sha512Hex(rnd + '/' + tailPart + '#' + chat.secret) tailPart += '&apiSig=' + rnd + hsh request = codeforcesUrl + tailPart startWait = time.time() waitTime = endTimes.get() + 1 - time.time() if waitTime > 0: time.sleep(waitTime) startT = time.time() try: r = requests.get(request, timeout=15) except requests.exceptions.Timeout as errt: logger.error("Timeout on Codeforces.") return False except requests.exceptions.ChunkedEncodingError as e: logger.error("ChunkedEncodingError on CF: %s", e) return False except Exception as e: logger.critical('Failed to request codeforces: \nexception: %s\n', e, exc_info=True) return False finally: perfLogger.info("cf request " + method + ": {:.3f}s; waittime: {:.3f}".format( time.time() - startT, startT - startWait)) endTimes.put(time.time()) if r.status_code != requests.codes.ok: if r.status_code == 429: logger.error("too many cf requests... trying again") return sendRequest(method, params, authorized, chat) elif r.status_code // 100 == 5: logger.error("Codeforces Http error " + str(r.reason) + " (" + str(r.status_code) + ")") else: try: r = r.json() handleCFError(request, r, chat) except simplejson.errors.JSONDecodeError as jsonErr: logger.critical("no valid json; status code for cf request: " + str(r.status_code) + "\n" + "this request caused the error:\n" + str(request), exc_info=True) return False else: try: r = r.json() if r['status'] == 'OK': return r['result'] else: logger.critical("Invalid Codeforces request: " + r['comment']) return False except Exception as e: #TODO why does CF send an invalid json with http 200? logger.critical( "json decoding failed; status code for cf request: " + str(r.status_code) + "\n" + "this request caused the error:\n" + str(request) + "\n" + "text got back:\n" + str(r.text), exc_info=True) return False
try: msgText = open(sys.argv[1]).read()[:-1] # discard trailing newline logger.info("sending broadcast message:\n" + msgText) for chatId in Chat.chats: msg = msgText chat = Chat.chats[chatId] while True: m = re.search("\[%t [0-9]*\]", msg) if m is None: break tInSec = int(m.group()[4: -1]) timeLeft = int(tInSec - time.time()) delta = datetime.timedelta(seconds=timeLeft) dTime = f"*{util.displayTime(tInSec, chat.timezone)}* (in {':'.join(str(delta).split(':')[:2])} hours)" msg = msg[:m.span()[0]] + dTime + msg[m.span()[1]:] #print(f"chat: {chatId}: {msg}") chat.sendMessage(msg) time.sleep(1) while tg.requestSpooler._q[0].qsize() > 0: try: print(f"waiting {tg.requestSpooler._q[0].qsize()}") except Exception as ex: print(ex) time.sleep(1) logger.info("sending broadcasts finished") except Exception as e: logger.critical(str(e)) else: logger.error("wrong command line options\nusage: python3 sendBroadcast.py <file with text>") os._exit(0)