async def announce_time(self, msg, time: int = None): try: # Make sure we receive a positive number if int(time) < 1: raise ValueError self.delay = int(time) updated = session.query(Settings).filter(Settings.key == "announcement_delay").update({"value": self.delay}) if not updated: # Insert if it wasn't updated, because it didn't exist. insert = Settings(key="announcement_delay", value=self.delay) session.add(insert) session.commit() self.restart_task() await msg.reply(f"{bot.msg_prefix}New announce time is {time} seconds") except ValueError: await msg.reply(f"{bot.msg_prefix}Invalid time, please use an integer in seconds") except TypeError: await msg.reply(f"{bot.msg_prefix} Current announce time is {self.delay} seconds.") except Exception as e: print(type(e), e)
async def ignore_add(self, msg: Message, pattern: str): # Check if the regex is valid, return error if not. try: re.compile(pattern) except re.error: await msg.reply(f"{bot.msg_prefix}Invalid regex pattern.") return # Will not support a space in the pattern, but that doesn't matter because usernames can't have spaces query = session.query(IgnoreList).filter( IgnoreList.pattern == pattern).one_or_none() if query is None: # Pattern didn't exist, so add it. insert = IgnoreList(pattern=pattern) session.add(insert) session.commit() session.refresh(insert) await msg.reply( f"{bot.msg_prefix}I will now ignore links from {pattern}") bot.ignore_list_patterns[insert.id] = pattern else: await msg.reply( f"{bot.msg_prefix}That pattern is already in the database.")
async def wig_add(self, msg: Message, *args): """Adds a new wig to the database""" wig_name = " ".join(map(str, args)) new_wig = Wigs(wig_name=wig_name, enabled=1) session.add(new_wig) session.commit() session.refresh(new_wig) await msg.reply(f"{bot.msg_prefix}New wig added as id {new_wig.id}.")
def save_topic(topic: dict): topic_json = json.dumps(topic) rows_affected = session.query(Settings).filter( Settings.key == "topic").update({"value": topic_json}) if not rows_affected: ins = Settings(key="topic", value=topic_json) session.add(ins) session.commit() return True
async def shoutout_msg(self, msg: Message, *args): message = "" for word in args: message += f"{word} " successful = session.query(Settings.value).filter(Settings.key == "shoutout_msg").update({"value": message}) if not successful: insert = Settings(key="shoutout_msg", value=message) session.add(insert) session.commit() await msg.reply(f"{bot.msg_prefix} Message updated.")
def __init__(self): query = session.query(Settings.value).filter(Settings.key == "announcement_delay").one_or_none() self.delay = int(query[0]) if query is not None else 900 self.chan = cfg.channels[0] self.next_run = datetime.min self.timezone = pytz.timezone("America/Chicago") self.channel_active = False self.announcements_sleeping = True self.sleep_override = False self.current_category_setting = "announcement_category" self.enable = session.query(Settings.value).filter(Settings.key == "announcement_enabled").one_or_none() if self.enable is None: insert = Settings(key="announcement_enabled", value=True) session.add(insert)
async def set_topic(self, msg, *args): topic = "" for arg in args: topic += f"{arg} " rows_affected = session.query(Settings).filter(Settings.key == "topic").update({"value": topic}) if not rows_affected: ins = Settings(key="topic", value=topic) session.add(ins) session.commit() await msg.reply(f"{bot.msg_prefix}Topic set.")
async def announce_category_add(self, msg, *args): """Add a category""" cat_name = " ".join(map(str, args)) exists = session.query(AnnouncementCategories).filter(AnnouncementCategories.name == cat_name).count() if exists: await msg.reply(f"{bot.msg_prefix}Duplicate category name") return # Insert the new category into the database new_announcement = AnnouncementCategories(name=cat_name) session.add(new_announcement) session.commit() # Refresh to pull the ID inserted as session.refresh(new_announcement) await msg.reply(f"{bot.msg_prefix}{new_announcement.name} added as id {new_announcement.id}")
def get_cooldown(self, feed: str): """ Returns the cooldown Loads it from the database, or returns 0 if one is not set. """ if feed not in self.mqtt_cooldown: q = session.query(Settings.value).filter(Settings.key == f"mqtt_cooldown_{feed}").one_or_none() if q is None: # Value wasn't in the database, lets insert it. insert = Settings(key=f"mqtt_cooldown_{feed}", value=0) session.add(insert) self.mqtt_cooldown[feed] = 0 else: self.mqtt_cooldown[feed] = int(q[0]) return self.mqtt_cooldown[feed]
def set_cooldown(self, feed: str, cooldown: int) -> None: """ Sets the MQTT cooldown Updates or inserts the value into the database Exception handling should be done in the calling function """ q = session.query(Settings.id).filter(Settings.key == f"mqtt_cooldown_{feed}").one_or_none() if q is None: # Value wasn't in the database, lets insert it. insert = Settings(key=f"mqtt_cooldown_{feed}", value=cooldown) session.add(insert) self.mqtt_cooldown[feed] = cooldown else: session.query(Settings).filter(Settings.key == f"mqtt_cooldown_{feed}").update({"value": cooldown}) self.mqtt_cooldown[feed] = cooldown session.commit()
async def on_pubsub_bits(self, raw: PubSubData, data) -> None: """Send MQTT push when a us4er redeems bits""" """ print(raw.message_dict) {'data': {'user_name': 'tisboyo', 'channel_name': 'baldengineer', 'user_id': '461713054', 'channel_id': '125957551', 'time': '2020-09-20T02:48:34.819702158Z', 'chat_message': 'cheer1', 'bits_used': 1, 'total_bits_used': 2, 'is_anonymous': False, 'context': 'cheer', 'badge_entitlement': None}, 'version': '1.0', 'message_type': 'bits_event', 'message_id': '5a2da2f4-a6b5-5d23-b7cc-839a3ea5140c' } """ # bits = await load_data("bits") # bits[datetime.now().isoformat()] = raw.message_dict # await save_data("bits", bits) user_id = raw.message_dict["data"]["user_id"] server_id = raw.message_dict["data"]["channel_id"] bits_used = raw.message_dict["data"]["bits_used"] rows_affected = (session.query(Users).filter( Users.user_id == user_id, Users.channel == server_id).update({ "cheers": Users.cheers + bits_used, "last_message": datetime.now() })) # If the user doesn't exist, insert them if not rows_affected: user_object = Users( user_id=user_id, channel=server_id, user=raw.message_dict["data"]["user_name"], message_count=1, cheers=bits_used, ) session.add(user_object) session.commit()
async def announce_add(self, msg, *args): message = "" # Build the message for arg in args: message += f"{arg} " print(f"Adding to announce: {message}", end="") announcement_object = Announcements(text=message) session.add(announcement_object) session.commit() session.refresh(announcement_object) id = announcement_object.id print(f"...done, {id=}") await msg.reply(f"{bot.msg_prefix}Added announce {id=}")
async def on_user_join(self, user_name: str, channel: Channel, commit: bool = True) -> None: """User has joined the channel""" # get_user_info is cached by the bot, so only one actual request per user is sent to the api user_id = await get_user_id(user_name) channel_id = await get_user_id(channel.name) # Don't overwrite if the user is already here, happens if they join twice if (user_name, user_id) not in self.user_joined.keys(): in_database = session.query(Users).filter(Users.user_id == user_id, Users.channel == channel_id).one_or_none() # If the user isn't in the database insert them so we can find them later if not in_database: user_object = Users(user_id=user_id, channel=channel_id, user=user_name) session.add(user_object) if commit: session.commit() self.user_joined[(user_name, user_id)] = datetime.now() print(f"{datetime.now().isoformat()}: {user_name} has joined #{channel.name}, in database: {bool(in_database)}")
async def user_exit(self, users: Union[str, frozenset], channel: Channel): now = datetime.now() # If only a single user is passed, convert it to a frozenset as if a list of users was passed if isinstance(users, str): users = frozenset([users]) for user_name in users: # Build a tuple to compare user = (user_name, await get_user_id(user_name)) # Check to make sure the user is still in the user_joined to prevent errors if user in self.user_joined.keys(): # Calculate the time in the channel for this session joined: datetime = self.user_joined[user] parted = now time_this_session = parted - joined _, user_id = user channel_id = int(await get_user_id(channel.name)) # Update the database # Adding time objects is not possible with sqlite, so using an integer to track seconds rows_affected = ( session.query(Users) .filter(Users.user_id == user_id, Users.channel == channel_id) .update({"time_in_channel": Users.time_in_channel + time_this_session.seconds}) ) if not rows_affected: user_object = Users( user_id=user_id, channel=channel_id, user=user_name, time_in_channel=time_this_session.seconds ) session.add(user_object) # Remove the user from the user_joined key, since they have exited the channel del self.user_joined[user] # Commit the changes session.commit()
async def on_raw_message(self, msg: Message) -> None: """Increment the user message counter""" # Make sure the user actually sent a message,and it's not a whisper. if not msg.is_user_message or msg.is_whisper: return user_id = msg.tags.user_id server_id = msg.tags.room_id rows_affected = (session.query(Users).filter( Users.user_id == user_id, Users.channel == server_id).update({ "message_count": Users.message_count + 1, "last_message": datetime.now() })) # If the user doesn't exist, insert them if not rows_affected: user_object = Users(user_id=user_id, channel=server_id, user=msg.author, message_count=1) session.add(user_object) # Added for #155 # If we have had a raid in the last 60 seconds, don't send anything for the new users, # even thought they got inserted into the database. if (self.last_raid + timedelta(seconds=60)) < datetime.now(): if not bot.user_ignored(msg.author): await bot.MQTT.send( first_time_chatter_topic, dumps({ "author": msg.author, "timestamp": str(datetime.now()) })) print(f"New user {msg.author} sent to MQTT.") session.commit()
async def on_raw_message(self, msg: Message) -> None: """Increment the user message counter""" # Make sure the user actually sent a message,and it's not a whisper. if not msg.is_user_message or msg.is_whisper: return user_id = msg.tags.user_id server_id = msg.tags.room_id rows_affected = ( session.query(Users) .filter(Users.user_id == user_id, Users.channel == server_id) .update({"message_count": Users.message_count + 1, "last_message": datetime.now()}) ) # If the user doesn't exist, insert them if not rows_affected: user_object = Users(user_id=user_id, channel=server_id, user=msg.author, message_count=1) session.add(user_object) session.commit()
async def lost(self, msg: Message, *args): # Check if user is on ignore list if bot.user_ignored(str(msg.author)): return # Increase both counts self.lost_count["session"] += 1 self.lost_count["total"] += 1 rows_affected = (session.query(Settings).filter( Settings.key == "lost_count").update( {"value": json.dumps(self.lost_count)})) if not rows_affected: ins = Settings(key="lost_count", value=json.dumps(self.lost_count)) session.add(ins) session.commit() await msg.reply( f"@baldengineer has lost {self.lost_count['session']} things so far today, {self.lost_count['total']} total." )
async def trivia_q(self, msg: Message, question_num: int, answer_num: int): question_num, answer_num = int(question_num), int( answer_num) # Framework only passes strings, convert it. channel_id = await get_user_id(msg.channel.name) print( f"Trivia question #{question_num}, Answer: {ascii_lowercase[answer_num-1].upper()}" ) # Don't send a question while another is active, queuing the next one. while self.question_active or self.almost_active: await sleep(0.5) self.almost_active = True # Resets in reset_question # Don't set the question as active until it's sent to chat. self.current_question = question_num self.current_answer = answer_num result = session.query(TriviaQuestions).filter_by( id=self.current_question).one_or_none() self.answer_text = ascii_lowercase[answer_num - 1] if result is not None: # Send a welcome message and instructions if this is the first question of the session if not self.trivia_active: await msg.reply(f"{bot.msg_prefix}Trivia incoming...") await msg.reply( f"{self.msg_prefix}To play trivia, answer with a single letter message of your answer(case insensitive)." ) self.trivia_active = True async def sleep_if_active(time: int) -> bool: """Sleeps if trivia is active, returns True if trivia is still active at the end of the sleep""" # If trivia is not active, we will speed past sending the messages to chat and finalize answers for _ in range(time * 2): if self.trivia_active: await sleep(0.5) else: return False return True # Mark question as active self.question_active = True # Resets in reset_question # Sleep early to let the watchers catch up to the stream if await sleep_if_active(7 + self.manual_delay): # Send the question to chat await msg.reply(f"{self.msg_prefix}{result.text}") if await sleep_if_active(40): await msg.reply(f"{self.msg_prefix}15 seconds remaining") if await sleep_if_active(10): await msg.reply(f"{self.msg_prefix}Final answers...") # One last sleep to let people get final answers in. await sleep_if_active(5) # Insert/Update the participants that got a correct answer correctly_answered = list() for participant in self.session: if (self.session[participant]["q"].get( self.current_question, False) and participant in self. current_question_participant # See note in reset_session ): correctly_answered.append(participant) # Get the user id, but sometimes this fails and returns a -1, if it's -1 try again up to 3 times. # If it still fails, send a message to the console and do not insert into the database. user_id = -1 get_user_id_count = 0 while user_id == -1: user_id = await get_user_id(participant) get_user_id_count += 1 if get_user_id_count >= 3: print( f"Unable to fetch user id for {participant} after 3 attempts." ) break query = (session.query(TriviaResults).filter( TriviaResults.user_id == user_id, TriviaResults.channel == channel_id).one_or_none() ) # Grab the current row if query: # Update it query.trivia_points += 1 # type: ignore query.questions_answered_correctly[ self.current_question] = True else: # Insert if the row didn't exist. query = TriviaResults( user_id=user_id, channel=channel_id, questions_answered_correctly={ self.current_question: True }, trivia_points=1, ) # Update the query session.add(query) session.commit() if len(correctly_answered) > 0: correctly_answered = ", ".join(correctly_answered) # Check the total length, if over 500 characters shorten it. message = f"{self.msg_prefix} Congrats to {correctly_answered} for the correct answer." if len(message) > 500: count = len(correctly_answered.split(", ")) message = f"{self.msg_prefix} Wow! {count} of you got it right! Congrats" await msg.reply(message) self.reset_question()
async def on_pubsub_subscription(self, raw: PubSubData, data): """Twitch subscription event""" """ print(raw.message_dict) {'benefit_end_month': 0, 'user_name': 'tisboyo', 'display_name': 'tisboyo', 'channel_name': 'baldengineer', 'user_id': '461713054', 'channel_id': '125957551', 'time': '2020-09-27T20:01:35.385520498Z', 'sub_message': {'message': '', 'emotes': None}, 'sub_plan': 'Prime', 'sub_plan_name': '104 Capacitor Level Sub', 'months': 0, 'cumulative_months': 4, 'streak_months': 4, 'context': 'resub', 'is_gift': False, 'multi_month_duration': 0} print(raw.raw_data) {'type': 'MESSAGE', 'data': {'topic': 'channel-subscribe-events-v1.125957551', 'message': '{"benefit_end_month":0,"user_name":"tisboyo","display_name":"tisboyo","channel_name":"baldengineer", "user_id":"461713054","channel_id":"125957551","time":"2020-09-27T20:01:35.385520498Z", "sub_message":{"message":"","emotes":null},"sub_plan":"Prime","sub_plan_name":"104 Capacitor Level Sub", "months":0,"cumulative_months":4,"streak_months":4,"context":"resub","is_gift":false,"multi_month_duration":0}'}} """ if raw.message_dict["is_gift"]: # A gift subscription user_id = raw.message_dict["recipient_id"] # Add count to the user for gifting a sub gifter_id = int(raw.message_dict["user_id"]) channel_id = int(raw.message_dict["channel_id"]) session.query(Users).filter(Users.user_id == gifter_id, Users.channel == channel_id).update({ Users.subs_gifted: Users.subs_gifted + 1 }) else: # A regular subscription user_id = raw.message_dict["user_id"] cumulative_months = raw.message_dict[ "cumulative_months"] if "cumulative_months" in raw.message_dict else 0 streak_months = raw.message_dict[ "streak_months"] if "streak_months" in raw.message_dict else 0 # In both gift and regular sub server_id = raw.message_dict["channel_id"] sub_level = raw.message_dict["sub_plan"] rows_affected = (session.query(Subscriptions).filter( Subscriptions.user_id == user_id, Subscriptions.channel == server_id).update({ "subscription_level": sub_level, "cumulative_months": cumulative_months, "streak_months": streak_months })) # Add row if one wasn't updated. if not rows_affected: user_object = Subscriptions( user_id=user_id, channel=server_id, subscription_level=sub_level, cumulative_months=cumulative_months, streak_months=streak_months, ) session.add(user_object) session.commit()
async def ignore_user(self, msg: Message, user: str): insert = LinksToDiscordIgnoreList(username=user.lower()) session.add(insert) session.commit() await msg.reply(f"{bot.msg_prefix}I will now ignore links from {user}")
# Verify the category is a valid one if (category := self.get_announcements_category(category_id)) is None: await msg.reply(f"{bot.msg_prefix}Invalid category id.") return # TODO Change to just an update, and do a alembic revision to insert a default after #79 is complete. updated = ( session.query(Settings) .filter(Settings.key == self.current_category_setting) .update({Settings.value: category_id}) ) if not updated: # Insert the key insert = Settings(key=self.current_category_setting, value=category_id) session.add(insert) session.commit() await msg.reply(f"{bot.msg_prefix}Active announcement category is now {category.name}") def get_announcements_category(self, category_id: int) -> AnnouncementCategories or None: """ Returns an AnnouncementCategories object or None Use: if (category := self.get_announcements_category(category_id)) is None: """ category_id = int(category_id) category = session.query(AnnouncementCategories).filter(AnnouncementCategories.id == category_id).first() # Have to check if a valid category was returned # to prevent a NoneType error if an invalid one is found if category is None: