async def clearProxy(self, ctx: commands.Context): proxiedUser = await getAuthorFromContext(ctx) try: OVERARCH.clearProxy(ctx.author.id) except NotFoundException: fail(R.noProxy) await ctx.send(R.clearProxy.success(proxiedUser.display_name))
async def whichArchive(self, ctx: commands.Context): author = await getAuthorFromContext(ctx) guildID = OVERARCH.getGuildIDForUser(author.id) if not guildID: fail(T.UTIL.errorNoArchivePreference) guild: discord.Guild = await self.bot.fetch_guild(guildID) await ctx.send(R.whichArchive.success(guild.name))
async def changeGenre(story: Story): for genreName in targetGenre: try: story.removeGenre(genreName) print(f"Removed {genreName} genre from {story.cite()}.") except NotFoundException: fail(A.removeGenre.errorNotFound(story.cite(), genreName))
async def useHere(self, ctx: commands.Context): if not ctx.guild: fail(R.useHere.errorDM) author = await getAuthorFromContext(ctx) OVERARCH.setArchivePref(author.id, ctx.guild.id) OVERARCH.write() await ctx.send(R.useHere.success(ctx.guild.name))
async def collectStories(ctx: commands.Context, matcher: Callable[[Story], bool], headerText: str, failText: str): archive = getArchiveFromContext(ctx) archiveChannel: discord.TextChannel = await fetchChannel( ctx.guild, archive.channelID) pages = [] for post in archive.posts.values(): foundStories: list[Story] = [] for story in post.stories: if matcher(story): foundStories.append(story) if foundStories: author = await convertMember(post.authorID, ctx) try: message: discord.Message = await archiveChannel.fetch_message( post.messageID) except discord.HTTPException: fail(R.deletedPost(author.display_name)) pages.append({ "embed": getLaprOSEmbed(**R.collectionEmbed( headerText, author.display_name, [story.title for story in foundStories], message.jump_url)) }) if not pages: await ctx.send(failText) else: await paginate(ctx, pages)
async def multi(self, ctx: commands.Context, *, allTextCommands: str): # thank you, love allTextCommands = [ command if not command.startswith(BOT_PREFIX) else command[len(BOT_PREFIX):] for command in allTextCommands.split("\n") ] for textCommand in allTextCommands: targetCmdName: str = None args: list[str] = None try: targetCmdName, *args = getStringArgsFromText(textCommand) except SyntaxError: fail(A.multi.errorQuotes(textCommand)) commandFn: commands.Command found = False for commandFn in self.get_commands(): if targetCmdName.lower( ) == commandFn.name or targetCmdName.lower( ) in commandFn.aliases: try: print(f"running command {targetCmdName}") await commandFn(ctx, *args) found = True except TypeError: await fail(A.multi.errorArgs(targetCmdName)) break if not found: fail(A.multi.errorInvalid(targetCmdName))
async def changeReason(story: Story): try: story.setRatingReason(newReason, ignoreMax=isModerator(ctx.author)) print(f"Set rating reason of {story.cite()} to {newReason}.") except MaxLenException: fail(A.setRatingReason.errorLen(newReason))
async def getMyPost(self, ctx: commands.Context): if not isinstance(ctx.channel, discord.DMChannel): fail(A.getMyPost.errorNoDM) post = await self.getPost(ctx) message: discord.Message = await ctx.send(A.archivePostMessageWait) self.dmPosts[post.messageID] = message await self.updateFocusedStory(message, post)
async def changeGenre(story: Story): for genreName in newGenre: try: story.addGenre(genreName.capitalize()) print(f"Added {genreName} genre to {story.cite()}.") except InvalidNameException: fail(A.addGenre.errorInvalid(genreName)) except DuplicateException: fail(A.addGenre.errorDup(story.cite(), genreName))
async def getPost(self, ctx: commands.Context): """ Gets the post from the context. If `error`, if a post isn't found, the context is notified and an error is thrown. """ archive = getArchiveFromContext(ctx) author = await getAuthorFromContext(ctx) post = archive.getPost(author.id) if not post: fail(A.errorNoPost) return post
async def removeStory(self, ctx: commands.Context, *targetTitle: str): targetTitle = " ".join(targetTitle) post = await self.getPost(ctx) try: post.removeStory(targetTitle) except NotFoundException: fail(A.removeStory.errorNotFound(targetTitle)) await self.updatePost(ctx)
async def changeCharacter(story: Story): try: story.removeCharacter(targetCharSpecies, targetCharName) print( f"Removed character with species {targetCharSpecies} and name {targetCharName} from {story.cite()}." ) except NotFoundException: fail( A.removeCharacter.errorNorFound(story.cite(), targetCharSpecies, targetCharName))
async def proxy(self, ctx: commands.Context, *, member: Union[discord.Member, int]): if not isModerator(ctx.author): fail(T.UTIL.errorNotMod) member = await convertMember(member, ctx) if member.id == ctx.author.id: fail(R.proxy.selfProxy) OVERARCH.setProxy(ctx.author.id, member.id) messageable = ctx.author if canHandleArchive(ctx) else ctx await messageable.send(R.proxy.success(member.display_name))
async def parseCharacter(ctx: commands.Context, text: str) -> tuple[str, str]: """ Gets the character name (optional, may be empty string) and the character species from a string. """ parts = [part.strip() for part in text.split(",", 1)] if not parts[0]: fail(A.errorParseCharacterNoSpecies) if len(parts) == 2: return parts[0], " ".join( [word.capitalize() for word in parts[1].split(" ")]) else: return "", " ".join( [word.capitalize() for word in parts[0].split(" ")])
async def updatePost(self, ctx: commands.Context, changer: Callable[[Story], None] = None): """ Calls the `changer` function on the currently focused story in the post linked to the context. """ post = await self.getPost(ctx) story = post.getFocusedStory() guild: discord.Guild = await self.bot.fetch_guild(post.archive.guildID) archiveChannel: discord.TextChannel = discord.utils.get( await guild.fetch_channels(), id=post.archive.channelID) if not archiveChannel: fail(A.errorNoArchiveChannel) if not story: # this happens when deleting a story # focus the first story for the author if it exists print("Updating post for deleted story") if post.stories: print("Focusing first story and updating") story = post.stories[0] # if the deleted story was the only one that the author had, delete the message and reset the ID on the post elif post.messageID: print( "Deleted story was the last in the post, deleting message") message = await archiveChannel.fetch_message(post.messageID) await message.delete() post.delete() return # this shouldn't happen else: fail( "Deleted a story for a post with a nonexistent message ID. How does that work...?" ) if changer: await changer(story) try: message = await archiveChannel.fetch_message(post.messageID) except (discord.NotFound, discord.HTTPException): message: discord.Message = await archiveChannel.send( A.archivePostMessageWait) await message.add_reaction(T.UTIL.emojiFirst) await message.add_reaction(T.UTIL.emojiPrior) await message.add_reaction(T.UTIL.emojiNext) await message.add_reaction(T.UTIL.emojiLast) post.messageID = message.id await self.updateFocusedStory(message, post) OVERARCH.write()
async def focus(self, ctx: commands.Context, *, targetStory: Union[int, str]): post = await self.getPost(ctx) if isinstance(targetStory, int): targetStory -= 1 try: post.focusByIndex(targetStory) except MaxLenException: fail(A.focus.errorIndex(targetStory)) else: try: post.focusByTitle(targetStory.lower()) except NotFoundException: fail(A.focus.errorNotFound(targetStory)) await self.updatePost(ctx)
async def byAuthor(self, ctx: commands.Context, *, author: Union[discord.Member, int, str]): author = await convertMember(author, ctx) archive = getArchiveFromContext(ctx) archiveChannel: discord.TextChannel = await fetchChannel( ctx.guild, archive.channelID) post = archive.getPost(author.id) try: message: discord.Message = await archiveChannel.fetch_message( post.messageID) except AttributeError: fail(R.noPost(author.display_name)) except discord.HTTPException: fail(R.deletedPost(author.display_name)) await ctx.send(embed=getLaprOSEmbed(**R.byAuthor.embed( author.display_name, post.getStoryTitles(), message.jump_url)))
async def changeLink(story: Story): try: story.addLink(linkSiteAbbr, linkURL, ignoreMax=isModerator(ctx.author)) except InvalidNameException: fail(A.addLink.errorInvalid(story.cite(), linkSiteAbbr)) except DuplicateException: fail(A.addLink.errorDup(story.cite(), linkSiteAbbr)) except MaxLenException: fail(A.addLink.errorLen(len(linkURL)))
async def changeCharacter(story: Story): try: story.addCharacter(newCharSpecies, newCharName, ignoreMax=isModerator(ctx.author)) print( f"Added character with species {newCharSpecies} and name {newCharName} to {story.cite()}." ) except DuplicateException: fail( A.addCharacter.errorDup(story.cite(), newCharSpecies, newCharName)) except MaxSpeciesLenException: fail(A.addCharacter.errorLenSpecies(len(newCharSpecies))) except MaxNameLenException: fail(A.addCharacter.errorLenName(len(newCharName)))
async def addStory(self, ctx: commands.Context, *storyTitle: str): # not kwarg so it works with multi storyTitle = " ".join(storyTitle) author = await getAuthorFromContext(ctx) now = time.time() isNewPost = False try: post = await self.getPost(ctx) except laprOSException: print("Creating new post.") archive = getArchiveFromContext(ctx) post = archive.createPost(author.id) last = self.addStoryCooldown diff = now - last print(diff) diff = (60 * 10) - diff if diff > 0: mins = round(diff / 60, 2) fail(A.addStory.errorCooldown(mins)) isNewPost = True try: newStory = Story() newStory.setTitle(storyTitle, ignoreMax=isModerator(ctx.author)) post.addStory(newStory) post.focusByTitle(newStory.title) except MaxLenException: fail(A.addStory.errorLen(len(storyTitle))) return except DuplicateException: fail(A.addStory.errorDup(storyTitle)) return await self.updatePost(ctx) if isinstance(ctx.channel, discord.DMChannel): if not post.messageID in self.dmPosts: await self.getMyPost(ctx) if isNewPost: self.addStoryCooldown = now
async def changeTitle(story: Story): try: story.setTitle(newTitle, ignoreMax=isModerator(ctx.author)) except MaxLenException: fail(A.setTitle.errorLen(len(newTitle)))
async def changeSummary(story: Story): try: story.setSummary(newSummary, ignoreMax=isModerator(ctx.author)) print(f"Set summary of {story.cite()} to:\n{newSummary}") except MaxLenException: fail(A.setSummary.errorLen(len(newSummary)))
async def changeRating(story: Story): try: story.setRating(newRating) print(f"Set rating of {story.cite()} to {newRating}.") except InvalidNameException: fail(A.setRating.errorInvalid(story.cite(), newRating))
async def changeLink(story: Story): try: story.removeLink(targetSiteAbbr) except NotFoundException: fail(A.removeLink.errorNotFound(story.cite(), targetSiteAbbr))
async def fetch(self, ctx: commands.Context, *args: str): if not args: fail(R.fetch.noArgs) if not ctx.invoked_subcommand: fail(R.fetch.error(" ".join(args)))