async def cmd_bool(self, src, **_): m = Menu(self.client, src.channel, "Choice", "Test Function", user=src.author) m.add_section(repr(await m.get_bool())) await m.post()
async def cmd_menu2(self, src, **_): m = Menu(self.client, src.channel, "Choice", "Test Function", user=src.author) m.add_section("\n".join(await m.get_multi( ["asdf", "qwert", "asdfqwert", "qwertyuiop"])) or "(None)") await m.post()
async def cmd_poll(self, args, src, _question: str = "", _channel: int = None, _time: int = 0, **_): if len(args) < 2: return "Must provide at least two options." duration = _time if _time > 0 else 3600 title = "Poll" if _question: title += ": " + _question if _channel: targ = self.client.get_channel(_channel) else: targ = src.channel if not targ: return "Invalid Channel" poll = Menu(self.client, targ, title, "Test Function") await poll.get_poll(args, duration)
async def cmd_purge(self, args, src: Src, **_): """Purge up to 200 messages in the current channel. Syntax: `{p}purge <number of messages to delete>` """ if len(args) < 1: raise CommandArgsError("Please provide a number between 1 and 200") try: delete_num = int(args[0].strip()) except ValueError: raise CommandInputError("Please make sure your input is a number") if not 0 < delete_num <= 200: raise CommandInputError( "Can only delete between 0 and 200 Messages.") confirm_menu: Menu = Menu( self.client, src.channel, "Confirm Purge", f"Really delete the last {delete_num} Messages in this Channel?\n" f"(This Menu and your Invocation will also be deleted.)", src.author, ) confirmed = await confirm_menu.get_bool() if confirmed is True: try: await src.channel.purge(limit=delete_num + 2, check=None) except discord.errors.Forbidden: raise CommandOperationError( "I don't have permissions to purge messages.") else: logEmbed = discord.Embed( title="Purge Event", description=f"{delete_num} messages were purged from " f"`#{src.channel.name}` in {src.guild.name} by " f"`{src.author.name}#{src.author.discriminator}`.", color=0x0ACDFF, ) await self.client.log_moderation(embed=logEmbed) elif confirmed is False: await confirm_menu.add_section("Purge Cancelled.") await confirm_menu.post() else: await confirm_menu.add_section("Confirmation Timed Out.") await confirm_menu.post()
async def cmd_vote(self, src, _question: str = None, _channel: int = None, _time: int = 0, **_): duration = _time if _time > 0 else 3600 title = "Vote: " + _question if _question else "Vote" if _channel: targ = self.client.get_channel(_channel) else: targ = src.channel if not targ: return "Invalid Channel" poll = Menu(self.client, targ, title, "Test Function") await poll.get_vote(duration)
async def cmd_menu(self, src, **_): m = Menu(self.client, src.channel, "Choice", "Test Function", user=src.author) m.add_section( await m.get_one(["asdf", "qwert", "asdfqwert", "qwertyuiop"]) or "(None)") await m.post() m.add_section( await m.get_one(["zxcv", "qazwsx", "yuiop", "poiuytrewq"]) or "(None)", # overwrite=0, ) await m.post() m.add_section(await m.get_one(["aaaaaaaaa", "wysiwyg", "zzz"]) or "(None)" # , overwrite=0, ) await m.post()
async def cmd_event(self, src, _image: str = None, _message: str = None, _nomenu: bool = False, **_): """Post a message announcing the start of an event. Define a message which will be sent to one or more predefined channels. The message may include mass pings by way of including tags `{{e}}` and `{{h}}` for substitution. Destination channels may be selected conversationally or by way of a reaction-based menu. Options: `--message=<msg>` :: Define the message to send ahead of time. This will skip the step where Petal asks you what you want the message to say. `--nomenu` :: Forsake the Reaction UI and determine destination channels conversationally. """ channels_list = [] channels_dict = {} msg = "" for chan in self.config.get("xPostList"): channel = self.client.get_channel(chan) if channel is not None: msg += (str(len(channels_list)) + ". (" + channel.name + " [{}]".format(channel.guild.name) + ")\n") channels_list.append(channel) channels_dict[channel.guild.name + "/#" + channel.name] = channel else: self.log.warn( chan + " is not a valid channel. I'd remove it if I were you.") # Get channels to send to. if _nomenu: # Do it only conversationally. menu = None while True: await self.client.send_message( src.author, src.channel, "Hi there, " + src.author.name + "! Please select the number of " + "each guild you want to post " + "to. (dont separate the numbers)", ) await self.client.send_message(src.author, src.channel, msg) chans = await Messages.waitfor( self.client, all_checks(Messages.by_user(src.author), Messages.in_channel(src.channel)), timeout=20, ) if chans is None: return ( "Sorry, the request timed out. Please make sure you" " type a valid sequence of numbers.") if self.validate_channel(channels_list, chans.content): break else: await self.client.send_message( src.author, src.channel, "Invalid channel choices. You may try again immediately.", ) post_to = [] for i in chans.content: print(channels_list[int(i)]) post_to.append(channels_list[int(i)]) else: # Use the Reaction-based GUI. menu = Menu( self.client, src.channel, "Event Announcement Post (by {})".format( src.author.display_name), "Use the Reaction Buttons to fill out the Announcement.", user=src.author, ) if _image: menu.em.set_thumbnail(url=_image) selection = await menu.get_multi(list(channels_dict), title="Target Channels") post_to = [ channels_dict[c] for c in selection if c in channels_dict ] if not post_to: # menu.add_section( # "No valid target channels selected; Post canceled.", "Verdict" # ) # await menu.close("No valid target channels selected; Post canceled.") menu.add_section("No Channels selected; Cancelled.", "Target Channels", overwrite=-1) await menu.post() return menu.add_section("\n".join([c.mention for c in post_to]), "Target Channels", overwrite=-1) await menu.post() try: msgstr = (_message or (await Messages.waitfor( self.client, all_checks( Messages.by_user(src.author), Messages.in_channel(src.channel), ), timeout=120, channel=src.channel, prompt="What do you want to send?" " (remember: {e} = `@ev` and {h} = `@here`)", )).content).format(e="@everyone", h="@here") except AttributeError: # Likely tried to get `None.content`. raise CommandOperationError("Text Input timed out.") if _nomenu: embed = discord.Embed(title="Message to post", description=msgstr, colour=0x0ACDFF) embed.add_field(name="Channels", value="\n".join([c.mention for c in post_to])) await self.client.embed(src.channel, embed) msg2 = await Messages.waitfor( self.client, all_checks(Messages.by_user(src.author), Messages.in_channel(src.channel)), timeout=20, channel=src.channel, prompt="If this is correct, type `confirm`.", ) if msg2 is None: return "Event post timed out." elif msg2.content.lower() != "confirm": return "Event post cancelled." else: # confirmer = Menu(self.client, src.channel, "Confirm Post", user=src.author) menu.add_section(msgstr, "Message Preview") proceed = await menu.get_bool( prompt="Send this Event Announcement?", title="Confirmation") if proceed is None: # menu.em.description = "[ Closed ]" menu.add_section("Posting timed out.", "Confirmation", overwrite=-1) return elif proceed is not True: # menu.em.description = "[ Closed ]" menu.add_section("Posting cancelled.", "Confirmation", overwrite=-1) return posted: List[discord.Message] = [] # TODO # em = discord.Embed() # if _image: # em.set_image(url=_image) for i in post_to: posted.append(await i.send(msgstr)) await asyncio.sleep(1) if menu: menu.add_section("Messages have been posted.", "Confirmation", overwrite=-1) await menu.post() else: # menu.em.description = "[ Closed ]" await self.client.send_message(src.author, src.channel, "Messages have been posted.") try: subkey, subname = self.get_event_subscription(msgstr) except AttributeError: pass else: if subkey is None: return ( "I was unable to auto-detect any game titles in your post." " No subscribers will be notified for this event.") else: n = await Messages.waitfor( self.client, all_checks(Messages.by_user(src.author), Messages.in_channel(src.channel)), timeout=20, channel=src.channel, prompt= f"I auto-detected a possible game in your announcement:" f" **{subname}**. Would you like to notify subscribers? [y/N]", ) if not n: return "Timed out." elif n.content.lower() not in ("y", "yes"): return "Subscribers will not be notified." else: response = await self.notify_subscribers( src.channel, posted[0], subkey) todelete = "[{}]".format(subkey) for post in posted: content = post.content # print(content) # print(todelete) if todelete in content: # print("replacing") content = content.replace(todelete, "") # print("replaced: " + content) await post.edit(content=content) return response
async def cmd_update(self, src, _title: str = "", _content: str = "", _platform: str = "", _reddit: bool = False, _twitter: bool = False, _fb: bool = False, _tumblr: bool = False, **_): """ Post updates to various Social Media accounts. Syntax: `{p}update [OPTIONS]` Options: `--title="<title>"` :: Set the post title. Only matters for Reddit and Tumblr. Asked for if not provided. `--content="<content>"` :: Fill in the post content. This is the long bit. Asked for if not provided. `--platform=<0,1,2,3>` :: Old style numeric platform selection. Exactly the same as what is asked for if not provided. `--reddit`, `--twitter`, `--fb`, `--tumblr` :: Determine which platform(s) to post to. Alternative to `--platform`. """ modes = [] names = [] using = [] table = { "reddit": self.router.reddit, "twitter": self.router.twit, "facebook": self.router.fb, "tumblr": self.router.tumblr, } flagged = "" if _reddit: flagged += "0" if _twitter: flagged += "1" if _fb: flagged += "2" if _tumblr: flagged += "3" if table["reddit"]: names.append(str(len(modes)) + " reddit") modes.append(self.router.reddit) using.append("reddit") if table["twitter"]: names.append(str(len(modes)) + " twitter") modes.append(self.router.twit) using.append("twitter") if table["facebook"]: names.append(str(len(modes)) + " facebook") modes.append(self.router.fb) using.append("facebook") if table["tumblr"]: names.append(str(len(modes)) + " tumblr") modes.append(self.router.tumblr) using.append("tumblr") if not modes: return "No modules enabled for social media posting." if True not in (_reddit, _twitter, _fb, _tumblr) and _platform == "": # No destinations have been sent as a flag. Ask the user. sendto = "" ui = Menu( self.client, src.channel, "Platform", "Select platform(s) to publish on.", user=src.author, ) await ui.post() platforms = await ui.get_multi(using) platforms.sort() ui.em.description += "\nSelection: " + sequence_words( [x.capitalize() for x in platforms]) await ui.post() for i, name in enumerate(using): if name in platforms: sendto += str(i) if not sendto: await ui.close() return "Cancelled" else: sendto = flagged + _platform # Check whether sendto contains anything illegal. if sendto.strip("0123") != "": return "Invalid platform set, please try again." if not _title: # A title has not been sent as a flag. Ask the user. await self.client.send_message( src.author, src.channel, "Please type a title for your post (timeout after 1 minute).", ) _title = await self.client.wait_for_message(channel=src.channel, author=src.author, timeout=60) if _title is None: return "The process timed out, you need a valid title." _title = _title.content if not _content: # Post content has not been sent as a flag. Ask the user. await self.client.send_message( src.author, src.channel, "Please type the content of the post " + "below. Limit to 280 characters for " + "Twitter posts. This process will " + "time out after 2 minutes.", ) _content = await self.client.wait_for_message(channel=src.channel, author=src.author, timeout=120) if _content is None: return "The process timed out, you need content to post." _content = _content.content # Make sure this would not be an overly long tweet. if "1" in sendto: if len(_content) > 280: return "This post is {} characters too long for Twitter.".format( len(_content) - 280) # Get final confirmation before sending. await self.client.send_message( src.author, src.channel, "Your post is ready. Please type `send` to confirm.", ) confirm = await self.client.wait_for_message(channel=src.channel, author=src.author, content="send", timeout=10) if confirm.content.lower() != "send": return "Timed out, message not sent." # return str([flagged, sendto, platform, title, content]) # Post to Reddit if "0" in sendto: sub1 = self.router.reddit.subreddit( self.config.get("reddit")["targetSR"]) try: response = sub1.submit(_title.content, selftext=_content.clean_content, send_replies=False) self.log.f("smupdate", "Reddit Response: " + str(response)) except APIException: await self.client.send_message( src.author, src.channel, "The post did not send, " + "this key has been " + "ratelimited. Please wait for" + " about 8-10 minutes before " + "posting again", ) else: await self.client.send_message( src.author, src.channel, "Submitted post to " + self.config.get("reddit")["targetSR"], ) await asyncio.sleep(2) # Post to Twitter if "1" in sendto: self.router.twit.PostUpdate(_content.clean_content) await self.client.send_message(src.author, src.channel, "Submitted tweet") await asyncio.sleep(2) # Post to Facebook if "2" in sendto: # Setting up facebook takes a bit of digging around to see how # their API works. Basically you have to be admin'd on a page # and have its ID as well as generate an OAUTH2 long-term key. # Facebook python API was what I googled resp = self.router.fb.get_object("me/accounts") page_access_token = None for page in resp["data"]: if page["id"] == self.config.get("facebook")["pageID"]: page_access_token = page["access_token"] postpage = facebook.GraphAPI(page_access_token) # if postpage is None: # await self.client.send_message( # src.author, # src.channel, # "Invalid page id for " + "facebook, will not post", # ) # else: # # FIXME: 'postpage.put_wall_post' and 'page' are invalid # # Did you mean to put them in the loop above? # status = postpage.put_wall_post(body.clean_content) # await self.client.send_message( # src.author, # src.channel, # "Posted to facebook under page: " + page["name"], # ) # self.log.f("smupdate", "Facebook Response: " + str(status)) # await asyncio.sleep(2) # Post to Tumblr if "3" in sendto: self.router.tumblr.create_text( self.config.get("tumblr")["targetBlog"], state="published", slug="post from petalbot", title=_title.content, body=_content.clean_content, ) await self.client.send_message( src.author, src.channel, "Posted to tumblr: " + self.config.get("tumblr")["targetBlog"], ) return "Done posting"
async def cmd_send(self, args, src: discord.Message, _everyone: bool = False, _here: bool = False, _identity: str = None, _i: str = None, _image: str = None, _I: str = None, **_): """Broadcast an official-looking message into another channel. By using the Identity Option, you can specify who the message is from. Valid Identities:```\n{}``` Syntax: `{{p}}send [OPTIONS] <channel-id> ["<message>"]` Parameters ---------- _ : dict Dict of additional Keyword Args. self self args : List[str] List of Positional Arguments supplied after Command. src : discord.Message The Discord Message that invoked this Command. _everyone : bool Include an `@everyone` ping in the message. Overrides `--here`. _here : bool Include a `@here` ping in the message. _identity, _i : str Select the group/team on whose behalf this message is being sent. _image, _I : str Provide the URL of an image to be included in the embed. """ if 2 < len(args) < 1: raise CommandArgsError( "Must provide a Channel ID and, optionally, a quoted message.") elif not args[0].isdigit(): raise CommandArgsError("Channel ID must be integer.") destination: discord.TextChannel = self.client.get_channel( int(args.pop(0))) if not destination: raise CommandArgsError("Invalid Channel.") if not args: await self.client.send_message( src.author, src.channel, "Please give a message to send (just reply below):", ) try: msg = await self.client.wait_for( "message", check=checks.all_checks( checks.Messages.by_user(src.author), checks.Messages.in_channel(src.channel), ), timeout=30, ) except asyncio.TimeoutError: raise CommandInputError("Timed out while waiting for message.") else: text = msg.content else: text = args[0] identity = (_identity or _i or default).lower() ident = idents.get(identity, idents[list(sorted(idents.keys()))[0]]) ident["description"] = text img = _image or _I try: preview = discord.Embed(**ident) if img: preview.set_image(url=img) menu = Menu(self.client, src.channel, "", "", user=src.author) menu.em = preview confirm = await menu.get_bool( prompt="Send this message to {} on behalf of {}?\n" "(This section will not be sent.)".format( destination.mention, identity) + ("\n***NOTE: THIS MESSAGE WILL SEND A MASS PING!***" if _everyone or _here else ""), title="Confirm", ) if confirm is True: em = discord.Embed(**ident) if img: em.set_image(url=img) if _everyone: await destination.send("(@everyone)", embed=em) elif _here: await destination.send("(@here)", embed=em) else: await destination.send(embed=em) # await self.client.embed(destination, em) elif confirm is False: raise CommandExit("Message cancelled.") else: raise CommandExit("Confirmation timed out.") except discord.errors.Forbidden: raise CommandOperationError( "Failed to send message: Access Denied") else: return ("{} (ID: `{}`) sent the following message to {}" " on behalf of `{}`:\n{}".format( src.author.name, str(src.author.id), destination.mention, identity, text, ))