async def pigeon_date(self, ctx, member: discord.Member): """Date other pigeons.""" if member.id == self.bot.owner_id or ctx.author.id == self.bot.owner_id: raise SendableException(ctx.translate("pigeon_undateable")) channel = self.get_pigeon_channel(ctx.guild) if member.id == ctx.author.id: raise SendableException(ctx.translate("cannot_date_self")) self.pigeon_check(ctx, member, name="pigeon2") pigeon1 = ctx.pigeon pigeon2 = ctx.pigeon2 date = Date(guild_id=ctx.guild.id, start_date=None, pigeon1=pigeon1, pigeon2=pigeon2) date.save() for pigeon in (date.pigeon1, date.pigeon2): pigeon.status = Pigeon.Status.dating pigeon.save() embed = self.get_base_embed(ctx.guild) embed.title = "Pigeon Dating" embed.description = f"{pigeon1.name} has invited {pigeon2.name} to a date." footer = [] footer.append(f"use '{ctx.prefix}pigeon accept' to accept") footer.append(f"or '{ctx.prefix}pigeon reject' to reject") embed.set_footer(text="\n".join(footer)) asyncio.gather(channel.send(embed=embed))
async def pigeon_reject(self, ctx): """Reject an invitation.""" pigeon = ctx.pigeon challenge = pigeon.current_activity if not isinstance(challenge, (Date, Fight)): raise SendableException(ctx.translate("nothing_to_reject")) if challenge.accepted: raise SendableException(ctx.translate("already_accepted")) challenge.accepted = False challenge.save() for pigeon in challenge.pigeons: pigeon.status = Pigeon.Status.idle pigeon.save() embed = self.get_base_embed(ctx.guild) embed.description = f"{ctx.author.mention} has rejected the {challenge.type.lower()}!" channel = self.get_pigeon_channel(ctx.guild) await channel.send( content= f"{challenge.pigeon1.human.user.mention} | {challenge.pigeon2.human.user.mention}", embed=embed)
def pigeon_check(self, ctx, member=None, name="pigeon", human=None): cmd = ctx.invoked_subcommand or ctx.command command_name = cmd.name pigeon = None if command_name not in self.subcommands_no_require_pigeon: pigeon = get_active_pigeon(member or ctx.author, human=human) setattr(ctx, name, pigeon) pigeon_raise_if_not_exist(ctx, pigeon, name=name) if pigeon.is_jailed: raise SendableException(ctx.translate(f"{name}_pigeon_jailed")) if pigeon.status == Pigeon.Status.idle: #TODO: optimize. activities = [] activities.append( pigeon.explorations.where(Exploration.finished == False)) activities.append(pigeon.outbox.where(Mail.finished == False)) activities.append(pigeon.fights.where(Fight.finished == False)) for activity_group in activities: for activity in activity_group: activity.finished = True activity.save() if command_name not in self.subcommands_no_require_available: pigeon_raise_if_unavailable(ctx, pigeon, name=name) if command_name not in self.subcommands_no_require_stats: pigeon_raise_if_stats_too_low(ctx, pigeon, name=name) if command_name in self.subcommands_pvp_only and not pigeon.pvp: raise SendableException(ctx.translate(f"{name}_pvp_not_enabled")) return pigeon
async def item_use(self, ctx, *, name): if name == "": raise commands.errors.MissingRequiredArgument("name") try: item = Item.get(name=name) except Item.DoesNotExist: raise SendableException("Item not found.") if not item.usable: raise SendableException(ctx.translate("item_not_usable")) waiter = BoolWaiter( ctx, prompt= f"`{item.description}`\nAre you sure you want to use this item?") if not await waiter.wait(): return await ctx.send(ctx.translate("canceled")) human = ctx.get_human() human_item, created = HumanItem.get_or_create(item=item, human=human) if created or human_item.amount == 0: raise SendableException(ctx.translate("you_missing_item")) used = False if item.code == "ban_hammer": await ctx.author.ban(reason="Ban hammer item was used.", delete_message_days=0) used = True elif item.code in ("big_bath", "big_snack", "big_toy"): pigeon = Pigeon.get_or_none(human=human, condition=Pigeon.Condition.active) if pigeon is None: raise SendableException(ctx.translate("you_no_pigeon")) used = True stat = { "big_bath": "cleanliness", "big_snack": "food", "big_toy": "happiness" }[item.code] data = {stat: 100} pigeon.update_stats(data, increment=False) pigeon.save() elif item.code == "milky_way": return await self.bot.get_command("milkyway create")(ctx) elif item.code == "jester_hat": member = await MemberWaiter( ctx, prompt=ctx.translate("prank_member_prompt")).wait() return await self.bot.get_command("prank nickname")(ctx, member) if used: human_item.amount -= 1 human_item.save() await ctx.success("Item has been successfully used.")
async def question(self, ctx, question: PersonalQuestion = None): if question is None: question = PersonalQuestion.get_random() if question is None or PersonalQuestion.select().where( PersonalQuestion.asked == False).count() == 0: raise SendableException(ctx.translate("all_questions_asked")) if question.asked: raise SendableException(ctx.translate("question_already_asked")) await ctx.send(embed=question.embed) question.asked = True question.save()
async def conversation_start(self, ctx): if ctx.author.id in self.cached_conversations: raise SendableException( ctx.translate("already_running_conversation")) conversant, _ = Conversant.get_or_create(user_id=ctx.author.id) if not conversant.enabled: conversant.enabled = True conversant.save() user_ids = [] user_ids_in_conversation = list(self.cached_conversations.keys()) for cs in Conversant.get_available(user_ids_in_conversation): if cs.user_id != ctx.author.id: user_ids.append(cs.user_id) if len(user_ids) == 0: raise SendableException(ctx.translate("no_conversants_available")) await ctx.success(ctx.translate("attempting_to_find_conversant")) random.shuffle(user_ids) user_to_speak_to = None for user_id in user_ids: user = self.bot.get_user(user_id) if user is None: continue if await check_if_available(user): user_to_speak_to = user break else: await ctx.send(ctx.translate("still_looking_please_be_patient") ) if user_to_speak_to is None: raise SendableException(ctx.translate("no_conversants_available")) conversation = Conversation() conversation.participant1 = Participant.create(conversant=conversant) conversation.participant2 = Participant.create( conversant=Conversant.get(user_id=user_to_speak_to.id)) conversation.save() embed = get_conversation_tutorial_embed(ctx) for participant in conversation.get_participants(): other = conversation.get_other(participant) embed.set_footer( text=f"Speaking to conversant with id '{other.key}'") await participant.send(embed=embed) self.cached_conversations[ participant.conversant.user_id] = conversation
async def pigeon_retrieve(self, ctx): """Retrieve and check on your pigeon.""" pigeon = ctx.pigeon if pigeon.status == Pigeon.Status.idle: raise SendableException(ctx.translate("pigeon_idle")) embed = self.get_base_embed(ctx.guild) activity = pigeon.current_activity if activity is None: raise SendableException(ctx.translate("nothing_to_retrieve")) if isinstance(activity, Exploration): if activity.end_date_passed: retrieval = ExplorationRetrieval(activity) embed = retrieval.embed retrieval.commit() return asyncio.gather(ctx.send(embed=embed)) else: embed.description = f"**{pigeon.name}** is still on {pigeon.gender.get_posessive_pronoun()} way to explore!" embed.set_footer( text="Check back at", icon_url= "https://www.animatedimages.org/data/media/678/animated-pigeon-image-0045.gif" ) embed.timestamp = activity.end_date return asyncio.gather(ctx.send(embed=embed)) elif isinstance(activity, Mail): if activity.end_date_passed: retrieval = MailRetrieval(activity) embed = retrieval.embed retrieval.commit() Reminder.create(user_id=activity.recipient.user_id, channel_id=None, dm=True, text=ctx.translate("pigeon_inbox_unread_mail"), due_date=datetime.datetime.utcnow()) return asyncio.gather(ctx.send(embed=embed)) else: embed.description = f"**{pigeon.name}** is still on {pigeon.gender.get_posessive_pronoun()} way to send a message!" embed.set_footer( text="Check back at", icon_url= "https://www.animatedimages.org/data/media/678/animated-pigeon-image-0045.gif" ) embed.timestamp = activity.end_date return asyncio.gather(ctx.send(embed=embed))
async def template_view(self, ctx, name): try: template = PollTemplate.get(name = name, guild_id = ctx.guild.id) except PollTemplate.DoesNotExist: raise SendableException(ctx.translate("poll_template_does_not_exist")) columns = template.shared_columns del columns["guild_id"] if columns["result_channel_id"] is not None: columns["result_channel"] = "#" + str(ctx.guild.get_channel(columns["result_channel_id"])) del columns["result_channel_id"] if columns["channel_id"] is not None: columns["channel"] = "#" + str(template.channel) del columns["channel_id"] if columns["role_id_needed_to_vote"] is not None: columns["role_needed_to_vote"] = str(ctx.guild.get_role(template.role_id_needed_to_vote)) del columns["role_id_needed_to_vote"] new_columns = {} for key, value in columns.items(): new_key = key.replace("_", " ").capitalize() if isinstance(value, bool): new_key += "?" new_columns[new_key] = value lines = prettify_dict(new_columns) embed = discord.Embed( color = ctx.guild_color, title = f"Config of poll-template '{name}'", description = f"```\n{lines}```" ) asyncio.gather(ctx.send(embed = embed))
def get_human_item(self, user, code=None): #TODO: optimize human_item = HumanItem.get_or_none(human=self.bot.get_human(user=user), item=Item.get(code=code)) if human_item is None or human_item.amount == 0: raise SendableException(self.bot.translate("no_" + code)) return human_item
def pigeon_raise_if_unavailable(ctx, pigeon, name="pigeon"): pigeon_raise_if_not_exist(ctx, pigeon, name) if pigeon.status != Pigeon.Status.idle: ctx.command.reset_cooldown(ctx) raise SendableException( ctx.translate(f"{name}_not_idle").format( status=pigeon.status.name))
async def temporary_channel_extend(self, ctx, channel: discord.TextChannel): try: temp_channel = TemporaryChannel.get(channel_id=channel.id, guild_id=ctx.guild.id) except TemporaryChannel.DoesNotExist: raise SendableException(ctx.translate("temp_channel_not_found")) human_item = self.get_human_item(ctx.author, code=temp_channel.item_code) if human_item.amount > 1: waiter = IntWaiter( ctx, prompt=ctx.translate("temporary_channel_count_prompt"), min=1, max=human_item.amount) items_to_use = await waiter.wait() else: items_to_use = 1 temp_channel.set_expiry_date( datetime.timedelta(days=temp_channel.days * items_to_use)) asyncio.gather(temp_channel.update_channel_topic()) human_item.amount -= items_to_use human_item.save() temp_channel.save() await ctx.send( f"Okay. This channel has been extended until `{temp_channel.expiry_date}`" )
async def zodiac(self, ctx, sign: EnumConverter(ZodiacSign) = None): if sign is None: human = ctx.get_human() if human.date_of_birth is None: raise SendableException(ctx.translate("date_of_birth_not_set")) sign = human.zodiac_sign query = Human.select(Human.user_id, Human.zodiac_sign) query = query.join(Earthling, on=(Human.id == Earthling.human)) query = query.where(Earthling.guild_id == ctx.guild.id) query = query.where(Human.date_of_birth != None) table = pretty.Table() table.add_row(pretty.Row(["member", "sign"], header=True)) i = 0 for human in query: if sign != human.zodiac_sign: continue if human.user is None: continue values = [str(human.user), str(sign.name)] table.add_row(pretty.Row(values)) i += 1 await table.to_paginator(ctx, 15).wait()
async def item_explorable(self, ctx, *, name): if name == "": raise commands.errors.MissingRequiredArgument("name") if not is_tester(ctx.author): raise SendableException(ctx.translate("not_a_tester")) try: item = Item.get(name=name) except Item.DoesNotExist: raise SendableException("Item not found.") await item.editor_for(ctx, "rarity", skippable=True) await item.editor_for(ctx, "explorable", skippable=True) item.save() await ctx.send("OK")
async def item_create(self, ctx, *, name): if name == "": raise commands.errors.MissingRequiredArgument("name") if not is_tester(ctx.author): raise SendableException(ctx.translate("not_a_tester")) item, new = Item.get_or_create(name=name) if not new: await item.editor_for(ctx, "name", skippable=not new) await item.editor_for(ctx, "description", skippable=not new) await item.editor_for(ctx, "rarity", skippable=True) await item.editor_for(ctx, "explorable", skippable=True) waiter = AttachmentWaiter(ctx, prompt=ctx.translate("item_image_prompt"), skippable=not new) try: item.image_url = await waiter.wait(store=True) except Skipped: pass item.save() await ctx.send("OK")
async def farm_view(self, ctx): farm_crop = self.get_farm_crop_query(ctx.farm).first() if farm_crop is None: raise SendableException(ctx.translate("nothing_planted")) stage = farm_crop.current_stage crop_path = farm_crop.crop.get_stage_sprite_path(stage) crop = farm_crop.crop embed = discord.Embed(color=discord.Color.green()) embed.title = f"{crop.name}" path = f"{config.path}/tmp/{crop.name.lower()}_stage_{stage}.png" if not os.path.exists(path): background = Image.open(f"{crop.root_path}/background.png") crop = Image.open(crop_path) image = Image.new('RGBA', (background.size[0], background.size[1])) image.paste(background, (0, 0)) image.paste(crop, (0, 0), crop.convert('RGBA')) image.save(path, "PNG") file = await self.bot.store_file(path, filename="file.png") embed.set_image(url=file) embed.set_footer(text=f"Stage {stage} / {Crop.max_stages}") await ctx.send(embed=embed)
async def pigeon_rob(self, ctx, member: discord.Member): pigeon = ctx.pigeon if not pigeon.pvp_action_available: raise SendableException( ctx.translate("pvp_action_not_available_yet")) if member.id == ctx.author.id: raise SendableException(ctx.translate("cannot_rob_self")) human1 = ctx.get_human() human2 = ctx.get_human(member.id) class RobType(enum.Enum): item = 1 gold = 2 class RobResult(enum.Enum): success = 1 failure = 2 # type = RobType.gold if random.randint(0, 1) == 1 else RobType.item type = RobType.gold pigeon.last_used_pvp = datetime.datetime.utcnow() if type == RobType.gold: amount = random.randint(0, 100) amount = min(human2.gold, amount) result = RobResult.failure if random.randint( 0, 3) == 1 else RobResult.success if result == RobResult.success: human2.gold -= amount human1.gold += amount human2.save() human1.save() await ctx.send( f"You successfully steal {Pigeon.emojis['gold']}{amount} from {human2.user}" ) elif result == RobResult.failure: jail_time = 3 pigeon.jailed_until = datetime.datetime.utcnow( ) + datetime.timedelta(hours=jail_time) await ctx.send( f"You fail to steal from {human2.user} and are put in jail for {jail_time} hours" ) pigeon.save()
async def role(self, ctx): has_5k = ctx.guild.get_role(self._role_ids["5k+"]) in ctx.author.roles is_nitro_booster = ctx.author.premium_since is not None allowed = has_5k or is_nitro_booster if not allowed: raise SendableException( "You are not allowed to run this command yet, needed: 5k+ XP or Nitro Booster" )
async def translation_remove(self, ctx, key, locale="en_US"): missing_translations = self.bot.get_missing_translations(locale) try: translation = Translation.get(message_key=key) except Translation.DoesNotExist: raise SendableException(ctx.translate("key_not_found")) translation.delete_instance() missing_translations.add(key) asyncio.gather(ctx.success())
async def emoji_add(self, ctx,*, name : lambda x : x.lower().replace(" ", "_")): if len(ctx.message.attachments) == 0: raise SendableException(ctx.translate("no_attachments")) image = ctx.message.attachments[0] available_guilds = [x for x in self.guilds if len(x.emojis) < x.emoji_limit] guild = available_guilds[0] emoji = await guild.create_custom_emoji(name = name, image = await image.read()) SavedEmoji.create(name = emoji.name, guild_id = guild.id, emoji_id = emoji.id) asyncio.gather(ctx.send(ctx.translate("emoji_created")))
async def pigeon_pvp(self, ctx): pigeon = ctx.pigeon if pigeon.pvp and not pigeon.can_disable_pvp: raise SendableException(ctx.translate("pvped_too_recently")) pigeon.pvp = not pigeon.pvp pigeon.save() asyncio.gather( ctx.send(f"Okay. PvP is now " + ("on" if pigeon.pvp else "off")))
async def temporary_channel_accept(self, ctx, temp_channel: TemporaryChannel): if temp_channel.status != TemporaryChannel.Status.pending: raise SendableException(ctx.translate("temp_channel_not_pending")) if not temp_channel.active: raise SendableException(ctx.translate("temp_channel_not_active")) temp_channel.set_expiry_date( datetime.timedelta(days=temp_channel.days * temp_channel.pending_items)) temp_channel.pending_items = 0 await temp_channel.create_channel() temp_channel.status = TemporaryChannel.Status.accepted temp_channel.save() try: await temp_channel.user.send( f"Your request for a temporary channel was accepted.") except: pass asyncio.gather(ctx.success())
async def farm_plant(self, ctx, crop_name: StringConverter(Crop.pluck("name"))): farm_crop = self.get_farm_crop_query(ctx.farm).first() if farm_crop is not None: raise SendableException(ctx.translate("already_planted")) crop = Crop.get(name=crop_name) item = crop.seed_item human_item = HumanItem.get_or_none(item=item, human=ctx.get_human()) if human_item is None or human_item.amount <= 0: raise SendableException( ctx.translate("item_not_enough").format(item=item, amount=1)) ctx.farm.plant(crop=crop) human_item.amount -= 1 human_item.save() await ctx.success(ctx.translate("crop_planted").format(crop=crop))
async def pigeon_accept(self, ctx): """Accept an invitation.""" pigeon2 = ctx.pigeon challenge = pigeon2.current_activity if not isinstance(challenge, (Date, Fight)) or challenge.pigeon1 == pigeon2: raise SendableException(ctx.translate("nothing_to_accept")) error_messages = challenge.validate(ctx) if error_messages is not None and len(error_messages) > 0: challenge.delete_instance() raise SendableException(error_messages[0]) if isinstance(challenge, Fight): for pigeon in challenge.pigeons: human = ctx.get_human(user=pigeon.human.user_id) human.gold -= challenge.bet human.save() challenge.accepted = True challenge.start_date = datetime.datetime.utcnow() challenge.end_date = challenge.start_date + datetime.timedelta( minutes=5) challenge.save() embed = self.get_base_embed(ctx.guild) lines = [] lines.append( f"{ctx.author.mention} has accepted the {challenge.type.lower()}!") lines.append( f"The pigeons have now started {challenge.pigeon1.status.name.lower()}." ) embed.description = "\n".join(lines) embed.set_footer(text=f"{challenge.type} will end at", icon_url=challenge.icon_url) embed.timestamp = challenge.end_date channel = self.get_pigeon_channel(ctx.guild) await channel.send(embed=embed)
async def item_give(self, ctx, member: discord.Member, *, name): if name == "": raise commands.errors.MissingRequiredArgument("name") try: item = Item.get(name=name) except Item.DoesNotExist: raise SendableException("Item not found.") member = member or ctx.author human = ctx.get_human(user=member) human.add_item(item, 1) await ctx.send("added")
async def group_create(self, ctx): group = MentionGroup(guild_id=ctx.guild.id) await group.editor_for(ctx, "name") group.name = group.name.lower() try: group.save() except: raise SendableException(ctx.translate("group_already_exists")) else: group.join(ctx.author, is_owner=True) await ctx.send(ctx.translate("group_created").format(group=group))
async def parrot(self, ctx, *, text): asyncio.gather(ctx.message.delete(), return_exceptions=False) with ctx.typing(): cost = 10 human = ctx.get_human() if human.gold < cost: raise SendableException( ctx.translate("not_enough_gold").format(cost=cost)) asyncio.gather(ctx.send(text)) human.gold -= cost human.save()
async def temporary_channel_deny(self, ctx, temp_channel: TemporaryChannel, *, reason): if temp_channel.status != TemporaryChannel.Status.pending: raise SendableException(ctx.translate("temp_channel_not_pending")) if not temp_channel.active: raise SendableException(ctx.translate("temp_channel_not_active")) temp_channel.status = TemporaryChannel.Status.denied temp_channel.active = False temp_channel.deny_reason = reason human_item = self.get_human_item(temp_channel.user, code=temp_channel.item_code) human_item.amount += temp_channel.pending_items human_item.save() temp_channel.save() try: await temp_channel.user.send( f"Your request for a temporary channel was denied. Reason: `{temp_channel.deny_reason}`" ) except: pass asyncio.gather(ctx.success())
async def prank_nickname(self, ctx, member: discord.Member): await self.pre_prank(ctx, member) if not self.bot.can_change_nick(ctx.member): raise SendableException(ctx.translate("cannot_change_nickname")) if not self.bot.can_change_nick(ctx.author): raise SendableException( ctx.translate("cannot_toggle_because_cannot_change_nickname")) await self.prank_check(ctx, ctx.member) waiter = StrWaiter(ctx, max_words=None, prompt=ctx.translate("nickname_prank_prompt"), max_length=32) new_nickname = await waiter.wait() await self.create_prank( ctx, embed_description= f"Nickname of {ctx.member.mention} has been changed to **{new_nickname}**.", new_nickname=new_nickname, old_nickname=ctx.member.nick)
def pigeon_raise_if_stats_too_low(ctx, pigeon, name="pigeon"): if pigeon.cleanliness <= 10: message = ctx.translate(f"{name}_too_stinky") elif pigeon.happiness <= 10: message = ctx.translate(f"{name}_too_sad") elif pigeon.food <= 10: message = ctx.translate(f"{name}_too_hungry") elif pigeon.health <= 10: message = ctx.translate(f"{name}_too_wounded") else: return ctx.command.reset_cooldown(ctx) raise SendableException(message.format(pigeon=pigeon))
async def prank_emoji(self, ctx, member: discord.Member): await self.pre_prank(ctx, member) await self.prank_check(ctx, ctx.member) waiter = StrWaiter(ctx, max_words=1, prompt=ctx.translate("emoji_prank_prompt")) emoji_ = await waiter.wait() try: await ctx.message.add_reaction(emoji_) except: raise SendableException("Could not use emoji") await self.create_prank(ctx, embed_description=None, emoji=emoji_)