class Homepage(SimpleResponseCommand): condition = IsCommand(["homepage", "home"]) help = "`homepage`: link to Meowbot homepage" def get_message_args(self, context: CommandContext): return {"text": url_for("main.index", _external=True)}
class Dog(SimpleResponseCommand): condition = IsCommand(["dog"]) private = True def get_message_args(self, context: CommandContext): return {"text": "no", "icon_emoji": f"{Emoji.MONKACAT}"}
class Fail(SimpleResponseCommand): private = True condition = And(IsCommand(["fail"]), IsUser([get_admin_user_id()])) def get_message_args(self, context: CommandContext): raise Exception()
class AddCat(SimpleResponseCommand): condition = IsCommand(["addcat", "addacat", "registercat"]) help = "`addcat [name] [photo_url]`: add a cat to the database" def get_message_args(self, context: CommandContext): if len(context.args) != 2: return { "text": "Expected 2 args (name, url). " f"Got {len(context.args)}", "thread_ts": context.event.ts, } name, url = context.args # TODO: figure out why URLs are wrapped in <>. url = url[1:-1] if not validators.url(url): return { "text": f"`{url}` is not a valid URL", "thread_ts": context.event.ts, } row = Cat(name=name.lower(), url=url) meowbot.db.session.add(row) meowbot.db.session.commit() return { "attachments": [{ "text": f"Registered {name}!", "image_url": url }], "thread_ts": context.event.ts, }
class Ping(SimpleResponseCommand): condition = IsCommand(["ping"]) help = "`ping`: see if meowbot is awake" def get_message_args(self, context: CommandContext): return {"text": "pong!", "icon_emoji": f"{Emoji.PING_PONG}"}
class RemoveCat(SimpleResponseCommand): condition = IsCommand(["removecat"]) help = "`removecat [name] [number]`: delete a photo from the database" def get_message_args(self, context: CommandContext): if len(context.args) != 2: return context.api.chat_post_message({ "text": "Expected 2 args (name, number). " f"Got {len(context.args)}" }) name, number = context.args if not number.isnumeric(): return { "text": f"Second argument must be a number. Got `{number}`" } offset = int(number) if offset <= 0: return {"text": f"Number must be > 0. Got `{offset}`"} row = (Cat.query.filter_by(name=name.lower()).order_by( Cat.id).limit(1).offset(offset - 1).one_or_none()) if row is None: return {"text": "No matching rows"} meowbot.db.session.delete(row) meowbot.db.session.commit() return {"text": "Successfully removed!"}
class Meow(SimpleResponseCommand): condition = IsCommand(["meow"]) help = "`meow`: meow!" def get_message_args(self, context: CommandContext): return {"text": f"Meow! {Emoji.CATKOOL}"}
class Hmm(SimpleResponseCommand): condition = IsCommand(["hmm", "think", "thinking"]) help = ("`hmm`: thinking...", ) def get_message_args(self, context: CommandContext): return {"text": str(random.choice(list(Emoji.thinking())))}
class AdoptCat(SimpleResponseCommand): condition = IsCommand(["adoptcat"]) help = "`adoptcat [zipcode]`: get cat adoption info" def get_message_args(self, context: CommandContext): if len(context.args) == 1: (zip_code, ) = context.args if not zip_code.isnumeric(): return {"text": f"Zip code must be a number. Got `{zip_code}`"} elif len(context.args) > 1: return {"text": "Usage: `adoptcat [zipcode]`"} else: zip_code = get_default_zip_code() api_key = get_petfinder_api_key() petfinder_url = "http://api.petfinder.com/pet.find" r = requests.get( petfinder_url, params={ "key": api_key, "output": "basic", "animal": "cat", "count": "25", "location": zip_code, "format": "json", }, ) data = r.json() def pet_info(pet): url = ("https://www.petfinder.com/cat/" "{short_name}-{pet_id}/state/city/shelter-{shelter_id}/" ).format( short_name=pet["name"]["$t"].split(" ", 1)[0].lower(), pet_id=pet["id"]["$t"], shelter_id=pet["shelterId"]["$t"], ) photos = [ photo["$t"] for photo in pet.get("media", {}).get( "photos", {}).get("photo", []) if photo["@size"] == "pn" ] name = pet["name"]["$t"] sex = pet["sex"]["$t"] age = pet["age"]["$t"] return { "basic_info": f"{name} sex: {sex} age: {age} {url}", "photo": None if len(photos) == 0 else photos[0], } pets = random.sample( [pet_info(pet) for pet in data["petfinder"]["pets"]["pet"]], k=5) return { "attachments": [{ "text": pet["basic_info"], "image_url": pet["photo"] } for pet in pets], "thread_ts": context.event.ts, }
class GitHub(SimpleResponseCommand): condition = IsCommand(["github", "git", "source"]) help = "`github`: GitHub page for Meowbot" def get_message_args(self, context: CommandContext): return {"text": "https://github.com/pbhuss/meowbot"}
class TV(SimpleResponseCommand): condition = IsCommand(["tv", "television", "meowtv", "meowbottv"]) help = "`tv`: watch Meowbot TV" def get_message_args(self, context: CommandContext): return {"text": url_for("main.tv")}
class RefreshTV(SimpleResponseCommand): condition = IsCommand(["refreshtv", "refresh"]) help = "`refreshtv`: refresh Meowbot TV" def get_message_args(self, context: CommandContext): get_redis().incr("tvid") return {"text": "Refreshed tv!"}
class Debug(SimpleResponseCommand): private = True condition = And(IsCommand(["debug"]), IsUser([get_admin_user_id()])) def get_message_args(self, context: CommandContext): context._data["token"] = "REMOVED" return {"text": pformat(context._data)}
class Shrug(SimpleResponseCommand): condition = IsCommand(["shrug"]) help = "`shrug`: shrug" private = True def get_message_args(self, context: CommandContext): return {"text": rf"¯\_{Emoji.CAT}_/¯"}
class Rainbow(SimpleResponseCommand): condition = IsCommand(["rainbow"]) help = "`rainbow [text]`: make rainbow text" def get_message_args(self, context: CommandContext): text = " ".join(context.args) return {"text": coloring(text, itertools.cycle(ALL_TEXT_COLORS))}
class Poop(SimpleResponseCommand): condition = IsCommand(["poop"]) help = "`poop`: meowbot poops" private = True def get_message_args(self, context: CommandContext): return {"icon_emoji": f"{Emoji.SMIRK_CAT}", "text": f"{Emoji.POOP}"}
class Christmas(SimpleResponseCommand): condition = IsCommand(["christmas"]) help = "`christmas [text]`: make Christmas text" def get_message_args(self, context: CommandContext): text = " ".join(context.args) return {"text": coloring(text, christmas_color())}
class Color(SimpleResponseCommand): condition = IsCommand(["color", "colour"]) help = "`color [text]`: add some color" def get_message_args(self, context: CommandContext): text = " ".join(context.args) return {"text": coloring(text, random_color())}
class ListCats(SimpleResponseCommand): condition = IsCommand(["listcats"]) help = "`listcats`: see all cats available for the `cat` command" def get_message_args(self, context: CommandContext): rows = meowbot.db.session.query(Cat.name).distinct() names = ", ".join(row.name for row in rows) return {"text": f"Cats in database: {names}"}
class CatCommand(SimpleResponseCommand): condition = IsCommand(["cat", "getcat"]) help = "`cat [name] [number]`: gives one cat" def get_message_args(self, context: CommandContext): if len(context.args) in (1, 2): name = context.args[0] num_photos = Cat.query.filter_by(name=name.lower()).count() if num_photos == 0: return {"text": f"No cats named {name} registered"} if len(context.args) == 2: number = context.args[1] if not number.isnumeric(): return { "text": "Second argument must be a number. " f"Got `{number}`" } number = int(number) if 1 <= number <= num_photos: offset = number - 1 else: offset = random.randint(0, num_photos - 1) else: offset = random.randint(0, num_photos - 1) row = (Cat.query.filter_by(name=name.lower()).order_by( Cat.id).limit(1).offset(offset).one()) self.image_url = row.url self.alt_text = name else: self.image_url = requests.head( "https://api.thecatapi.com/v1/images/search?" "format=src&mime_types=image/gif", headers={ "x-api-key": get_cat_api_key() }, ).headers["Location"] self.alt_text = "cat gif" return { "blocks": [{ "type": "image", "image_url": self.image_url, "alt_text": self.alt_text, }] } def post_run(self, context): for response in self.responses: if not response.ok: context.api.chat_post_message({ "channel": context.event.channel, "text": ("Image could not be retrieved by Slack: " f"{self.image_url}"), }) super().post_run(context)
class Fact(SimpleResponseCommand): condition = IsCommand(["fact", "catfact", "catfacts", "facts"]) help = "`fact`: get a cat fact" private = True def get_message_args(self, context: CommandContext): return { "text": requests.get("https://catfact.ninja/fact").json()["fact"] }
class Lacroix(SimpleResponseCommand): condition = IsCommand(["lacroix"]) help = "`lacroix`: meowbot recommends a flavor" def get_message_args(self, context: CommandContext): flavor = random.choice(list(Emoji.lacroix())) flavor_name = flavor.name.split("_")[0].capitalize() user = quote_user_id(context.event.user) return {"text": f"{user}: I recommend {flavor} {flavor_name} La Croix"}
class Magic8(SimpleResponseCommand): condition = IsCommand(["magic8", "8ball"]) help = "`magic8 [question]`: tells your fortune" def get_message_args(self, context: CommandContext): text = "{} asked:\n>{}\n{}".format( quote_user_id(context.event.user), " ".join(context.args), random.choice(magic_eight_ball_options), ) return {"text": text, "icon_emoji": f"{Emoji.EIGHT_BALL}"}
class EnableTV(SimpleResponseCommand): condition = And(IsCommand(["enabletv"]), IsUser([get_admin_user_id()])) help = "`enable`: this restores Meowbot TV" private = True def get_message_args(self, context: CommandContext): redis = get_redis() redis.delete("killtv") return { "text": "Meowbot TV has been enabled", }
class XKCD(SimpleResponseCommand): condition = IsCommand(["xkcd"]) help = "`xkcd [num]`: get today's xkcd, or a particular number" def get_message_args(self, context: CommandContext): if context.args: if len(context.args) != 1: return {"text": f"Usage: {self.get_help(context)}"} (num, ) = context.args if not num.isnumeric(): return {"text": f"Argument must be a number (got `{num}`)"} url = f"http://xkcd.com/{num}/info.0.json" else: url = "http://xkcd.com/info.0.json" resp = requests.get(url) if resp.status_code == 404: return {"text": "Comic not found"} data = resp.json() date = datetime.date(int(data["year"]), int(data["month"]), int(data["day"])).strftime("%b %d, %Y") return { "blocks": [ { "type": "image", "image_url": data["img"], "title": { "type": "plain_text", "text": data["safe_title"] }, "alt_text": data["alt"], }, { "type": "context", "elements": [ { "type": "mrkdwn", "text": f'#{data["num"]}' }, { "type": "mrkdwn", "text": date }, ], }, ] }
class Concerts(SimpleResponseCommand): condition = IsCommand(["concerts", "concert"]) help = "`concerts`: upcoming concerts at Yerba Buena Gardens Festival" def get_message_args(self, context: CommandContext): redis = get_redis() key = "concertcal" ical_data = redis.get(key) if ical_data is None: ical_data = requests.get( "https://ybgfestival.org/events/?ical=1&tribe_display=list" ).content redis.set(key, ical_data) # Expire at midnight PST redis.expireat( key, (arrow.utcnow().to("US/Pacific") + timedelta(days=1)).replace( hour=0, minute=0).timestamp, ) cal = ics.Calendar(ical_data.decode("utf-8")) events = cal.timeline.start_after(arrow.utcnow() - timedelta(hours=3)) colors = ["#7aff33", "#33a2ff"] return { "text": "Upcoming concerts at <https://ybgfestival.org/|" "Yerba Buena Gardens Festival>:", "attachments": [{ "title": event.name, "title_link": event.url, "text": event.description, "color": color, "fields": [ { "title": "When", "value": "{} - {}".format( event.begin.strftime("%a %b %d, %I:%M"), event.end.strftime("%I:%M %p"), ), "short": False, }, ], } for event, color in zip(events, colors)], }
class Shakespeare(SimpleResponseCommand): condition = IsCommand(["shakespeare"]) help = "`shakespeare`: generates a Shakespearean insult" def get_message_args(self, context: CommandContext): return { "text": "{}thou {} {} {}".format( " ".join(context.args) + " " if len(context.args) > 0 else "", random.choice(shakespeare_insult_start), random.choice(shakespeare_insult_middle), random.choice(shakespeare_insult_end), ) }
class Nyan(SimpleResponseCommand): condition = IsCommand(["nyan"]) help = "`nyan`: nyan" def get_message_args(self, context: CommandContext): return { "blocks": [{ "type": "image", "image_url": ("https://media.giphy.com/media/sIIhZliB2McAo/giphy.gif"), "alt_text": "nyan cat", }] }
class SetLocation(BaseCommand): condition = IsCommand(["setlocation"]) help = "`setlocation [location]`: set your default location for `weather`" def run(self, context: CommandContext): location = " ".join(context.args) redis = get_redis() redis.hset("user_location", context.event.user, location) context.api.chat_post_ephemeral( { "channel": context.event.channel, "user": context.event.user, "text": f"Set default location to {location}", } )
class Redis(SimpleResponseCommand): private = True condition = And(IsCommand(["redis"]), IsUser([get_admin_user_id()])) def get_message_args(self, context: CommandContext): if len(context.args) == 0: return {"text": "must specify at least one argument"} redis = get_redis() func_name, *func_args = context.args func = getattr(redis, func_name, None) if func is None: return {"text": f"invalid func_name: {func_name}"} arg_names = ", ".join(func_args) ret = func(*func_args) return {"text": f"Ran `{func_name}({arg_names})`\nReturned `{ret}`"}