diff --git a/.env.template b/.env.template index e9696e9affb3210abaf01f85c4dbff84d373a11e..f4b7f21e76d7c4f1356dbf05904a1731a4221cb6 100644 --- a/.env.template +++ b/.env.template @@ -8,8 +8,10 @@ PREFIX=! # Pipi Bot PIPI_DELAY=15 -PIPI_THRESHOLD_1=10 -PIPI_THRESHOLD_2=20 +PIPI_THRESHOLD_1=1 +PIPI_THRESHOLD_2=3 +PIPIMETER_LOOP=10 +PIPI_RESET_THRESHOLD=30 PIPI_COLOR_0=YellowGreen PIPI_COLOR_1=Green PIPI_COLOR_2=GoldenRod @@ -24,6 +26,9 @@ VOTE_PLUS=haugePlus VOTE_MINUS=haugeMinus VOTE_NEUTRAL=haugeNeutral +# Giveaway Bot +GIVEAWAY_COLOR=OrangeRed + # Redis Server REDIS_HOST=127.0.0.1 REDIS_PORT=6379 diff --git a/.idea/vagrant.xml b/.idea/vagrant.xml new file mode 100644 index 0000000000000000000000000000000000000000..a5aa78680308cff5d34e132d6cabd2ab43745203 --- /dev/null +++ b/.idea/vagrant.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VagrantProjectSettings"> + <option name="instanceFolder" value="" /> + <option name="provider" value="" /> + </component> +</project> \ No newline at end of file diff --git a/hausgeist.py b/hausgeist.py index a480b4cd8710f8ad6f1d7886d9cfd1af9606e2cf..e43628c7618890e938fc7afc3c827f05dcaad9ef 100644 --- a/hausgeist.py +++ b/hausgeist.py @@ -1,15 +1,13 @@ -import asyncio import os -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 +from vote_cog import VoteCog load_dotenv() IRC_TOKEN = os.getenv("IRC_TOKEN") @@ -19,19 +17,6 @@ NICK = os.getenv("NICK") CHANNEL = os.getenv("CHANNEL") PREFIX = os.getenv("PREFIX") -# vote bot related -VOTE_DELAY_END = int(os.getenv("VOTE_DELAY_END")) -VOTE_DELAY_INTERIM = int(os.getenv("VOTE_DELAY_INTERIM")) -VOTE_MIN_VOTES = int(os.getenv("VOTE_MIN_VOTES")) -VOTE_COLOR = os.getenv("VOTE_COLOR") -VOTE_PLUS = os.getenv("VOTE_PLUS") -VOTE_MINUS = os.getenv("VOTE_MINUS") -VOTE_NEUTRAL = os.getenv("VOTE_NEUTRAL") -vote_end_task = None -vote_interim_task = None -vote_task_new = None -votes = {} - class HaugeBot(commands.Bot, ABC): def __init__(self): @@ -45,8 +30,10 @@ class HaugeBot(commands.Bot, ABC): client_secret=CLIENT_SECRET) self.pipi_cog = PipiCog(self) self.giveaway_cog = GiveawayGog(self) + self.vote_cog = VoteCog(self) self.add_cog(self.pipi_cog) self.add_cog(self.giveaway_cog) + self.add_cog(self.vote_cog) @staticmethod async def send_me(ctx, content, color): @@ -74,50 +61,6 @@ class HaugeBot(commands.Bot, ABC): bot = HaugeBot() -# redis-server related -R_HOST = os.getenv("REDIS_HOST") -R_PORT = os.getenv("REDIS_PORT") -R_DB = os.getenv("REDIS_DB") -R_PW = os.getenv("REDIS_PW") - -useRedis = False - -# try to connect -r = None -try: - r = redis.Redis(host=R_HOST, port=R_PORT, db=R_DB, password=R_PW) - print(r) - r.ping() - useRedis = True - print('Redis: Connected!') -except Exception as ex: - print('A connection to the Redis server could not be established. Redis querys are avoided.') - -if useRedis and r: - # update constants in Redis-DB - r.set('voteMin', VOTE_MIN_VOTES) - r.set('voteDelayEnd', VOTE_DELAY_END) - r.set('voteDelayInter', VOTE_DELAY_INTERIM) - - # reset DB - p = r.pipeline() # start transaction - p.set('plus', 0) - p.set('neutral', 0) - p.set('minus', 0) - p.execute() # transaction end - - -async def notify_vote_result(message, final_result=False): - votes_list = get_votes() - - output = f'{VOTE_PLUS} {votes_list[0][0]} ({votes_list[0][1]}%), ' \ - f'{VOTE_NEUTRAL} {votes_list[1][0]} ({votes_list[1][1]}%), ' \ - f'{VOTE_MINUS} {votes_list[2][0]} ({votes_list[2][1]}%) | ' - output += f'Endergebnis' if final_result else f'Zwischenergebnis' - output += f' mit insgesamt {len(votes)} abgegebenen Stimmen' - - await bot.send_me(message, output, VOTE_COLOR) - @bot.command(name="hauge-commands", aliases=["Hauge-commands", "haugebot-commands", "Haugebot-commands"]) async def cmd_haugebot_commands(ctx): @@ -125,143 +68,4 @@ async def cmd_haugebot_commands(ctx): "Eine Liste mit den Commands des HaugeBot findest du unter: https://github.com/dnns01/TwitchHausGeist/blob/master/README.md") -@bot.event -async def event_message(message): - global votes - - if message.content.startswith(PREFIX): - await bot.handle_commands(message) - else: - - # make sure the bot ignores itself and nightbot - if message.author.name.lower() in [NICK.lower(), 'nightbot']: - return - - # check if message is a vote - msg = message.content - if msg[:2] == '+-' or msg[:2] == '-+' or msg[:3] == '-/+' or msg[:3] == '+/-' or msg[:len( - VOTE_NEUTRAL)] == VOTE_NEUTRAL: - add_vote(message, 'neutral') - elif msg[:1] == '+' or msg[:len(VOTE_PLUS)] == VOTE_PLUS: - add_vote(message, 'plus') - elif msg[:1] == '-' or msg[:len(VOTE_MINUS)] == VOTE_MINUS: - add_vote(message, 'minus') - - if useRedis: - # update redis-database - update_redis() - - -def add_vote(ctx, votetype): - """adds votes to the votes-dict and sets timestamps""" - global votes, vote_end_task, vote_interim_task - - # Delay between two votes is not yet finished. So votes are not counted. - if vote_task_new and not vote_task_new.done(): - return - - if len(votes) == 0: - vote_end_task = asyncio.create_task(vote_end_voting(bot.get_channel(CHANNEL))) - vote_interim_task = asyncio.create_task(vote_interim_voting(bot.get_channel(CHANNEL))) - - # should vote extend voting? - if ctx.author.name not in votes or votes[ctx.author.name] != votetype: - vote_end_task.set_name(int(time.time())) - - # add vote to dict - votes[ctx.author.name] = votetype - - if useRedis: - # update redis-database - update_redis() - - -def update_redis(): - """analyzes the votes-dict and counts the votes""" - plus = 0 - minus = 0 - neutral = 0 - - # count values in dict - for x in votes.values(): - if x == 'neutral': - neutral += 1 - elif x == 'plus': - plus += 1 - elif x == 'minus': - minus += 1 - - if useRedis: - p = r.pipeline() # start transaction - p.set('plus', plus) - p.set('neutral', neutral) - p.set('minus', minus) - p.execute() # transaction end - - -def get_votes(): - """analyzes the votes-dict and counts the votes""" - plus = 0 - minus = 0 - neutral = 0 - - # count values in dict - for x in votes.values(): - if x == 'neutral': - neutral += 1 - elif x == 'plus': - plus += 1 - elif x == 'minus': - minus += 1 - - 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): - """ End a currently open voting """ - - global vote_task_new - - # Wait for the initial VOTE_DELAY_END seconds - await asyncio.sleep(VOTE_DELAY_END) - - # Check every second, if the delay has finished, because since VOTE_DELAY_END seconds there was no vote, - # that extended the voting time - while int(vote_end_task.get_name()) + VOTE_DELAY_END >= time.time(): - await asyncio.sleep(1) - - if len(votes) >= VOTE_MIN_VOTES: - await notify_vote_result(channel, final_result=True) - - votes.clear() - vote_task_new = asyncio.create_task(vote_block_votes()) - - if useRedis: - # update redis-database - update_redis() - - -async def vote_interim_voting(channel): - """ End a currently open voting """ - - global vote_interim_task - - await asyncio.sleep(VOTE_DELAY_INTERIM) - if not vote_end_task.done() and len(votes) >= VOTE_MIN_VOTES: - await notify_vote_result(channel) - vote_interim_task = asyncio.create_task(vote_interim_voting(bot.get_channel(CHANNEL))) - - if useRedis: - # update redis-database - update_redis() - - -async def vote_block_votes(): - """ Just do nothing but sleep for VOTE_DELAY_INTERIM seconds """ - - await asyncio.sleep(VOTE_DELAY_INTERIM) - - bot.run() diff --git a/vote_cog.py b/vote_cog.py new file mode 100644 index 0000000000000000000000000000000000000000..399324d10560f803c946e345a9008a7068c08b13 --- /dev/null +++ b/vote_cog.py @@ -0,0 +1,134 @@ +import asyncio +import os +import time + +from twitchio.ext import commands + +import vote_redis + + +@commands.core.cog(name="VoteCog") +class VoteCog: + def __init__(self, bot): + self.bot = bot + self.DELAY_END = int(os.getenv("VOTE_DELAY_END")) + self.DELAY_INTERIM = int(os.getenv("VOTE_DELAY_INTERIM")) + self.MIN_VOTES = int(os.getenv("VOTE_MIN_VOTES")) + self.COLOR = os.getenv("VOTE_COLOR") + self.PLUS = os.getenv("VOTE_PLUS") + self.MINUS = os.getenv("VOTE_MINUS") + self.NEUTRAL = os.getenv("VOTE_NEUTRAL") + self.vote_end_task = None + self.vote_interim_task = None + self.vote_task_new = None + self.votes = {} + self.bot.add_listener(self.event_message) + self.redis = vote_redis.VoteRedis() + + async def notify_vote_result(self, message, final_result=False): + votes_list = self.get_votes() + + output = f'{self.PLUS} {votes_list[0][0]} ({votes_list[0][1]}%), ' \ + f'{self.NEUTRAL} {votes_list[1][0]} ({votes_list[1][1]}%), ' \ + f'{self.MINUS} {votes_list[2][0]} ({votes_list[2][1]}%) | ' + output += f'Endergebnis' if final_result else f'Zwischenergebnis' + output += f' mit insgesamt {len(self.votes)} abgegebenen Stimmen' + + await self.bot.send_me(message, output, self.COLOR) + + async def vote_end_voting(self, channel): + """ End a currently open voting """ + + # Wait for the initial VOTE_DELAY_END seconds + await asyncio.sleep(self.DELAY_END) + + # Check every second, if the delay has finished, because since VOTE_DELAY_END seconds there was no vote, + # that extended the voting time + while int(self.vote_end_task.get_name()) + self.DELAY_END >= time.time(): + await asyncio.sleep(1) + + if len(self.votes) >= self.MIN_VOTES: + await self.notify_vote_result(channel, final_result=True) + + self.votes.clear() + self.vote_task_new = asyncio.create_task(self.vote_block_votes()) + self.update_redis() + + async def vote_interim_voting(self, channel): + """ End a currently open voting """ + + await asyncio.sleep(self.DELAY_INTERIM) + if not self.vote_end_task.done() and len(self.votes) >= self.MIN_VOTES: + await self.notify_vote_result(channel) + self.vote_interim_task = asyncio.create_task( + self.vote_interim_voting(self.bot.get_channel(self.bot.CHANNEL))) + + async def vote_block_votes(self): + """ Just do nothing but sleep for VOTE_DELAY_INTERIM seconds """ + + await asyncio.sleep(self.DELAY_INTERIM) + + def get_votes(self): + """analyzes the votes-dict and counts the votes""" + plus = 0 + minus = 0 + neutral = 0 + + # count values in dict + for x in self.votes.values(): + if x == 'neutral': + neutral += 1 + elif x == 'plus': + plus += 1 + elif x == 'minus': + minus += 1 + + return [[plus, self.bot.get_percentage(plus, len(self.votes))], + [neutral, self.bot.get_percentage(neutral, len(self.votes))], + [minus, self.bot.get_percentage(minus, len(self.votes))]] + + async def event_message(self, message): + # make sure the bot ignores itself and nightbot + if message.author.name.lower() in [self.NICK.lower(), 'nightbot']: + return + + # check if message is a vote + msg = message.content + if msg[:2] == '+-' or msg[:2] == '-+' or msg[:3] == '-/+' or msg[:3] == '+/-' or msg[:len( + self.NEUTRAL)] == self.NEUTRAL: + self.add_vote(message, 'neutral') + elif msg[:1] == '+' or msg[:len(self.PLUS)] == self.PLUS: + self.add_vote(message, 'plus') + elif msg[:1] == '-' or msg[:len(self.MINUS)] == self.MINUS: + self.add_vote(message, 'minus') + + def add_vote(self, ctx, votetype): + """adds votes to the votes-dict and sets timestamps""" + + # Delay between two votes is not yet finished. So votes are not counted. + if self.vote_task_new and not self.vote_task_new.done(): + return + + if len(self.votes) == 0: + self.vote_end_task = asyncio.create_task(self.vote_end_voting(self.bot.get_channel(self.bot.CHANNEL))) + self.vote_interim_task = asyncio.create_task( + self.vote_interim_voting(self.bot.get_channel(self.bot.CHANNEL))) + + # should vote extend voting? + if ctx.author.name not in self.votes or self.votes[ctx.author.name] != votetype: + self.vote_end_task.set_name(int(time.time())) + + # add vote to dict + self.votes[ctx.author.name] = votetype + self.update_redis() + + def update_redis(self): + """analyzes the votes-dict and counts the votes""" + + if self.redis.is_connected: + votes = self.get_votes() + p = self.redis.pipeline() # start transaction + p.set('plus', votes[0][0]) + p.set('neutral', votes[1][0]) + p.set('minus', votes[2][0]) + p.execute() # transaction end diff --git a/vote_redis.py b/vote_redis.py new file mode 100644 index 0000000000000000000000000000000000000000..13f4d7f770d02311bee5d2aea8f8aaef991232ab --- /dev/null +++ b/vote_redis.py @@ -0,0 +1,34 @@ +import os + +import redis + + +class VoteRedis(redis.Redis): + def __init__(self): + self.host = os.getenv("REDIS_HOST") + self.port = os.getenv("REDIS_PORT") + self.db = os.getenv("REDIS_DB") + self.password = os.getenv("REDIS_PW") + super().__init__(host=self.host, port=self.port, db=self.db, password=self.password) + + try: + self.ping() + print('Redis: Connected!') + self.is_connected = True + self.init_db() + except Exception: + print('A connection to the Redis server could not be established. Redis querys are avoided.') + self.is_connected = False + + def init_db(self): + # update constants in Redis-DB + self.set('voteMin', os.getenv("VOTE_MIN_VOTES")) + self.set('voteDelayEnd', os.getenv("VOTE_DELAY_END")) + self.set('voteDelayInter', os.getenv("VOTE_DELAY_INTERIM")) + # + # reset DB + p = self.pipeline() # start transaction + p.set('plus', 0) + p.set('neutral', 0) + p.set('minus', 0) + p.execute() # transaction end