├── .gitignore ├── docs ├── poll-bot-online.png └── index.md ├── cogs ├── __pycache__ │ ├── meta.cpython-37.pyc │ ├── owner.cpython-37.pyc │ ├── poll.cpython-37.pyc │ ├── stats.cpython-37.pyc │ ├── Database.cpython-37.pyc │ ├── strawpoll.cpython-37.pyc │ └── BackgroundTasks.cpython-37.pyc ├── owner.py ├── meta.py ├── strawpoll.py └── poll.py ├── config.json ├── launcher.py ├── LICENSE ├── bot.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | *.pyc 3 | 4 | -------------------------------------------------------------------------------- /docs/poll-bot-online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/averagely/Poll-Bot/HEAD/docs/poll-bot-online.png -------------------------------------------------------------------------------- /cogs/__pycache__/meta.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/averagely/Poll-Bot/HEAD/cogs/__pycache__/meta.cpython-37.pyc -------------------------------------------------------------------------------- /cogs/__pycache__/owner.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/averagely/Poll-Bot/HEAD/cogs/__pycache__/owner.cpython-37.pyc -------------------------------------------------------------------------------- /cogs/__pycache__/poll.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/averagely/Poll-Bot/HEAD/cogs/__pycache__/poll.cpython-37.pyc -------------------------------------------------------------------------------- /cogs/__pycache__/stats.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/averagely/Poll-Bot/HEAD/cogs/__pycache__/stats.cpython-37.pyc -------------------------------------------------------------------------------- /cogs/__pycache__/Database.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/averagely/Poll-Bot/HEAD/cogs/__pycache__/Database.cpython-37.pyc -------------------------------------------------------------------------------- /cogs/__pycache__/strawpoll.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/averagely/Poll-Bot/HEAD/cogs/__pycache__/strawpoll.cpython-37.pyc -------------------------------------------------------------------------------- /cogs/__pycache__/BackgroundTasks.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/averagely/Poll-Bot/HEAD/cogs/__pycache__/BackgroundTasks.cpython-37.pyc -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "discord_token" : "", 3 | "shards": { 4 | "count": 1, 5 | "first_shard_id": 0, 6 | "last_shard_id": 0 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /launcher.py: -------------------------------------------------------------------------------- 1 | # usage: python launcher.py num_shards first_shard_id:last_shard_id 2 | 3 | import discord 4 | from bot import PollBot 5 | import sys 6 | import json 7 | 8 | with open('config.json') as config_file: 9 | config = json.load(config_file) 10 | 11 | bot = PollBot(config) 12 | bot.run() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 finnreid19 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cogs/owner.py: -------------------------------------------------------------------------------- 1 | # thanks to evieepy on github https://gist.github.com/EvieePy/d78c061a4798ae81be9825468fe146be 2 | import discord 3 | from discord.ext import commands 4 | 5 | 6 | class Owner(commands.Cog): 7 | def __init__(self, bot): 8 | self.bot = bot 9 | 10 | @commands.command(name="load", hidden=True) 11 | @commands.is_owner() 12 | async def load(self, ctx, *, cog: str): 13 | try: 14 | self.bot.load_extension(cog) 15 | except Exception as e: 16 | await ctx.send("**`ERROR:`**") 17 | else: 18 | await ctx.send("**`SUCCESS`**") 19 | 20 | @commands.command(name="unload", hidden=True) 21 | @commands.is_owner() 22 | async def unload(self, ctx, *, cog: str): 23 | print(cog) 24 | try: 25 | self.bot.unload_extension(cog) 26 | except Exception as e: 27 | await ctx.send("**`ERROR:`**") 28 | else: 29 | await ctx.send("**`SUCCESS`**") 30 | 31 | @commands.command(name="reload", hidden=True) 32 | @commands.is_owner() 33 | async def reload(self, ctx, *, cog: str): 34 | try: 35 | self.bot.unload_extension(cog) 36 | self.bot.load_extension(cog) 37 | except Exception as e: 38 | await ctx.send("**`ERROR:`**") 39 | else: 40 | await ctx.send("**`SUCCESS`**") 41 | 42 | @commands.command(name="shards", hidden=True) 43 | @commands.is_owner() 44 | async def getShards(self, ctx): 45 | await ctx.send("Shards: " + str(self.bot.shard_count)) 46 | 47 | 48 | 49 | 50 | def setup(bot): 51 | bot.add_cog(Owner(bot)) 52 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | import logging 4 | import aiohttp 5 | 6 | #logging.basicConfig(level=logging.INFO) 7 | 8 | extensions = ( 9 | "cogs.poll", 10 | "cogs.strawpoll", 11 | "cogs.meta", 12 | "cogs.owner" 13 | ) 14 | 15 | class PollBot(commands.AutoShardedBot): 16 | def __init__(self, config): 17 | prefixes = ["+", "poll:", "Poll:", "POLL:"] 18 | super().__init__( 19 | command_prefix = prefixes, 20 | status = discord.Status.online, 21 | activity = discord.Game(name = "+help")) 22 | self.config = config 23 | self.shard_count = self.config["shards"]["count"] 24 | shard_ids_list = [] 25 | shard_ids = [] 26 | 27 | # create list of shard ids 28 | for i in range(self.config["shards"]["first_shard_id"], self.config["shards"]["last_shard_id"]+1): 29 | shard_ids_list.append(i) 30 | self.shard_ids = tuple(shard_ids_list) 31 | 32 | self.remove_command("help") 33 | 34 | for extension in extensions: 35 | self.load_extension(extension) 36 | 37 | async def on_ready(self): 38 | self.http_session = aiohttp.ClientSession() 39 | print("Logged in as") 40 | print(self.user.name) 41 | print(self.user.id) 42 | print("--------") 43 | 44 | async def on_message(self, message): 45 | if not message.author.bot: 46 | await self.process_commands(message) 47 | 48 | # @on_message.error 49 | # async def on_message_error(self, ctx, error): 50 | # if isinstance(error, commands.CommandOnCooldown): 51 | # await ctx.send('I could not find that member...') 52 | 53 | def run(self): 54 | super().run(self.config["discord_token"], reconnect=True) 55 | -------------------------------------------------------------------------------- /cogs/meta.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord.ext.commands.cooldowns import BucketType 4 | 5 | class Meta(commands.Cog): 6 | def __init__(self, bot): 7 | self.bot = bot 8 | 9 | self.help_message = discord.Embed( 10 | description="**Reaction Poll**\nCreate a reaction poll by typing `+poll *your message*`. Poll Bot will automatically add the reactions 👍, 👎, and 🤷.\nCreate a reaction poll with multiple options by typing `+poll {title} [Option1] [Option2] [Option3]`.\n\n**Strawpoll**\nCreate a strawpoll by typing `+strawpoll {title} [Option1] [Option2] [Option 3]`, with up to 30 options." 11 | + "\n\n **Don't want advertisements?** \n [Purchase Poll Bot premium for no advertisements and better uptime!](https://www.patreon.com/pollbot)" 12 | + "\n\n**Other Commands**\n+invite\n\n**Still Have Questions?**\nCheck out Poll Bot's faq: [https://stayingqold.github.io/Poll-Bot/](https://stayingqold.github.io/Poll-Bot/)\nJoin our Discord server: " 13 | + "\n", 14 | colour=0x83BAE3, 15 | ) 16 | 17 | self.invite_message = discord.Embed( 18 | description="Invite Poll Bot to your server: ", 19 | colour=0x83BAE3, 20 | ) 21 | 22 | @commands.command(name="help") 23 | @commands.cooldown(2,60,BucketType.user) 24 | async def help(self, ctx): 25 | await ctx.message.channel.send(embed=self.help_message) 26 | 27 | @commands.command(name="invite") 28 | @commands.cooldown(2,60,BucketType.user) 29 | async def invite(self, ctx): 30 | await ctx.message.channel.send(embed=self.invite_message) 31 | 32 | @help.error 33 | async def help_error(self, ctx, error): 34 | if isinstance(error, commands.CommandOnCooldown): 35 | await ctx.send(error) 36 | 37 | @invite.error 38 | async def invite_error(self, ctx, error): 39 | if isinstance(error, commands.CommandOnCooldown): 40 | await ctx.send(error) 41 | 42 | def setup(bot): 43 | bot.add_cog(Meta(bot)) 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Poll Bot [ARCHIVED] 2 | Poll Bot stopped running in July 2022. This repository is archived and will not be updated. If you want to run your own instance of Poll Bot, you can find the source code here, though there have been many breaking changes to the Discord API and discord.py since this repository was archived, so it will take some work to get it running again. Below is the old README.md. 3 | 4 | ## Poll Bot 5 | 6 | Poll Bot is a Discord bot that lets you create strawpolls and reaction polls 7 | 8 | Add Poll Bot to your server here: https://discordapp.com/oauth2/authorize?client_id=298673420181438465&scope=bot&permissions=0 9 | 10 | ## How to use Poll Bot 11 | Create a strawpoll by typing '+strawpoll {title} [Option1] [Option2] [Option 3]', with up to 26 options. 12 | 13 | 14 | Create a reaction poll by typing '+poll _____’. Poll Bot will automatically add the reactions 👍, 👎, and 🤷 15 | 16 | Create a multi reaction poll by typing +poll {title} [option 1] [option 2] [option 3] 17 | 18 | ## Installation 19 | If you are running your own instance of Poll Bot, please 1) change the name and logo and 2) don't post it on bot listing sites unless you've made some changes to it. 20 | 21 | 22 | ### Install Requirements 23 | Install python 3.5.3+ 24 | 25 | Install discord.py by doing something like this (depends on how you have python set up, but this generally will work): 26 | 27 | ```bash 28 | python3 -m pip install -U discord.py 29 | ``` 30 | 31 | ### Configure config.json 32 | Add your discord token and sharding information to `config.json`. Your discord token can be found from the [applications page on discord.com](https://discord.com/developers/applications). Use 1 shard for every 1000 servers your bot is on (for example, if your bot is on 2,000 servers, `count` should be 2). If you are just running this for your own server, you are probably going to be safe using 1 shard with the id of 0, so your config file would look like this: 33 | 34 | ```json 35 | { 36 | "discord_token" : "your_token_here", 37 | "shards": { 38 | "count": 1, 39 | "first_shard_id": 0, 40 | "last_shard_id": 0 41 | } 42 | } 43 | ``` 44 | 45 | Poll Bot is set up for [large bot sharding](https://discord.com/developers/docs/topics/gateway#sharding-for-very-large-bots), so the # of shards must be a multiple of 16. You likely won't have to worry about this. 46 | 47 | ### Running Poll Bot 48 | Once you have installed the requirements and set up `config.json`, run `python3 launcher.py` to run the bot. 49 | 50 | 51 | ## License 52 | MIT 53 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Poll Bot Frequently Asked Questions 2 | ## Why is Poll Bot not responding to me? 3 | This is most likely because Poll Bot does not have the correct permissions in the channel you are trying to use it in. Poll Bot requires: __Read Messages, Send Messages, Add Reactions, Embed Links, and Read Message History__. 4 | 5 | Even if Poll Bot has a roll that enables all of this, channel or category specific permissions might override the role permissions, so make sure the channel and category permissions also give Poll Bot these permissions. 6 | 7 | If you have double checked that Poll Bot has all of these permissions in the __role, category, and channel__, and Poll Bot is still not responding, check to see if it is offline. If you go to Poll Bot's profile and it doesn't have a green dot (as shown in the picture below), it is offline. Please join the [Poll Bot support server](https://discord.gg/FhT6nUn) and ping @finn#1327. 8 | 9 | ![Poll Bot Online Indicator](poll-bot-online.png) 10 | 11 | ## How do I create a poll? 12 | ### Reaction Poll 13 | Create a reaction poll by typing +poll *your message*. Poll Bot will automatically add the reactions 👍, 👎, and 🤷‍♂️. 14 | 15 | Create a reaction poll with __multiple options__ by typing +poll {title} [Option1] [Option2] [Option3], with up to 26 options. 16 | 17 | ### Strawpoll 18 | Create a strawpoll by typing +strawpoll {title} [Option1] [Option2] [Option 3], with up to 30 options. 19 | 20 | ## How do I get rid of ads? 21 | You can purchase Poll Bot Premium for $5 per month here: [https://www.patreon.com/pollbot](https://www.patreon.com/pollbot) 22 | 23 | ## How do I invite the bot to my server? 24 | You can use this link to invite Poll Bot to any server you are an admin in: [https://discordapp.com/oauth2/authorize?client_id=298673420181438465&permissions=84032&scope=bot](https://discordapp.com/oauth2/authorize?client_id=298673420181438465&permissions=84032&scope=bot) 25 | 26 | ## What permissions does Poll Bot require? 27 | Read Messages, Send Messages, Add Reactions, Embed Links, and Read Message History. 28 | 29 | ## Can I make the bot only be used by certain ranks or staff members? 30 | No. 31 | 32 | ## How do I change the prefix? 33 | You cannot. 34 | 35 | ## Is there a setting that prohibits people from voting more than once? 36 | Not on reaction polls, but if you use strawpolls (+strawpoll {title} [option 1] [option 2] [...]) people are limited to one vote (to the extent strawpoll.com can enforce). 37 | 38 | ## It looks like the bot is Offline, what can I do to get it back Online? 39 | Please join the [Poll Bot support server](https://discord.gg/FhT6nUn) and ping @finn#1327. 40 | 41 | 42 | ## Need More Help? 43 | Join the [Poll Bot support server](https://discord.gg/FhT6nUn) and ask for help in the #support channel. -------------------------------------------------------------------------------- /cogs/strawpoll.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from discord.ext.commands.cooldowns import BucketType 4 | 5 | 6 | class StrawPoll(commands.Cog): 7 | def __init__(self, bot): 8 | self.bot = bot 9 | 10 | def find_title(self, message): 11 | # this is the index of the first character of the title 12 | first = message.find('{') + 1 13 | # index of the last character of the title 14 | last = message.find('}') 15 | if first == 0 or last == -1: 16 | # TODO: Send a message telling the use how they are using it incorrectly. 17 | return "Not using the command correctly" 18 | return message[first:last] 19 | def find_options(self, message, options): 20 | # first index of the first character of the option 21 | first = message.find('[') + 1 22 | # index of the last character of the title 23 | last = message.find(']') 24 | if (first == 0 or last == -1): 25 | if len(options) < 2: 26 | # TODO: Send a message telling the use how they are using it incorrectly. 27 | return "Not using the command correctly" 28 | else: 29 | return options 30 | options.append(message[first:last]) 31 | message = message[last+1:] 32 | return self.find_options(message, options) 33 | 34 | @commands.command(name="strawpoll") 35 | @commands.cooldown(2,60,BucketType.user) 36 | async def strawpoll(self, ctx): 37 | if not ctx.message.author.bot: 38 | message = ctx.message.clean_content 39 | 40 | title = self.find_title(message) 41 | 42 | options = self.find_options(message, []) 43 | 44 | try: 45 | async with self.bot.http_session.post( 46 | "https://strawpoll.com/api/poll", 47 | json={ 48 | "poll": { 49 | "title": title, 50 | "answers": options, 51 | } 52 | 53 | }, 54 | headers={"Content Type": "application/json"}, 55 | ) as resp: 56 | json = await resp.json() 57 | 58 | await ctx.message.channel.send( 59 | "https://strawpoll.com/" + str(json["content_id"]) 60 | ) 61 | 62 | except KeyError: 63 | return "Please make sure you are using the format '+strawpoll {title} [Option1] [Option2] [Option 3]'" 64 | 65 | @strawpoll.error 66 | async def strawpoll_error(self, ctx, error): 67 | if isinstance(error, commands.CommandOnCooldown): 68 | await ctx.send(error) 69 | def setup(bot): 70 | bot.add_cog(StrawPoll(bot)) 71 | -------------------------------------------------------------------------------- /cogs/poll.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import asyncio 3 | from discord.ext import commands 4 | import random 5 | from discord.ext.commands.cooldowns import BucketType 6 | 7 | 8 | 9 | class Poll(commands.Cog): 10 | def __init__(self, bot): 11 | self.bot = bot 12 | 13 | self.emojiLetters = [ 14 | "\N{REGIONAL INDICATOR SYMBOL LETTER A}", 15 | "\N{REGIONAL INDICATOR SYMBOL LETTER B}", 16 | "\N{REGIONAL INDICATOR SYMBOL LETTER C}", 17 | "\N{REGIONAL INDICATOR SYMBOL LETTER D}", 18 | "\N{REGIONAL INDICATOR SYMBOL LETTER E}", 19 | "\N{REGIONAL INDICATOR SYMBOL LETTER F}", 20 | "\N{REGIONAL INDICATOR SYMBOL LETTER G}", 21 | "\N{REGIONAL INDICATOR SYMBOL LETTER H}", 22 | "\N{REGIONAL INDICATOR SYMBOL LETTER I}", 23 | "\N{REGIONAL INDICATOR SYMBOL LETTER J}", 24 | "\N{REGIONAL INDICATOR SYMBOL LETTER K}", 25 | "\N{REGIONAL INDICATOR SYMBOL LETTER L}", 26 | "\N{REGIONAL INDICATOR SYMBOL LETTER M}", 27 | "\N{REGIONAL INDICATOR SYMBOL LETTER N}", 28 | "\N{REGIONAL INDICATOR SYMBOL LETTER O}", 29 | "\N{REGIONAL INDICATOR SYMBOL LETTER P}", 30 | "\N{REGIONAL INDICATOR SYMBOL LETTER Q}", 31 | "\N{REGIONAL INDICATOR SYMBOL LETTER R}", 32 | "\N{REGIONAL INDICATOR SYMBOL LETTER S}", 33 | "\N{REGIONAL INDICATOR SYMBOL LETTER T}", 34 | "\N{REGIONAL INDICATOR SYMBOL LETTER U}", 35 | "\N{REGIONAL INDICATOR SYMBOL LETTER V}", 36 | "\N{REGIONAL INDICATOR SYMBOL LETTER W}", 37 | "\N{REGIONAL INDICATOR SYMBOL LETTER X}", 38 | "\N{REGIONAL INDICATOR SYMBOL LETTER Y}", 39 | "\N{REGIONAL INDICATOR SYMBOL LETTER Z}" 40 | ] 41 | 42 | # parses the title, which should be in between curly brackets ('{ title }') 43 | def find_title(self, message): 44 | # this is the index of the first character of the title 45 | first = message.find('{') + 1 46 | # index of the last character of the title 47 | last = message.find('}') 48 | if first == 0 or last == -1: 49 | return "Not using the command correctly" 50 | return message[first:last] 51 | # parses the options (recursively), which should be in between square brackets ('[ option n ]') 52 | def find_options(self, message, options): 53 | # first index of the first character of the option 54 | first = message.find('[') + 1 55 | # index of the last character of the title 56 | last = message.find(']') 57 | if (first == 0 or last == -1): 58 | if len(options) < 2: 59 | return "Not using the command correctly" 60 | else: 61 | return options 62 | options.append(message[first:last]) 63 | message = message[last+1:] 64 | return self.find_options(message, options) 65 | 66 | 67 | 68 | #@commands.Cog.listener() 69 | @commands.cooldown(2,60,BucketType.user) 70 | @commands.command(name="poll") 71 | # Limit how often a command can be used, (num per, seconds, BucketType.default/user/member/guild/channel/role) 72 | async def poll(self, ctx): 73 | message = ctx.message 74 | if not message.author.bot: 75 | if message.content.startswith("+poll") or message.content.startswith("poll:") or message.content.startswith("Poll:") or message.content.startswith("+poll:") or message.content.startswith("+Poll:"): 76 | messageContent = message.clean_content 77 | if messageContent.find("{") == -1: 78 | await message.add_reaction('👍') 79 | await message.add_reaction('👎') 80 | await message.add_reaction('🤷') 81 | else: 82 | title = self.find_title(messageContent) 83 | options = self.find_options(messageContent, []) 84 | 85 | try: 86 | pollMessage = "" 87 | i = 0 88 | for choice in options: 89 | if not options[i] == "": 90 | if len(options) > 21: 91 | await message.channel.send("Please make sure you are using the command correctly and have less than 21 options.") 92 | return 93 | elif not i == len(options): 94 | pollMessage = pollMessage + "\n\n" + self.emojiLetters[i] + " " + choice 95 | i += 1 96 | 97 | ads = ["[\n\nDon't want advertisements? Purchase Poll Bot Premium by clicking here.](https://www.patreon.com/pollbot)", "\n\n[Don't let eye strain ruin your day. Protect your eyes from harmful blue light using Bakery Gaming's glasses. Use code 'Poll Bot' for 20% off your order.](https://bakerygaming.store/collections/all)"] 98 | 99 | 100 | 101 | e = discord.Embed(title="**" + title + "**", 102 | description=pollMessage + ads[0], 103 | colour=0x83bae3) 104 | pollMessage = await message.channel.send(embed=e) 105 | i = 0 106 | final_options = [] # There is a better way to do this for sure, but it also works that way 107 | for choice in options: 108 | if not i == len(options) and not options[i] == "": 109 | final_options.append(choice) 110 | await pollMessage.add_reaction(self.emojiLetters[i]) 111 | i += 1 112 | except KeyError: 113 | return "Please make sure you are using the format 'poll: {title} [Option1] [Option2] [Option 3]'" 114 | else: 115 | return 116 | @poll.error 117 | async def poll_error(self, ctx, error): 118 | if isinstance(error, commands.CommandOnCooldown): 119 | await ctx.send(error) 120 | 121 | def setup(bot): 122 | bot.add_cog(Poll(bot)) 123 | --------------------------------------------------------------------------------