From 9132daaf8bcef45107676b4e917015af07081573 Mon Sep 17 00:00:00 2001
From: dnns01 <mail@dnns01.de>
Date: Sun, 4 Oct 2020 01:28:00 +0200
Subject: [PATCH] - Added loop for pipimeter. Every PIPIMETER_LOOP minutes
 (when the stream is live), when the pipimeter is not empty, the current count
 is sent into the chat (same as invoking !pipimeter as mod). In addition to
 that, the loop checks, whether the stream turned live after being offline for
 at least PIPI_RESET_THRESHOLD minutes. In that case, the pipi count is reset.
 - Added giveaway_cog.py and pipi_cog.py to source Giveaway and Pipi related
 functionality out into those cogs. - Changed the way, the pipimeter is
 colored. Instead of using absolute values, now the percentage controls the
 color change.

---
 README.md       |   5 +
 giveaway_cog.py |  74 ++++++++++++++
 hausgeist.py    | 262 +++++++++---------------------------------------
 pipi_cog.py     | 123 +++++++++++++++++++++++
 4 files changed, 252 insertions(+), 212 deletions(-)
 create mode 100644 giveaway_cog.py
 create mode 100644 pipi_cog.py

diff --git a/README.md b/README.md
index b131a27..a1109ce 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,8 @@ as the `hausgeist.py` file. This file must look like this:
     PIPI_DELAY=<Delay in seconds as spam protection. This is the number of seconds between two chat announcements>
     PIPI_THRESHOLD_1=<First Threshold. Used to change color and show, it is a bit more urgent>
     PIPI_THRESHOLD_2=<Second Threshold. Used to change color and show, that it is really urgent>
+    PIPIMETER_LOOP=<Number of minutes, when the current pipi count should get sent into the chat, when the stream is life>
+    PIPI_RESET_THRESHOLD=<Number of minutes, the stream had to be offline, when it turns live, to reset the pipi count>
     PIPI_COLOR_0=<Neutral color used for !pause command, and if pipi vote counter is 0>
     PIPI_COLOR_1=<Color used when pipi vote is at least one, and less than PIPI_THRESHOLD_1>
     PIPI_COLOR_2=<Color used when pipi vote is at least PIPI_THRESHOLD_1, and less than PIPI_THRESHOLD_2>
@@ -73,6 +75,9 @@ as the `hausgeist.py` file. This file must look like this:
     VOTE_MINUS=<Negative Chat Emote>
     VOTE_NEUTRAL=<Neutral Chat Emote>
     
+    # Giveaway Bot
+    GIVEAWAY_BOT=<Chat Color that is used to send giveaway messages>
+    
     # Redis Server
     REDIS_HOST=<IP of the Redis-Server>
     REDIS_PORT=<Port of the Redis-Server>
diff --git a/giveaway_cog.py b/giveaway_cog.py
new file mode 100644
index 0000000..3c0d09f
--- /dev/null
+++ b/giveaway_cog.py
@@ -0,0 +1,74 @@
+import os
+import random
+
+from twitchio.ext import commands
+
+
+@commands.core.cog(name="GiveawayCog")
+class GiveawayGog:
+    def __init__(self, bot):
+        self.bot = bot
+        self.giveaway_enabled = False
+        self.giveaway_entries = {}
+        self.COLOR = os.getenv("GIVEAWAY_COLOR")
+
+    @commands.command(name="giveaway")
+    async def cmd_giveaway(self, ctx):
+        """ take part at the giveaway """
+
+        if self.giveaway_enabled:
+            self.giveaway_entries[ctx.author.name] = 1
+
+    @commands.command(name="giveaway-open")
+    async def cmd_giveaway_open(self, ctx):
+        """ Reset and Open the giveaway """
+
+        if ctx.author.is_mod:
+            self.giveaway_enabled = True
+            self.giveaway_entries = {}
+            await self.bot.send_me(ctx,
+                                   "Das Giveaway wurde gestartet. Schreibe !giveaway in den Chat um daran teilzunehmen.",
+                                   self.COLOR)
+
+    @commands.command(name="giveaway-reopen")
+    async def cmd_giveaway_reopen(self, ctx):
+        """ Reopen the giveaway after closing it (so reset) """
+
+        if ctx.author.is_mod:
+            self.giveaway_enabled = True
+            await self.bot.send_me(ctx,
+                                   "Das Giveaway wurde wieder geöffnet. Schreibe !giveaway in den Chat um daran teilzunehmen.",
+                                   self.COLOR)
+
+    @commands.command(name="giveaway-close")
+    async def cmd_giveaway_close(self, ctx):
+        """ Close the giveaway """
+
+        if ctx.author.is_mod:
+            self.giveaway_enabled = False
+            await self.bot.send_me(ctx, "Das Giveaway wurde geschlossen. Es kann niemand mehr teilnehmen.", self.COLOR)
+
+    @commands.command(name="giveaway-draw")
+    async def cmd_giveaway_draw(self, ctx):
+        """ Draw a giveaway winner """
+
+        if ctx.author.is_mod:
+            if len(self.giveaway_entries) > 0:
+                winner = random.choice(list(self.giveaway_entries))
+                entry_count = len(self.giveaway_entries)
+                del self.giveaway_entries[winner]
+                await self.bot.send_me(ctx,
+                                       f"Es wurde aus {entry_count} Einträgen ausgelost. Und der Gewinner ist... @{winner}",
+                                       self.COLOR)
+            else:
+                await self.bot.send_me(ctx, "Es muss Einträge geben, damit ein Gewinner gezogen werden kann.",
+                                       self.COLOR)
+
+    @commands.command(name="giveaway-reset")
+    async def cmd_giveaway_reset(self, ctx):
+        """ Reset giveaway entrys """
+
+        if ctx.author.is_mod:
+            self.giveaway_enabled = False
+            self.giveaway_entries = {}
+            await self.bot.send_me(ctx, "Das Giveaway wurde geschlossen und alle Einträge entfernt.", self.COLOR)
diff --git a/hausgeist.py b/hausgeist.py
index 42c0cbd..a480b4c 100644
--- a/hausgeist.py
+++ b/hausgeist.py
@@ -1,30 +1,24 @@
 import asyncio
 import os
-import random
 import time
+from abc import ABC
 
 import redis
 from dotenv import load_dotenv
 from twitchio.dataclasses import Context, Message, Channel
 from twitchio.ext import commands
 
+from giveaway_cog import GiveawayGog
+from pipi_cog import PipiCog
+
 load_dotenv()
 IRC_TOKEN = os.getenv("IRC_TOKEN")
+CLIENT_ID = os.getenv("CLIENT_ID")
+CLIENT_SECRET = os.getenv("CLIENT_SECRET")
 NICK = os.getenv("NICK")
 CHANNEL = os.getenv("CHANNEL")
 PREFIX = os.getenv("PREFIX")
 
-# pipibot related
-PIPI_DELAY = int(os.getenv("PIPI_DELAY"))
-PIPI_THRESHOLD_1 = int(os.getenv("PIPI_THRESHOLD_1"))
-PIPI_THRESHOLD_2 = int(os.getenv("PIPI_THRESHOLD_2"))
-PIPI_COLOR_0 = os.getenv("PIPI_COLOR_0")
-PIPI_COLOR_1 = os.getenv("PIPI_COLOR_1")
-PIPI_COLOR_2 = os.getenv("PIPI_COLOR_2")
-PIPI_COLOR_3 = os.getenv("PIPI_COLOR_3")
-pipi_task = None
-pipi_votes = {}
-
 # vote bot related
 VOTE_DELAY_END = int(os.getenv("VOTE_DELAY_END"))
 VOTE_DELAY_INTERIM = int(os.getenv("VOTE_DELAY_INTERIM"))
@@ -38,16 +32,47 @@ vote_interim_task = None
 vote_task_new = None
 votes = {}
 
-# giveaway bot related
-giveaway_enabled = False
-giveaway_entries = {}
 
-bot = commands.Bot(
-    irc_token=IRC_TOKEN,
-    prefix=PREFIX,
-    nick=NICK,
-    initial_channels=[CHANNEL]
-)
+class HaugeBot(commands.Bot, ABC):
+    def __init__(self):
+        self.IRC_TOKEN = os.getenv("IRC_TOKEN")
+        self.CLIENT_ID = os.getenv("CLIENT_ID")
+        self.CLIENT_SECRET = os.getenv("CLIENT_SECRET")
+        self.NICK = os.getenv("NICK")
+        self.CHANNEL = os.getenv("CHANNEL")
+        self.PREFIX = os.getenv("PREFIX")
+        super().__init__(irc_token=IRC_TOKEN, prefix=PREFIX, nick=NICK, initial_channels=[CHANNEL], client_id=CLIENT_ID,
+                         client_secret=CLIENT_SECRET)
+        self.pipi_cog = PipiCog(self)
+        self.giveaway_cog = GiveawayGog(self)
+        self.add_cog(self.pipi_cog)
+        self.add_cog(self.giveaway_cog)
+
+    @staticmethod
+    async def send_me(ctx, content, color):
+        """ Change Text color to color and send content as message """
+
+        if type(ctx) is Context or type(ctx) is Channel:
+            await ctx.color(color)
+            await ctx.send_me(content)
+        elif type(ctx) is Message:
+            await ctx.channel.color(color)
+            await ctx.channel.send_me(content)
+
+    async def event_ready(self):
+        print('Logged in')
+        await self.pipi_cog.start_pipimeter_loop()
+
+    @staticmethod
+    def get_percentage(part, total):
+        """ Calculate percentage """
+        if total != 0:
+            return round(part / total * 100, 1)
+
+        return 0
+
+
+bot = HaugeBot()
 
 # redis-server related
 R_HOST = os.getenv("REDIS_HOST")
@@ -70,9 +95,6 @@ except Exception as ex:
 
 if useRedis and r:
     # update constants in Redis-DB
-    r.set('pipiDelay', PIPI_DELAY)
-    r.set('pipiT1', PIPI_THRESHOLD_1)
-    r.set('pipiT2', PIPI_THRESHOLD_2)
     r.set('voteMin', VOTE_MIN_VOTES)
     r.set('voteDelayEnd', VOTE_DELAY_END)
     r.set('voteDelayInter', VOTE_DELAY_INTERIM)
@@ -85,51 +107,6 @@ if useRedis and r:
     p.execute()  # transaction end
 
 
-def get_percentage(part, total):
-    """ Calculate percentage """
-    if total != 0:
-        return round(part / total * 100, 1)
-
-    return 0
-
-
-async def notify_pipi(ctx, use_timer=True, message=None):
-    """ Write a message in chat, if there hasn't been a notification since PIPI_DELAY seconds. """
-
-    global pipi_task
-
-    if use_timer and pipi_task and not pipi_task.done():
-        return
-
-    if pipi_task:
-        pipi_task.cancel()
-
-    pipi_task = asyncio.create_task(pipi_block_notification())
-    vote_ctr = 0
-    chatters = await bot.get_chatters(CHANNEL)
-
-    if message is not None:
-        await send_me(ctx, message, PIPI_COLOR_0)
-    else:
-        for vote in pipi_votes.values():
-            if vote == 1:
-                vote_ctr += 1
-
-        percentage = get_percentage(vote_ctr, chatters.count)
-
-        if vote_ctr == 0:
-            await send_me(ctx, f'Kein Druck (mehr) auf der Blase. Es kann fröhlich weiter gestreamt werden!',
-                          PIPI_COLOR_0)
-        elif vote_ctr == 1:
-            await send_me(ctx, f'{vote_ctr} ({percentage}%) Mensch müsste mal', PIPI_COLOR_1)
-        elif vote_ctr < PIPI_THRESHOLD_1:
-            await send_me(ctx, f'{vote_ctr} ({percentage}%) Menschen müssten mal', PIPI_COLOR_1)
-        elif vote_ctr < PIPI_THRESHOLD_2:
-            await send_me(ctx, f'{vote_ctr} ({percentage}%) Menschen müssten mal haugeAgree', PIPI_COLOR_2)
-        else:
-            await send_me(ctx, f'{vote_ctr} ({percentage}%) Menschen müssten mal haugeAgree haugeAgree', PIPI_COLOR_3)
-
-
 async def notify_vote_result(message, final_result=False):
     votes_list = get_votes()
 
@@ -139,67 +116,7 @@ async def notify_vote_result(message, final_result=False):
     output += f'Endergebnis' if final_result else f'Zwischenergebnis'
     output += f' mit insgesamt {len(votes)} abgegebenen Stimmen'
 
-    await send_me(message, output, VOTE_COLOR)
-
-
-async def send_me(ctx, content, color):
-    """ Change Text color to color and send content as message """
-
-    if type(ctx) is Context or type(ctx) is Channel:
-        await ctx.color(color)
-        await ctx.send_me(content)
-    elif type(ctx) is Message:
-        await ctx.channel.color(color)
-        await ctx.channel.send_me(content)
-
-
-@bot.event
-async def event_ready():
-    print('Logged in')
-
-
-@bot.command(name="pipi", aliases=["Pipi"])
-async def cmd_pipi(ctx):
-    """ User mentioned there is a need to go to toilet. """
-
-    pipi_votes[ctx.author.name] = 1
-    await notify_pipi(ctx)
-
-
-@bot.command(name="warpipi", aliases=["Warpipi", "zuspät", "Zuspät"])
-async def cmd_warpipi(ctx):
-    """ User already went to toilet. """
-
-    if ctx.author.name in pipi_votes:
-        pipi_votes[ctx.author.name] = 0
-        await notify_pipi(ctx)
-
-
-@bot.command(name="pause", aliases=["Pause"])
-async def cmd_pause(ctx):
-    """ We will do a break now! """
-
-    global pipi_votes
-
-    if ctx.author.is_mod:
-        pipi_votes = {}
-        await send_me(ctx, "Jetzt geht noch mal jeder aufs Klo, und dann streamen wir weiter!", PIPI_COLOR_0)
-
-
-@bot.command(name="reset", aliases=["Reset"])
-async def cmd_pause(ctx):
-    """ Reset pipi votes """
-
-    global pipi_votes
-
-    if ctx.author.is_mod:
-        pipi_votes = {}
-
-
-@bot.command(name="pipimeter", aliases=["Pipimeter"])
-async def cmd_pipimeter(ctx):
-    if ctx.author.is_mod:
-        await notify_pipi(ctx, use_timer=False)
+    await bot.send_me(message, output, VOTE_COLOR)
 
 
 @bot.command(name="hauge-commands", aliases=["Hauge-commands", "haugebot-commands", "Haugebot-commands"])
@@ -297,9 +214,9 @@ def get_votes():
         elif x == 'minus':
             minus += 1
 
-    return [[plus, get_percentage(plus, len(votes))],
-            [neutral, get_percentage(neutral, len(votes))],
-            [minus, get_percentage(minus, len(votes))]]
+    return [[plus, bot.get_percentage(plus, len(votes))],
+            [neutral, bot.get_percentage(neutral, len(votes))],
+            [minus, bot.get_percentage(minus, len(votes))]]
 
 
 async def vote_end_voting(channel):
@@ -347,83 +264,4 @@ async def vote_block_votes():
     await asyncio.sleep(VOTE_DELAY_INTERIM)
 
 
-async def pipi_block_notification():
-    """ Just do nothing but sleep for PIPI_DELAY seconds """
-
-    await asyncio.sleep(PIPI_DELAY)
-@bot.command(name="giveaway")
-async def cmd_giveaway(ctx):
-    """ take part at the giveaway """
-
-    global giveaway_entries
-    global giveaway_enabled
-
-    if giveaway_enabled:
-        giveaway_entries[ctx.author.name] = 1
-
-
-@bot.command(name="giveaway-open")
-async def cmd_giveawayopen(ctx):
-    """ Reset and Open the giveaway """
-
-    global giveaway_entries
-    global giveaway_enabled
-
-    if ctx.author.is_mod:
-        giveaway_enabled = True
-        giveaway_entries = {}
-        await send_me(ctx, "Das Giveaway wurde gestartet. Schreibe !giveaway in den Chat um daran teilzunehmen.", VOTE_COLOR)
-
-@bot.command(name="giveaway-reopen")
-async def cmd_giveawayopen(ctx):
-    """ Reopen the giveaway after closing it (so reset) """
-
-    global giveaway_enabled
-
-    if ctx.author.is_mod:
-        giveaway_enabled = True
-        await send_me(ctx, "Das Giveaway wurde wieder geöffnet. Schreibe !giveaway in den Chat um daran teilzunehmen.", VOTE_COLOR)
-
-
-@bot.command(name="giveaway-close")
-async def cmd_giveawayopen(ctx):
-    """ Close the giveaway """
-
-    global giveaway_entries
-    global giveaway_enabled
-
-    if ctx.author.is_mod:
-        giveaway_enabled = False
-        await send_me(ctx, "Das Giveaway wurde geschlossen. Es kann niemand mehr teilnehmen.", VOTE_COLOR)
-
-
-@bot.command(name="giveaway-draw")
-async def cmd_giveawaydraw(ctx):
-    """ Draw a giveaway winner """
-
-    global giveaway_entries
-
-    if ctx.author.is_mod:
-        if len(giveaway_entries) > 0:
-            winner = random.choice(list(giveaway_entries))
-            entry_count = len(giveaway_entries)
-            del giveaway_entries[winner]
-            await send_me(ctx, f"Es wurde aus {entry_count} Einträgen ausgelost. Und der Gewinner ist... @{winner}", VOTE_COLOR)
-        else:
-            await send_me(ctx, "Es muss Einträge geben, damit ein Gewinner gezogen werden kann.", VOTE_COLOR)
-
-
-@bot.command(name="giveaway-reset")
-async def cmd_giveawayreset(ctx):
-    """ Reset giveaway entrys """
-
-    global giveaway_entries
-    global giveaway_enabled
-
-    if ctx.author.is_mod:
-        giveaway_enabled = False
-        giveaway_entries = {}
-        await send_me(ctx, "Das Giveaway wurde geschlossen und alle Einträge entfernt.", VOTE_COLOR)
-
-
 bot.run()
diff --git a/pipi_cog.py b/pipi_cog.py
new file mode 100644
index 0000000..1f6e51d
--- /dev/null
+++ b/pipi_cog.py
@@ -0,0 +1,123 @@
+import asyncio
+import os
+
+from twitchio.dataclasses import Message
+from twitchio.ext import commands
+
+
+@commands.core.cog(name="PipiCog")
+class PipiCog:
+    def __init__(self, bot):
+        self.bot = bot
+        self.DELAY = int(os.getenv("PIPI_DELAY"))
+        self.THRESHOLD_1 = int(os.getenv("PIPI_THRESHOLD_1"))
+        self.THRESHOLD_2 = int(os.getenv("PIPI_THRESHOLD_2"))
+        self.PIPIMETER_LOOP = int(os.getenv("PIPIMETER_LOOP"))
+        self.RESET_THRESHOLD = int(os.getenv("PIPI_RESET_THRESHOLD"))
+        self.COLOR_0 = os.getenv("PIPI_COLOR_0")
+        self.COLOR_1 = os.getenv("PIPI_COLOR_1")
+        self.COLOR_2 = os.getenv("PIPI_COLOR_2")
+        self.COLOR_3 = os.getenv("PIPI_COLOR_3")
+        self.pipi_task = None
+        self.pipi_votes = {}
+
+    async def start_pipimeter_loop(self):
+        asyncio.create_task(self.pipimeter_loop())
+
+    async def notify_pipi(self, ctx, use_timer=True, message=None):
+        """ Write a message in chat, if there hasn't been a notification since DELAY seconds. """
+
+        if use_timer and self.pipi_task and not self.pipi_task.done():
+            return
+
+        if self.pipi_task:
+            self.pipi_task.cancel()
+
+        self.pipi_task = asyncio.create_task(self.pipi_block_notification())
+        vote_ctr = 0
+        chatters = await self.bot.get_chatters(self.bot.CHANNEL)
+
+        if message is not None:
+            await self.bot.send_me(ctx, message, self.COLOR_0)
+        else:
+            for vote in self.pipi_votes.values():
+                if vote == 1:
+                    vote_ctr += 1
+
+            percentage = self.get_percentage(vote_ctr, chatters.count)
+
+            if vote_ctr == 0:
+                await self.bot.send_me(ctx,
+                                       f'Kein Druck (mehr) auf der Blase. Es kann fröhlich weiter gestreamt werden!',
+                                       self.COLOR_0)
+            elif vote_ctr == 1:
+                await self.bot.send_me(ctx, f'{vote_ctr} ({percentage}%) Mensch müsste mal', self.COLOR_1)
+            elif percentage < self.THRESHOLD_1:
+                await self.bot.send_me(ctx, f'{vote_ctr} ({percentage}%) Menschen müssten mal', self.COLOR_1)
+            elif percentage < self.THRESHOLD_2:
+                await self.bot.send_me(ctx, f'{vote_ctr} ({percentage}%) Menschen müssten mal haugeAgree',
+                                       self.COLOR_2)
+            else:
+                await self.bot.send_me(ctx, f'{vote_ctr} ({percentage}%) Menschen müssten mal haugeAgree haugeAgree',
+                                       self.COLOR_3)
+
+    async def pipi_block_notification(self):
+        """ Just do nothing but sleep for DELAY seconds """
+
+        await asyncio.sleep(self.DELAY)
+
+    async def pipimeter_loop(self):
+        """ Send !pipimeter into the chat every x Minutes. Also check, whether the stream was offline for x Minutes.
+         If this is true, reset the pipi counter, as you can assume, that the stream recently started."""
+
+        offline_since = 0
+
+        while True:
+            await asyncio.sleep(self.PIPIMETER_LOOP * 60)
+
+            if await self.bot.get_stream(self.bot.CHANNEL):
+                if offline_since >= self.RESET_THRESHOLD:
+                    self.pipi_votes = {}
+                offline_since = 0
+
+                channel = self.bot.get_channel(self.bot.CHANNEL)
+                message = Message(channel=channel)
+                await self.notify_pipi(message, use_timer=False)
+            else:
+                offline_since += self.PIPIMETER_LOOP
+
+    @commands.command(name="pipi", aliases=["Pipi"])
+    async def cmd_pipi(self, ctx):
+        """ User mentioned there is a need to go to toilet. """
+
+        self.pipi_votes[ctx.author.name] = 1
+        await self.notify_pipi(ctx)
+
+    @commands.command(name="warpipi", aliases=["Warpipi", "zuspät", "Zuspät"])
+    async def cmd_warpipi(self, ctx):
+        """ User already went to toilet. """
+
+        if ctx.author.name in self.pipi_votes:
+            self.pipi_votes[ctx.author.name] = 0
+            await self.notify_pipi(ctx)
+
+    @commands.command(name="pause", aliases=["Pause"])
+    async def cmd_pause(self, ctx):
+        """ We will do a break now! """
+
+        if ctx.author.is_mod:
+            self.pipi_votes = {}
+            await self.bot.send_me(ctx, "Jetzt geht noch mal jeder aufs Klo, und dann streamen wir weiter!",
+                                   self.COLOR_0)
+
+    @commands.command(name="reset", aliases=["Reset"])
+    async def cmd_reset(self, ctx):
+        """ Reset pipi votes """
+
+        if ctx.author.is_mod:
+            self.pipi_votes = {}
+
+    @commands.command(name="pipimeter", aliases=["Pipimeter"])
+    async def cmd_pipimeter(self, ctx):
+        if ctx.author.is_mod:
+            await self.notify_pipi(ctx, use_timer=False)
-- 
GitLab