├── .gitignore
├── LICENSE
├── README.md
├── maubot.yaml
└── poll.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.mbp
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Tom Casavant
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Archiving since Matrix/Element have had poll support for a few years now
2 |
3 | # Poll Maubot
4 | A plugin for [maubot](https://github.com/maubot/maubot) that creates a poll in a riot room and allows users to vote
5 |
6 | ## Usage
7 | '!poll new "Question" "Choice1" "Choice2" "Choice3"' - Creates a new poll with given (at least 1) choices
8 |
9 | The user can now also create polls with line-breaks:
10 | ```
11 | !poll new What is my question?
12 | Choice1
13 | Choice2
14 | Choice3
15 | ```
16 |
17 | '!poll results' - Displays the results from the poll
18 |
19 | '!poll close' - Ends the poll
20 |
21 | Users vote by adding the matching emoji to the poll (i.e. if the first choice has a :thumbsup: then in order to pick that choice the user has to react with :thumbsup:)
22 |
23 | ## Version 3.0 (Latest release)
24 | - Made emoji options more accessible
25 | - Bot now reacts to itself with emoji options
26 | - The user can now define emoji options
27 | - User can define answers with line-breaks rather than quotes
28 |
29 |
30 | ## Older Releases:
31 |
32 | ### Version 2.0
33 | - Changed voting format to reactions (instead of '!poll vote')
34 |
35 | #### Version 2.0.1
36 | - Allows every room to have unique poll
37 |
38 |
39 | ## Wish List
40 | - Add user configuration to only allow certain users to create polls
41 | - Add auto-timing ability
42 | - Make emojis configurable
43 | - Add placeholder emojis on each poll (to let users just click one)
44 |
--------------------------------------------------------------------------------
/maubot.yaml:
--------------------------------------------------------------------------------
1 |
2 | # This is an example maubot plugin definition file.
3 | # All plugins must include a file like this named "maubot.yaml" in their root directory.
4 |
5 | # Target maubot version
6 | maubot: 0.1.0
7 |
8 | # The unique ID for the plugin. Java package naming style. (i.e. use your own domain, not xyz.maubot)
9 | id: casavant.tom.poll
10 |
11 | # A PEP 440 compliant version string.
12 | version: 3.0.1
13 |
14 | # The SPDX license identifier for the plugin. https://spdx.org/licenses/
15 | # Optional, assumes all rights reserved if omitted.
16 | license: MIT
17 |
18 | # The list of modules to load from the plugin archive.
19 | # Modules can be directories with an __init__.py file or simply python files.
20 | # Submodules that are imported by modules listed here don't need to be listed separately.
21 | # However, top-level modules must always be listed even if they're imported by other modules.
22 | modules:
23 | - poll
24 | #- config
25 | # The main class of the plugin. Format: module/Class
26 | # If `module` is omitted, will default to last module specified in the module list.
27 | # Even if `module` is not omitted here, it must be included in the modules list.
28 | # The main class must extend maubot.Plugin
29 | main_class: PollPlugin
30 |
31 | # Whether or not instances need a database
32 | database: false
33 |
34 | # Extra files that the upcoming build tool should include in the mbp file.
35 | #extra_files:
36 | # - base-config.yaml
37 | #- LICENSE
38 |
39 | # List of dependencies
40 | #dependencies:
41 | #- config
42 |
43 | #soft_dependencies:
44 | #- bar>=0.1
45 |
--------------------------------------------------------------------------------
/poll.py:
--------------------------------------------------------------------------------
1 | import re
2 | import random
3 | from typing import List, Tuple
4 | from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
5 | from mautrix.types import (EventType, ReactionEvent)
6 | from maubot import Plugin, MessageEvent
7 | from maubot.handlers import command
8 |
9 |
10 | QUOTES_REGEX = r"\"?\s*?\"" # Regex to split string between quotes
11 | # [Octopus, Ghost, Robot, Okay Hand, Clapping Hands, Hundred, Pizza Slice, Taco, Bomb, Checquered Flag]
12 | REACTIONS = ["\U0001F44D", "\U0001F44E", "\U0001F419", "\U0001F47B", "\U0001F916", "\U0001F44C",
13 | "\U0001F44F", "\U0001F4AF", "\U0001F355", "\U0001F32E", "\U0001F4A3", "\U0001F3C1"]
14 | EMOJI_REGEX = r"^[\u2600-\u26FF\u2700-\u27BF\U0001F300-\U0001F5FF\U0001F600-\U0001F64F\U0001F680-\U0001F6FF\U0001F900-\U0001F9FF]"
15 |
16 |
17 | class Poll:
18 | def __init__(self, question, choices, emojis=None):
19 | self.question = question
20 | self.choices = choices
21 | self.votes = [0] * len(choices) # initialize all votes to zero
22 | self.voters = []
23 | self.active = True # Begins the poll
24 | self.total = 0
25 |
26 | if emojis:
27 | self.emojis = []
28 | reactions_filtered = list(set(REACTIONS).difference(set(emojis)))
29 | emojis_random = random.sample(
30 | reactions_filtered, emojis.count(None))
31 | for emoji in emojis:
32 | if emoji:
33 | self.emojis.append(emoji)
34 | else:
35 | self.emojis.append(emojis_random.pop())
36 | else:
37 | # Select a random assortment of emojis
38 | self.emojis = random.sample(REACTIONS, len(choices))
39 |
40 | def vote(self, choice, user_id):
41 | # Adds a vote to the given choice
42 | self.votes[choice] += 1
43 | # Adds user to list of users who have already voted
44 | self.voters.append(user_id)
45 | self.total += 1
46 |
47 | def isAvailable(self, choice):
48 | # Checks if given choice is an option
49 | return choice <= len(self.choices)
50 |
51 | def hasVoted(self, user):
52 | # Checks if user has already voted
53 | return user in self.voters
54 |
55 | def isActive(self):
56 | # Checks if the poll is currently active
57 | return self.active
58 |
59 | def get_results(self):
60 | # Formats the results with percentages
61 | results = "
".join(
62 | [
63 | f"