async def _chan_duckstats(self, chan, context): ducc_record = list(ducc_db.find(channel=context)) total = len(ducc_record) if total == 0: await self.msg(modname, chan, [f"no duccs were in {context}!"]) return befriended = len([ducc for ducc in ducc_record if ducc["was_killed"] == False]) murdered = len([ducc for ducc in ducc_record if ducc["was_killed"] == True]) times = sorted( [(ducc["captured"] - ducc["appeared"], ducc["owner"]) for ducc in ducc_record], key=lambda i: i[0], ) fastest = times[0] slowest = times[-1] # Only calculate the average captures of captures done in less than 4 seconds. fast_times = [time[0] for time in times if time[0] <= 4] average = sum(fast_times) / len(fast_times) befriended_str = fmt.bold(fmt.green(befriended)) murdered_str = fmt.bold(fmt.red(murdered)) fastest_user_str = fmt.blue(fmt.zwnj(fastest[1])) slowest_user_str = fmt.blue(fmt.zwnj(slowest[1])) await self.msg( modname, chan, [ f"duck stats for {context}: {befriended_str} befriended, {murdered_str} murdered. The fastest capture was by {fastest_user_str} in {fastest[0]:,.2f}; the slowest capture was by {slowest_user_str} in {slowest[0]:,.2f}. The average speed of a ducc capture is {average:,.2f} seconds." ], )
async def filtersed(self, chan, src, msg): enabled = configuration.get(self.network, chan, "sed-pattern", cast=bool) if not enabled: return sections = IS_SED_comp.match(msg).groupdict() user = sections["user"] or src sedinput = None if chan in self.backlog and len(self.backlog[chan]): backlog = list(reversed(self.backlog[chan])) for back_msg in backlog: if back_msg[0] == user: if IS_SED_LIBERAL.match(back_msg[1]): continue if _sed(sections["sed"], back_msg[1]) is sedinput: continue sedinput = back_msg[1] break if not sedinput: return sedded = _sed(sections["sed"], sedinput) user_noping = fmt.zwnj(user) await self.message(chan, f"<{user}> {sedded}")
async def _user_duckstats(self, chan, user): user_noping = fmt.zwnj(user) ducc_record = list(ducc_db.find(owner=user)) total = len(ducc_record) channels = _format_items([ducc["channel"] for ducc in ducc_record]) if total == 0: await self.msg(modname, chan, [f"{user} doesn't have any duccs!"]) return befriended = len([ducc for ducc in ducc_record if ducc["was_killed"] == False]) murdered = len([ducc for ducc in ducc_record if ducc["was_killed"] == True]) times = sorted([ducc["captured"] - ducc["appeared"] for ducc in ducc_record]) fastest = times[0] slowest = times[-1] # Only calculate the average captures of captures done in less than 4 seconds. fast_times = [time for time in times if time <= 4] average = sum(fast_times) / len(fast_times) befriended_str = fmt.bold(fmt.green(befriended)) murdered_str = fmt.bold(fmt.red(murdered)) first_message = "" if total == 1: first_message = f"{user_noping} has captured {total} duccs, befriending {befriended_str} and murdering {murdered_str}." else: first_message = f"{user_noping} has captured {total} duccs, befriending {befriended_str} and murdering {murdered_str}. Their fastest capture was {fastest:,.2f} seconds, and their slowest capture was {slowest:,.2f} seconds; on average, they capture duccs in about {average:,.2f} seconds." await self.msg( modname, chan, [first_message, f"{user_noping} has duccs in: {channels}"] )
async def visit(self, ch, src, msg): """ :name: visit :hook: cmd :help: water your (or someone else's) botany plant :args: @username:str :aliases: water """ username = src if len(msg) > 1: username = msg.split()[0] user_noping = fmt.zwnj(username) visits_file = VISITORS_FILE.format(username) info = {} # water the plant by adding ourselves to the end of the recipient's # visitors.json file in their homedir visitors = [] try: info = _plant_info(username) visitors = _plant_visitors(username) except FileNotFoundError: await self.msg(modname, ch, [f"I couldn't find {user_noping}'s plant :/"]) return is_dead = info["is_dead"] description = info["description"] # don't bother watering dead plants if is_dead: await self.ctcp(ch, "ACTION", f"gazes sadly at {user_noping}'s dead {description}") return visitor = self.nickname if username != src: if os.path.exists(f"/home/{src}/.botany/"): visitor = src # just add ourselves to the visitors list. botany will take care of the # rest the next time that user opens it. visitors.append({"timestamp": int(time.time()), "user": visitor}) # json.load complains if the file object is write-able try: with open(visits_file, "w") as fwvisit: json.dump(visitors, fwvisit, indent=4) await self.ctcp(ch, "ACTION", f"waters {user_noping}'s {description}!") except PermissionError: await self.ctcp( ch, "ACTION", f"peeks at {user_noping}'s {description} over their garden wall", )
async def whoami(self, chan, src, msg): source = "" if not bot_conf.upstream == None: source = "".join([fmt.zwnj(i) for i in bot_conf.upstream]) owner = fmt.zwnj(bot_conf.botmaster) email = fmt.zwnj(bot_conf.email[0]) + "@" + bot_conf.email[1] response = bot_conf.rollcall_fmt.format( nickname=self.nickname, description=bot_conf.description, prefix=bot_conf.prefix, owner=owner, source=source, email=email, ) await self.msg(modname, chan, [response])
async def fiends(self, chan, src, msg): channel = chan if len(msg) > 1: channel = msg chan_duccs = ducc_db.find(channel=channel, was_killed=True) owners = [ducc["owner"] for ducc in chan_duccs] formatted = _format_items(owners, itemmap=lambda i: fmt.zwnj(i)) await self.msg(modname, chan, [f"ducc enemies in {chan}: {formatted}"])
async def _all_duckstats(self, chan): ducc_record = list(ducc_db.find()) total = len(ducc_record) channels = [ducc["channel"] for ducc in ducc_record] channels_total = len(utils.dedup(channels)) channel_frens = [ ducc["channel"] for ducc in ducc_record if ducc["was_killed"] == False ] channel_foes = [ ducc["channel"] for ducc in ducc_record if ducc["was_killed"] == True ] befriended = len([ducc for ducc in ducc_record if ducc["was_killed"] == False]) murdered = len([ducc for ducc in ducc_record if ducc["was_killed"] == True]) times = sorted( [ (ducc["captured"] - ducc["appeared"], ducc["owner"], ducc["channel"]) for ducc in ducc_record ], key=lambda i: i[0], ) fastest = times[0] slowest = times[-1] average = sum([t[0] for t in times]) / len(times) befriended_str = fmt.bold(fmt.green(befriended)) murdered_str = fmt.bold(fmt.red(murdered)) total_str = fmt.bold(fmt.cyan(total)) channels_total_str = fmt.yellow(channels_total) channels_str = _format_items(channels) fastest_user_str = fmt.blue(fmt.zwnj(fastest[1])) slowest_user_str = fmt.blue(fmt.zwnj(slowest[1])) await self.msg( modname, chan, [ f"duck stats for {channels_total_str} channels: {befriended_str} befriended, {murdered_str} murdered, {total_str} total. Fastest capture was {fastest[0]:,.2f} by {fastest_user_str} in {fastest[2]}; slowest was {slowest[0]:,.2f} by {slowest_user_str} in {slowest[2]}.", f"top channels: {channels_str}", ], )
async def owoify(self, chan, src, msg): ms = "" try: ms = common.get_backlog_msg(self, chan, msg) except: await self.msg(modname, chan, [f"my backwog is two showt!"]) return usr = fmt.zwnj(ms[0]) res = _owo_text(ms[1]) return (Msg.RAW, f"<{usr}> {res}")
async def mock(self, chan, src, msg): ms = None if chan in self.backlog: backlog = list(reversed(self.backlog[chan])) for back_msg in backlog: if back_msg[0] == msg: ms = back_msg break if not ms: await self.msg(modname, chan, [f"couldn't find anything to mock"]) return mocked = _mock_text(ms[1]) usr = fmt.zwnj(ms[0]) return (Msg.RAW, f"<{usr}> {mocked}")
async def pigify(self, c, n, m): # pig-latin module (·(oo)·) (・ั(00)・ั) # # this command is pretty useless, but as it was the first module # to be added to this bot, I'm leaving it here for, uh, historical # reasons. ms = [] if len(m) > 0: ms = [n, m] else: try: ms = common.get_backlog_msg(self, c, m) except: await self.msg(modname, c, [f"ymay acklogbay isway ootay ortshay!"]) return pigtext = pig.pigify(ms[1]) pigface = pig.pig_ascii() usr = fmt.zwnj(ms[0]) return (Msg.RAW, f"<{usr}> {pigtext} {pigface}")
async def botany(self, ch, src, msg): """ :name: botany :hook: cmd :help: check on your (or someone else's) botany plant :args: @username:str """ username = src if len(msg) > 1: username = msg.split()[0] user_noping = fmt.zwnj(username) info = {} visitors = [] try: info = _plant_info(username) visitors = _plant_visitors(username) except FileNotFoundError: await self.msg(modname, ch, [f"I couldn't find {user_noping}'s plant :/"]) return description = info["description"] info_watered_on = datetime.utcfromtimestamp(info["last_watered"]) generation = info["generation"] score = math.ceil(info["score"]) # the age string may have fields where the value is only one digit. # fix this with a regex to make it palatable to datetime.strptime. age_fields = AGE_RE.match(info["age"]).groupdict() age_fields2 = {} for (name, param) in age_fields.items(): if param: age_fields2[name] = int(param) age_delta = timedelta(**age_fields2) # when a user is visited, the info file isn't updated; instead, the visitor # and visit timestamp is stored in visitors.json. We need to check both of # these files (the info file and visitors.json) to find the last time the plant # was watered. last_visit = datetime.utcfromtimestamp(0) last_visitor = None if len(visitors) > 0: last_visit = datetime.utcfromtimestamp(visitors[-1]["timestamp"]) last_visitor = visitors[-1]["user"] if info_watered_on > last_visit: watered_on = info_watered_on else: watered_on = last_visit watered_by_str = "" if not last_visitor == None: last_visitor_noping = fmt.zwnj(last_visitor) watered_by_str = f" by {last_visitor_noping}" last_watered = datetime.now() - watered_on str_last_watered = format_timedelta(last_watered, locale="en_US") str_age = format_timedelta(age_delta, locale="en_US") is_dead = False if info["is_dead"] or last_watered.days >= 5: is_dead = True if is_dead: await self.msg(modname, ch, [f"{user_noping}'s {description} is dead!"]) else: await self.msg( modname, ch, [ f"{user_noping}'s {description} was last watered {str_last_watered} ago{watered_by_str}. It has {score:,} points, is {str_age} old, and is on generation {generation}." ], )