def sendHookah(self, uid): """ Send a set of hookah parts to a user """ with InventoryLock.lock: oos = self.outOfStock(self._saveLast) if len(oos) > 0: print(str(oos)) return "Not enough hookah parts." self.removeHookahFromDisplay(0) hookahItems = [4510, 4511, 4515, 4516, 4512, 4513] items = dict((iid, 1) for iid in hookahItems) k = Kmail(uid=uid, text="Hookah override. DO NOT FORGET TO COOK THE " "NOODLES AND VIAL.\n\nSeriously, don't forget.") k.addItems(items) self.sendKmail(k) return "Hookah sent to >>{}".format(uid)
def _sendHelpMessage(self, message): uid = message.uid helptext = [] for m in self._modules: mod = m.module clanOnly = m.clanOnly permission = m.permission no_permissions = (permission in _noPermissions) if not clanOnly or self.checkClan(uid): if (no_permissions or permission in self.properties.getPermissions(uid)): newTxt = mod.extendedCall('kmail_description') if newTxt is not None: if newTxt not in helptext: if not no_permissions: newTxt = "[ADMIN] " + newTxt print newTxt print permission helptext.append(newTxt) if not helptext: txt = "Sorry, I don't have any help available." if self._showChatHelpMessage: helptext.append( "CHAT HELP: If you want a list of chat commands, " "send me a PM with the text '!help' for all " "available commands. You can also use " "'!help COMMAND_NAME' to get detailed information on " "a specific command.") txt = '\n\n'.join(helptext) return [KmailResponse(self, self, Kmail(uid, txt))]
def _heartbeat(self): now = time.time() if (self._lastCheck is None or now - self._lastCheck > 60 * 60 * 24): self._checkNew() if _versionGreater(self._lastAvailableVersion, self.properties.version, self._notifyOn): if self._chatEvery > 0: if now - self._lastChatNotify > self._chatEvery: self.chat("New version available: {}".format(_url)) self._lastChatNotify = now if self._firstcheck: self._firstcheck = False updateAdmins = self.properties.getAdmins('update_notify') for uid in updateAdmins: lastKmail = self._kmailed.get(uid, 0) if now - lastKmail >= 14 * 24 * 60 * 60: k = Kmail( uid, "New cwbot version ({}) available: {}".format( self._lastAvailableVersion, _url)) self.sendKmail(k) self._kmailed[uid] = now else: self.log( 'Already kmailed {} about update.'.format(uid)) lastPm = self._privateMessaged.get(uid, 0) if now - lastPm >= 7 * 24 * 60 * 60: self.whisper( uid, "New cwbot version available: {}".format(_url)) self._privateMessaged[uid] = now else: self.log('Already pm\'ed {} about update.'.format(uid))
def _boot(self, uid): r1 = RemovePlayerFromClanWhitelistRequest(self.session, uid) self.tryRequest(r1) r2 = BootClanMemberRequest(self.session, uid) self.tryRequest(r2) if self._bootMessage is not None: self.sendKmail(Kmail(uid, self._bootMessage))
def sendHookah(self, uid): """ Send a set of hookah parts to a user """ with InventoryLock.lock: oos = self.outOfStock(self._saveLast) if len(oos) > 0: print(str(oos)) return "Not enough hookah parts." self.removeHookahFromDisplay(0) hookahItems = [4510,4511,4515,4516,4512,4513] items = dict((iid, 1) for iid in hookahItems) k = Kmail(uid=uid, text="Hookah override. DO NOT FORGET TO COOK THE " "NOODLES AND VIAL.\n\nSeriously, don't forget.") k.addItems(items) self.sendKmail(k) return "Hookah sent to >>{}".format(uid)
def processNewCommunications(self): """ This function is called every second or two (as specified in modules.ini) and downloads and processes new chats/kmails. """ msgs = self._c.getNewChatMessages() self._processChat(msgs) # check for mail handler issues if self._mailHandler.exception.is_set(): self._mailHandler.join() # get new mail newKmail = Kmail.fromPyKol(self._mailHandler.getNextKmail()) while newKmail is not None: responses = self._processKmail(newKmail) try: # send responses to MailHandler kmails = [r.kmail for r in responses] self._mailHandler.respondToKmail(newKmail.info['id'], map(Kmail.toPyKol, kmails)) except Exception as e: for r in responses: r.manager.kmailFailed(r.module, r.kmail, e) raise newKmail = Kmail.fromPyKol(self._mailHandler.getNextKmail())
def dispatchKmail(self, uid, dailyDigest=False, noThrow=False): with self._lock: now_ = datetime.datetime.now(utc) txt = ("Hobopolis chat monitor violations for {} " "(* indicates player in clan chat):\n\n".format( utcToArizona(now_, '%x'))) tf1 = "%d %b %I:%M %p" tf2 = "%I:%M %p" violators = [ u for u, v in self._violations.items() if numViolations(v) > self.numWarnings ] if len(violators) == 0 and not dailyDigest: return if len(violators) > 0: violateStrs = [] self.log("Violators: {}".format(violators)) for uname in violators: vList = self._violations[uname] timeRanges = [[ utcToArizona(rBegin, tf1), utcToArizona(rEnd, tf2) ] for rBegin, rEnd in violationTimeRanges(vList)] s = uname + " in time range(s): " s += ", ".join("({0[0]} - {0[1]})".format(range_) for range_ in timeRanges) # mark players in clan chat (but not in hobopolis) if any(v.violation and v.data.get('inClan', False) for v in vList): s = "*" + s # note if they joined /hobopolis timeJoin = next((v.time for v in vList if not v.violation), None) if timeJoin is not None: s += ("; joined /hobopolis at {}".format( utcToArizona(timeJoin, tf1))) violateStrs.append(s) txt += "\n".join(violateStrs) else: txt += "No users were in violation of Hobopolis chat rules." k = Kmail(uid=uid, text=txt) self.log("Sending HoboChatMon kmail to {}...".format(uid)) self.sendKmail(k) return True
def registerViolation(self, userName, inClanChat): with self._lock: violations = self._violations.get(userName, []) violations.append( ChatEvent(userName, True, {'inClan': inClanChat})) numV = numViolations(violations) r = SearchPlayerRequest(self.session, userName) ul = self.tryRequest(r) uid = ul["players"][0]["userId"] txt = ('Hi there! You\'ve been detected adventuring in hobopolis without listening to the ' 'hobopolis chat channel! Please type "/l hobopolis" into chat before continuing ' 'to adventure in hobopolis. Thanks!') k = Kmail(uid=uid, text=txt) self.debugLog("Warning {} about chat violation".format(userName)) self.sendKmail(k) self.log("Detected {} adventuring in hobopolis without being " "in channel (violation number #{})!" .format(userName, numV)) self._violations[userName] = violations
def sendChatLog(self, channel, uid): chars = 0 messages = [] with self._lock: self.reduceChatLog() for msg in (m for m in reversed(self._chatlog) if m['channel'] == channel): spacer = "" if msg['type'] == "emote" else ":" line = ("[{}] {}{} {}" .format(msg['time'].strftime("%I:%M:%S"), msg['user'], spacer, msg['text'])) chars += len(line) + 1 if chars <= MAX_KMAIL: messages.append(line) else: break msgText = '\n'.join(reversed(messages)) self.sendKmail(Kmail(uid=uid, text=msgText)) return True
def _doBooting(self, simulate): bootedMembers = [] if self._daysUntilBoot <= 0: return self.log("Running bootings...") self._refreshClanMembers() checkSeconds = _daysToSecs(self._daysUntilBoot) curTime = time.time() numRecords = len(self._userDb) i = 0 for uid_s, record in self._userDb.items(): i += 1 if self._stopNow.is_set(): break if not record.get('updated', False): continue nextCheckTime = record.get('lastActiveCheck', 0) + checkSeconds if curTime >= nextCheckTime: prefix = "[{}/{}] ".format(i, numRecords) booted = self._bootIfInactive(int(uid_s), simulate, prefix) if booted: bootedMembers.append(uid_s) else: nextCheckDays = 1 + _secsToDays(nextCheckTime - curTime) self.debugLog("[{}/{}] Skipping boot check for {}; will " "check in {} days.".format( i, numRecords, record['userName'], nextCheckDays)) self.log("Done running bootings.") if bootedMembers: bootedList = [ "{} (#{}) [{}]".format(record['userName'], record['userId'], record['rank']['rankName']) for key, record in self._userDb.items() if key in bootedMembers ] txt = ("The following users have been booted and/or " "removed from the clan whitelist:\n\n{}\n".format( "\n".join(bootedList))) self.log("Booted members: {}".format(", ".join(bootedList))) for uid in self.properties.getAdmins("boot_clan_member_notify"): self.sendKmail(Kmail(uid, txt))
def _processCommand(self, message, cmd, args): if cmd == "timeline": dvid = self._lastRaidlog.get('dvid') if (self._dungeonActive() or dvid is None or str(dvid) not in self._pastes): return ("You can't get the timeline while the dungeon is " "active.") data = self._pastes[str(dvid)] if data['error']: return ("Error with timeline: {}".format(data['url'])) return "Timeline for current instance: {}".format(data['url']) elif cmd == "timelines": timelines = self._pastes.values() timelines.sort(key=lambda x: x['time'], reverse=True) strings = [] for item in timelines: urlText = item['url'] if not item['error'] else "ERROR" dvidText = item['dvid'] dt_utc = datetime.datetime.fromtimestamp( item['time'], pytz.utc) dt_az = dt_utc.astimezone(_tz) timeText = dt_az.strftime("%a %d %b %y") kisses = item['kisses'] strings.append("{} - {} [{} kisses]: {}".format( timeText, dvidText, kisses, urlText)) sendString = "" for string in strings: if len(string) + len(sendString) > 1500: break sendString += string + "\n" if sendString == "": return "No timelines in memory." self.sendKmail( Kmail( message['userId'], "Here are the most recent Dreadsylvania " "instances:\n\n{}".format(sendString))) return "Timelines sent."
def _processCommand(self, message, cmd, args): if cmd == "die": args = args.strip() if args == "": args = "0" try: t = int(args) e = kol.Error.Error("Manual crash") e.timeToWait = t * 60 + 1 self.chat("Coming online in {} minutes.".format(t)) raise e except kol.Error.Error: raise except: pass return "Invalid argument to !die '{}'".format(args) elif cmd == "simulate": self.parent.director._processChat([{ 'text': args, 'userName': "******", 'userId': -2, 'channel': "hobopolis", 'type': "normal", 'simulate': True }]) return "Simulated message: {}".format(args) elif cmd == "spam": return "\n".join(["SPAM"] * 15) elif cmd == "restart": self._raiseEvent("RESTART", "__system__") elif cmd == "raise_event": r = self._raiseEvent(args) return "Reply to event '{}': {}".format(args, r) elif cmd == "kmail_test": n = 1000 try: n = int(args) except Exception: pass text = "" count = 0 startChar = "A" while n >= 100: count += 100 n -= 100 newText = "A" * 100 + "{}".format(count) newText = startChar + newText[-99:] text += newText startChar = " " k = Kmail(message['userId'], text) self.sendKmail(k) elif cmd == "bot_status": r = StatusRequest(self.session) d = self.tryRequest(r) return "\n".join("{}: {}".format(k, v) for k, v in d.items() if k not in ["pwd", "eleronkey"]) elif cmd == "inclan": tf = self.parent.checkClan(int(args)) return str(tf) elif cmd == "plist": return str(self.properties.getPermissions(int(args))) elif cmd == "say": txtMatch = re.search(r"^\s*(?:/(\w+))?\s*(.*)", args) if txtMatch: self.chat(txtMatch.group(2), channel=txtMatch.group(1)) return None return "Error matching text" return None
def newMessage(self, uid, text="", meat=0): """ Create a new Kmail object. """ return Kmail(uid, text, meat)
def _refreshClanMembers(self): self.debugLog("Fetching clan member list...") curTime = int(time.time()) newUserDb = copy.deepcopy(self._userDb) for record in newUserDb.values(): record['updated'] = False # member info comes from two sources: members in the clan right # now are in the detailed roster; members who are whitelisted to a # different clan are on the whitelist. Sadly, the information # contained in each is different. It's impossible to find the karma # of a user who is away on whitelist, and you can't find the # title of a user in the detailed roster. members = defaultdict(dict) r1 = ClanWhitelistRequest(self.session) d1 = self.tryRequest(r1) r2 = ClanDetailedMemberRequest(self.session) d2 = self.tryRequest(r2) if len(d2['members']) == 0: raise RuntimeError("Could not detect any members of clan!") self.debugLog("{} members on whitelist".format(len(d1['members']))) self.debugLog("{} members in clan".format(len(d2['members']))) for record in d1['members']: uid = int(record['userId']) entry = { 'userId': uid, 'userName': record['userName'], 'rank': self._ranks.get(_rankTransform(record['rankName']), _unknownRank), 'whitelist': True, 'updated': True } self._titles[uid] = record['clanTitle'] members[uid] = entry for record in d2['members']: uid = int(record['userId']) entry = { 'userId': uid, 'userName': record['userName'], 'rank': self._ranks.get(_rankTransform(record['rankName']), _unknownRank), 'inClan': True, 'karma': record['karma'], 'updated': True } members[uid].update(entry) self.debugLog("{} members total".format(len(members))) newMembers = [] # add some default values and put these in the database for uid, record in members.items(): record.setdefault('karma', 0) record.setdefault('inClan', False) record.setdefault('whitelist', False) record['lastData'] = curTime key = str(uid) if key not in newUserDb: newUserDb[key] = { 'lastPromotion': self._rolloverTime, 'entryCreated': self._rolloverTime, 'lastActiveCheck': 0 } newMembers.append(key) newUserDb[key].update(record) # delete old users from database deleteAfterSeconds = _daysToSecs(90) newUserDb = { k: v for k, v in newUserDb.items() if v.get('lastData') >= curTime - deleteAfterSeconds } # remove deleted users from _inactiveAstrals for k in self._inactiveAstrals.keys(): if k not in newUserDb: del self._inactiveAstrals[k] if newMembers: newMemberList = [ "{} (#{}){}".format( record['userName'], record['userId'], "" if record['inClan'] else " [whitelist only]") for key, record in newUserDb.items() if key in newMembers ] txt = ("The following users are new clan members:\n\n{}\n".format( "\n".join(newMemberList))) self.log("New members: {}".format(", ".join(newMemberList))) for uid in self.properties.getAdmins("new_clan_member_notify"): self.sendKmail(Kmail(uid, txt)) self._userDb = newUserDb self.debugLog("UserDb updated to {} entries".format(len(newUserDb)))
def _processCommand(self, msg, cmd, args): if cmd in ["points", "score"]: hasPermission = "dread_points" in self.properties.getPermissions( msg['userId']) txt = "Current Dreadsylvania scores:\n\n" sortedUsers = list(self._userPoints.items()) sortedUsers.sort(key=lambda x: -x[1]) for user, points in sortedUsers: events = self._userEvents[user] if not events: continue userName = self._properUserNames[user] userId = self._userIds[user] userTxt = ("[{}] {} (#{}): ".format( intOrFloatToString(points, 2), userName, userId)) userEventTxt = [] for e, num in events.items(): value = self._eventValue[e] userEventTxt.append("{} x {} ({:+})".format( num, e, value * num)) userTxt += ", ".join(userEventTxt) txt += userTxt + "\n" if hasPermission and not self._dungeonActive(): txt += "(use '!points official' to run drawing)\n" if args.lower().strip() in ["official", "test"]: if hasPermission: if (self._dungeonActive() and args.lower().strip() != "test"): return ("I can't perform the drawing until the " "instance is done.") else: self._performDrawing() else: return "You don't have permission to distribute loot." elif args and args.lower().strip().split()[0] in ["give", "grant"]: if not hasPermission: return "You don't have permission to do that." isPM = (msg['type'] == "private") if isPM: return "You can't grant points in a PM." match = re.search(r'(-?\d+)(?: points?)? to (.*)', args, flags=re.IGNORECASE) if not match: return "Invalid format." pointAmount = int(match.group(1)) user = _nameKey(match.group(2)) if user not in self._userPoints: return "No such user {}.".format(match.group(2)) self._grantedPoints.setdefault(user, 0) self._grantedPoints[user] += pointAmount self._processLog(self._lastRaidlog) return ("Granted {} points to {}".format( pointAmount, self._properUserNames[user])) else: self.sendKmail(Kmail(msg['userId'], txt)) return "Kmail sent." return None
def _sendMonsterList(self, uid): text = ("Available monsters:\n\n" + "\n".join(sorted(self._monsters.keys()))) self.sendKmail(Kmail(uid, text)) return "Monster list sent."