]> git.aero2k.de Git - urlbot-v3.git/commitdiff
Add CI pipeline definition
authorThorsten <mail@aero2k.de>
Wed, 15 Apr 2026 19:08:47 +0000 (21:08 +0200)
committerThorsten <mail@aero2k.de>
Wed, 15 Apr 2026 19:08:47 +0000 (21:08 +0200)
.ci.toml [new file with mode: 0644]
src/distbot/plugins/vote.py [new file with mode: 0644]
tests/test_unit/test_poll.py [new file with mode: 0644]

diff --git a/.ci.toml b/.ci.toml
new file mode 100644 (file)
index 0000000..95859b6
--- /dev/null
+++ b/.ci.toml
@@ -0,0 +1,7 @@
+[[step]]
+name = "unit-tests"
+image = "docker.io/library/python:3.12-slim"
+command = """
+pip install --quiet .[worker] pytest
+python -m pytest tests/test_unit/ -v
+"""
diff --git a/src/distbot/plugins/vote.py b/src/distbot/plugins/vote.py
new file mode 100644 (file)
index 0000000..404f4a0
--- /dev/null
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+import logging
+from collections import defaultdict
+from operator import itemgetter
+
+from distbot.bot.worker import Worker
+from distbot.common.action import Action
+from distbot.common.message import get_nick_from_message, get_words
+
+logger = logging.getLogger(__name__)
+
+
+class Voting(Worker):
+    # TODO: reporting, close-vote, autoclose?
+    binding_keys = ["vote.*", "nick.new-voting.*.#", "nick.close-voting"]
+    description = "start a voting using nick: new-voting <subject> <option(s)>, vote with vote 1/2/... if there are multiple or vote y/vote n for singular options. Use quotes as usual."
+
+
+    def __init__(self, actionqueue, queue="work"):
+        super().__init__(actionqueue, queue)
+        self.options = ["CDU", "SPD", "Trump", "nope"]
+        self.vote_subject = "Wer gewinnt die Wahl in Bielefeld 2024?"
+        self.votes = set([("TRex", "nope"), ("niemand", "Trump"), ("eggy", "CDU"), ("Saxman", "Trump"), ("urlbug", "Trump")])
+
+    def format_result(self):
+        results = defaultdict(int)
+        for user, vote in self.votes:
+            results[vote] += 1
+        return ", ".join([f"{option}: {count}" for option, count in sorted(results.items(), key=itemgetter(1), reverse=True)])
+
+
+    def close_voting(self):
+        if self.vote_subject:
+            result_msg = f"Voting '{self.vote_subject}' (with options {', '.join(self.options)}) is now closed. Votes: {str(self.format_result())}"
+            self.vote_subject = None
+            self.options.clear()
+            self.votes.clear()
+        else:
+            result_msg = "No active voting to close."
+        return Action(msg=result_msg)
+
+    def get_options(self):
+        binary = len(self.options) == 1
+        if binary:
+            voting_options = ["y", "n"]
+        else:
+            voting_options = list(map(str, range(1, len(self.options)+1)))
+        return voting_options
+
+    def parse_body(self, msg):
+        words = get_words(msg)
+
+        if words[0] == "vote":
+            if not self.vote_subject:
+                return Action(msg="No active voting in progress.")
+            sender = get_nick_from_message(msg)
+            vote = words[1]
+
+            if vote in self.options:
+                self.votes.add((sender, vote))
+            elif vote.isdigit() and 1 <= int(vote) <= len(self.options):
+                index = int(vote) - 1
+                self.votes.add((sender, self.options[index]))
+            elif len(self.options) == 1 and vote in "yn":
+                self.votes.add((sender, vote))
+            else:
+                return Action(msg="Invalid vote. Please use 'vote <option>' or 'vote <index> (e.g., vote 1)'.")
+
+        elif words[0] == "new-voting":
+            if self.vote_subject:
+                return Action(msg="Voting in progress. Close that one first.")
+            else:
+                self.vote_subject = words[1]
+                self.options = words[2:]
+                valid_options = ", ".join(self.get_options())
+                return Action(msg=f"Voting started. Please respond with any of {valid_options}")
+
+        elif words[0] == "close-voting":
+            if not self.vote_subject:
+                return Action(msg="No active voting in progress.")
+            else:
+                return self.close_voting()
+
+ALL = [Voting]
\ No newline at end of file
diff --git a/tests/test_unit/test_poll.py b/tests/test_unit/test_poll.py
new file mode 100644 (file)
index 0000000..96ebbaa
--- /dev/null
@@ -0,0 +1,40 @@
+import pytest
+from distbot.plugins import votepoll
+
+
+@pytest.mark.parametrize(
+    argnames=["words", "expected", "duration"],
+    argvalues=[
+        (["all", "vs", "nothing"], ["all", "nothing"], None),
+        (["all", "vs", "nothing", "vs"], ["all", "nothing"], None),
+        (["all", "vs", "nothing", "vs", "1000"], ["all", "nothing", "1000"], None),
+        (["all", "vs", "nothing", "vs", "emacs", "1000"], ["all", "nothing", "emacs"], 1000),
+    ]
+)
+def test_parse_words_for_poll_options(words, expected, duration):
+    result, d = votepoll.VotePoll.parse_words_for_poll_options(words)
+    assert result == expected
+    assert d == duration
+
+
+def test_votes():
+    assert votepoll.Poll(["a", "b", "c"], {}, 0.0).get_votes("a") == 0
+    assert votepoll.Poll(["a", "b", "c"], {"someone": "a"}, 0.0).get_votes("a") == 1
+
+
+def test_status_report():
+    report = votepoll.Poll(["a", "b", "c"], {"someone": "a"}, 0.0).status_report()
+    assert report == """**Poll status:**
+ * A - a: 1
+ * B - b: 0
+ * C - c: 0
+**Winner:** a (someone)"""
+
+
+def test_status_report_tie():
+    report = votepoll.Poll(["a", "b", "c"], {"someone": "a", "noone": "b"}, 0.0).status_report()
+    assert report == """**Poll status:**
+ * A - a: 1
+ * B - b: 1
+ * C - c: 0
+**Winners:** a (someone), b (noone)"""