diff --git a/README.md b/README.md index b131a2769f49fc03e0d0e6f6fb81750c4c184837..a1109cee926efec4b463f1a439585054b9fbcd45 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 0000000000000000000000000000000000000000..3c0d09f3fb1af59f557cc1362458d97029c697dd --- /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 42c0cbd3a5e4dbe880d35ec0c6f6475c0f13e05f..a480b4cd8710f8ad6f1d7886d9cfd1af9606e2cf 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 0000000000000000000000000000000000000000..1f6e51dc977c9f4d11c841f77cb04bc28cc237e8 --- /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)