diff --git a/subathon/component.py b/subathon/component.py index 8f7ae1a..df31e36 100644 --- a/subathon/component.py +++ b/subathon/component.py @@ -13,24 +13,30 @@ from meta.sockets import Channel, register_channel from utils.lib import utc_now, strfdelta from . import logger -from .data import SubathonData, Subathon, RunningSubathon, SubathonContribution, SubathonGoal +from .data import ( + SubathonData, + Subathon, + RunningSubathon, + SubathonContribution, + SubathonGoal, +) from .subathon import ActiveSubathon, SubathonRegistry from .channel import SubathonPayload, prepare_subathon, TimerChannel class SubathonComponent(cmds.Component): """ - !subathon + !subathon !subathon setup [cap] - !subathon stop + !subathon stop - !subathon link - !subathon pause + !subathon link + !subathon pause !subathon resume !subathon adjust [@user] - !subathon config + !subathon config !subathon config name [new name] !subathon config cap [new cap] !subathon config scores [ ] @@ -38,17 +44,18 @@ class SubathonComponent(cmds.Component): !subathon lb - !goals - !goals remaining + !goals + !goals remaining !goals add !goals remove - + Command: Permissions: Description: Examples: """ + # TODO: Add explicit dependencies and version checks # for profile and event tracker modules @@ -58,7 +65,7 @@ class SubathonComponent(cmds.Component): self.subathons = SubathonRegistry(self.data, bot.profiles.profiles) self.channel = TimerChannel(self.bot.profiles.profiles, self.subathons) - register_channel('SubTimer', self.channel) + register_channel("SubTimer", self.channel) # ----- API ----- async def component_load(self): @@ -89,8 +96,8 @@ class SubathonComponent(cmds.Component): # Should probably wait on a central messaging slow-lock bucketed by channel instead await asyncio.sleep(1) await channel.send_message( - f"We have reached Goal #{i+1}: {goal.description} !! Thank you everyone for your support <3", - sender=self.bot.bot_id, + f"We have reached Goal #{i + 1}: {goal.description} !! Thank you everyone for your support <3", + sender=self.bot.bot_id, ) await goal.update(notified=True) @@ -98,20 +105,24 @@ class SubathonComponent(cmds.Component): # Blerp handler @cmds.Component.listener() async def event_message(self, message: twitchio.ChatMessage): - if message.chatter.id not in ('253326823', '1361913054'): + if message.chatter.id not in ("253326823", "1361913054"): return - if 'bits' not in message.text: + if "bits" not in message.text: return community = await self.bot.profiles.fetch_community(message.broadcaster) cid = community.communityid - if (active := await self.get_active_subathon(cid)) is not None and not await active.check_finished(): + if ( + active := await self.get_active_subathon(cid) + ) is not None and not await active.check_finished(): # Extract count and user match = re.match(r"(?P\w+) used (?P\d+)", message.text) if not match: - match = re.match(r"!For (?P\d+) bits, (?P\w+)", message.text) + match = re.match( + r"!For (?P\d+) bits, (?P\w+)", message.text + ) if match: - amount = int(match['amount']) - name = match['name'] + amount = int(match["amount"]) + name = match["name"] # This is for giving the contribution to the calling user # user = await self.bot.fetch_user(login=name.lower()) user = await message.chatter.user() @@ -128,12 +139,15 @@ class SubathonComponent(cmds.Component): @cmds.Component.listener() async def event_safe_bits_use(self, payload): event_row, detail_row, bits_payload = payload - if (active := await self.get_active_subathon(event_row['communityid'])) is not None and not await active.check_finished(): + if ( + active := await self.get_active_subathon(event_row["communityid"]) + ) is not None and not await active.check_finished(): # In an active subathon - pid = event_row['profileid'] - uid = event_row['user_id'] - score = detail_row['bits'] * active.subathondata.bit_score - await active.add_contribution(pid, score, event_row['event_id']) + pid = event_row["profileid"] + uid = event_row["user_id"] + score = detail_row["bits"] * active.subathondata.bit_score + logger.info(f"Adding subathon contribution from bits payload {payload}") + await active.add_contribution(pid, score, event_row["event_id"]) # Send message to channel sec = active.get_score_time(score) @@ -143,7 +157,7 @@ class SubathonComponent(cmds.Component): else: added = f"{sec} seconds" name = bits_payload.user.name - pl = 's' if bits_payload.bits != 1 else '' + pl = "s" if bits_payload.bits != 1 else "" contrib_str = f"{name} contributed {score} point{pl}" if not await active.check_cap(): @@ -152,8 +166,7 @@ class SubathonComponent(cmds.Component): contrib_str += " towards our subathon! Thank you <3" await bits_payload.broadcaster.send_message( - contrib_str, - sender=self.bot.bot_id + contrib_str, sender=self.bot.bot_id ) await self.dispatch_update(active) await self.goalcheck(active, bits_payload.broadcaster) @@ -166,16 +179,18 @@ class SubathonComponent(cmds.Component): # Ignore gifted here return - if (active := await self.get_active_subathon(event_row['communityid'])) is not None and not await active.check_finished(): + if ( + active := await self.get_active_subathon(event_row["communityid"]) + ) is not None and not await active.check_finished(): data = active.subathondata # In an active subathon - pid = event_row['profileid'] + pid = event_row["profileid"] tier = int(sub_payload.tier) if tier == 1000: - mult = data.sub1_score + mult = data.sub1_score elif tier == 2000: - mult = data.sub2_score + mult = data.sub2_score elif tier == 3000: mult = data.sub3_score else: @@ -183,13 +198,16 @@ class SubathonComponent(cmds.Component): score = mult * 1 - await active.add_contribution(pid, score, event_row['event_id']) + logger.info( + f"Adding subathon contribution from subscription payload {payload}" + ) + await active.add_contribution(pid, score, event_row["event_id"]) # Send message to channel added_min = int(active.get_score_time(score) // 60) added = f"{added_min} minutes" name = sub_payload.user.name - pl = 's' if score > 1 else '' + pl = "s" if score > 1 else "" contrib_str = f"{name} contributed {score} point{pl}" if not await active.check_cap(): @@ -198,8 +216,7 @@ class SubathonComponent(cmds.Component): contrib_str += " towards our subathon! Thank you <3" await sub_payload.broadcaster.send_message( - contrib_str, - sender=self.bot.bot_id + contrib_str, sender=self.bot.bot_id ) await self.dispatch_update(active) # Check goals @@ -209,16 +226,18 @@ class SubathonComponent(cmds.Component): async def event_safe_subscription_gift(self, payload): event_row, detail_row, gift_payload = payload - if (active := await self.get_active_subathon(event_row['communityid'])) is not None and not await active.check_finished(): + if ( + active := await self.get_active_subathon(event_row["communityid"]) + ) is not None and not await active.check_finished(): data = active.subathondata # In an active subathon - pid = event_row['profileid'] + pid = event_row["profileid"] tier = int(gift_payload.tier) if tier == 1000: - mult = data.sub1_score + mult = data.sub1_score elif tier == 2000: - mult = data.sub2_score + mult = data.sub2_score elif tier == 3000: mult = data.sub3_score else: @@ -226,12 +245,15 @@ class SubathonComponent(cmds.Component): score = mult * gift_payload.total - await active.add_contribution(pid, score, event_row['event_id']) + logger.info( + f"Adding subathon contribution from subscription gift payload {payload}" + ) + await active.add_contribution(pid, score, event_row["event_id"]) # Send message to channel added_min = int(active.get_score_time(score) // 60) added = f"{added_min} minutes" - name = gift_payload.user.name if gift_payload.user else 'Anonymous' + name = gift_payload.user.name if gift_payload.user else "Anonymous" contrib_str = f"{name} contributed {score} points" if not await active.check_cap(): @@ -240,8 +262,7 @@ class SubathonComponent(cmds.Component): contrib_str += " towards our subathon! Thank you <3" await gift_payload.broadcaster.send_message( - contrib_str, - sender=self.bot.bot_id + contrib_str, sender=self.bot.bot_id ) await self.dispatch_update(active) # Check goals @@ -251,16 +272,18 @@ class SubathonComponent(cmds.Component): async def event_safe_subscription_message(self, payload): event_row, detail_row, sub_payload = payload - if (active := await self.get_active_subathon(event_row['communityid'])) is not None and not await active.check_finished(): + if ( + active := await self.get_active_subathon(event_row["communityid"]) + ) is not None and not await active.check_finished(): data = active.subathondata # In an active subathon - pid = event_row['profileid'] + pid = event_row["profileid"] tier = int(sub_payload.tier) if tier == 1000: - mult = data.sub1_score + mult = data.sub1_score elif tier == 2000: - mult = data.sub2_score + mult = data.sub2_score elif tier == 3000: mult = data.sub3_score else: @@ -268,13 +291,16 @@ class SubathonComponent(cmds.Component): score = mult * 1 - await active.add_contribution(pid, score, event_row['event_id']) + logger.info( + f"Adding subathon contribution from subscription message payload {payload}" + ) + await active.add_contribution(pid, score, event_row["event_id"]) # Send message to channel added_min = int(active.get_score_time(score) // 60) added = f"{added_min} minutes" name = sub_payload.user.name - pl = 's' if score > 1 else '' + pl = "s" if score > 1 else "" contrib_str = f"{name} contributed {score} points{pl}" if not await active.check_cap(): @@ -283,8 +309,7 @@ class SubathonComponent(cmds.Component): contrib_str += " towards our subathon! Thank you <3" await sub_payload.broadcaster.send_message( - contrib_str, - sender=self.bot.bot_id + contrib_str, sender=self.bot.bot_id ) await self.dispatch_update(active) # Check goals @@ -297,17 +322,20 @@ class SubathonComponent(cmds.Component): cid = community.communityid if (active := await self.get_active_subathon(cid)) is not None: if active.running: + logger.info( + f"Automatically paused suabathon {active.subathondata!r} from stream offline." + ) await active.pause() if not await active.check_finished(): await payload.broadcaster.send_message( - "Paused the subathon timer because the stream went offline!", - sender=self.bot.bot_id - ) + "Paused the subathon timer because the stream went offline!", + sender=self.bot.bot_id, + ) await self.dispatch_update(active) # ----- Commands ----- - @cmds.group(name='subathon', aliases=['studython'], invoke_fallback=True) + @cmds.group(name="subathon", aliases=["studython"], invoke_fallback=True) async def group_subathon(self, ctx: cmds.Context): community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid @@ -325,31 +353,37 @@ class SubathonComponent(cmds.Component): if secs > 0: remaining = strfdelta(timedelta(seconds=secs)) - text = ( - f"{active.name} running for {duration}! {score} points recieved, {goalstr}, and {remaining} left on the timer" - ) + text = f"{active.name} running for {duration}! {score} points recieved, {goalstr}, and {remaining} left on the timer" else: - text = ( - f"{active.name} completed after {duration} with a total of {score} points, and {goalstr}!" - ) + text = f"{active.name} completed after {duration} with a total of {score} points, and {goalstr}!" await ctx.reply(text) else: await ctx.reply("No active subathon running!") - # subathon start + # subathon start # TODO: Usage line on command error - @group_subathon.command(name='setup', alias='start') + @group_subathon.command(name="setup", alias="start") @cmds.is_broadcaster() - async def cmd_setup(self, ctx: cmds.Context, initial_hours: float, sub1: float, sub2: float, sub3: float, bit: float, timescore: int, timecap: Optional[int]=None): + async def cmd_setup( + self, + ctx: cmds.Context, + initial_hours: float, + sub1: float, + sub2: float, + sub3: float, + bit: float, + timescore: int, + timecap: Optional[int] = None, + ): """ Creates a new subathon. USAGE: {prefix}subathon setup [timecap] Arguments: initial_hours = number of hours to start the timer with - sub1_points = points per T1 sub + sub1_points = points per T1 sub sub2_points = points per T2 sub - sub3_points = points per T3 sub + sub3_points = points per T3 sub bit_points = points per bit timepoints = seconds to be added to the timer per point timecap (optional) = number of hours to cap the timer at @@ -357,11 +391,13 @@ class SubathonComponent(cmds.Component): community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid if (active := await self.get_active_subathon(cid)) is not None: - await ctx.reply("There is already an active subathon running! Use !subathon stop to stop it!") + await ctx.reply( + "There is already an active subathon running! Use !subathon stop to stop it!" + ) return initial_time = initial_hours * 60 * 60 timecap_seconds = timecap * 60 * 60 if timecap else None - + subdata = await Subathon.create( name="Subathon", communityid=cid, @@ -371,9 +407,9 @@ class SubathonComponent(cmds.Component): sub3_score=sub3, bit_score=bit, score_time=timescore, - timecap=timecap_seconds + timecap=timecap_seconds, ) - base_timer_url = self.bot.config.subathon['timer_url'] + base_timer_url = self.bot.config.subathon["timer_url"] timer_link = f"{base_timer_url}?community={cid}" await ctx.reply( f"Setup your subathon! " @@ -385,7 +421,7 @@ class SubathonComponent(cmds.Component): await self.dispatch_update(active) # subathon stop - @group_subathon.command(name='stop') + @group_subathon.command(name="stop") @cmds.is_broadcaster() async def cmd_stop(self, ctx: cmds.Context): community = await self.bot.profiles.fetch_community(ctx.broadcaster) @@ -406,22 +442,20 @@ class SubathonComponent(cmds.Component): await ctx.reply("No active subathon to stop.") # subathon link - @group_subathon.command(name='link') + @group_subathon.command(name="link") @cmds.is_moderator() async def cmd_link(self, ctx: cmds.Context): community = await self.bot.profiles.fetch_community(ctx.broadcaster) cid = community.communityid if (active := await self.get_active_subathon(cid)) is not None: - base_timer_url = self.bot.config.subathon['timer_url'] + base_timer_url = self.bot.config.subathon["timer_url"] timer_link = f"{base_timer_url}?community={cid}" - await ctx.reply( - timer_link - ) + await ctx.reply(timer_link) else: await ctx.reply("No active subathon to stop.") - # subathon pause - @group_subathon.command(name='pause') + # subathon pause + @group_subathon.command(name="pause") @cmds.is_moderator() async def cmd_pause(self, ctx: cmds.Context): community = await self.bot.profiles.fetch_community(ctx.broadcaster) @@ -436,8 +470,8 @@ class SubathonComponent(cmds.Component): else: await ctx.reply("No active subathon to pause") - # subathon resume - @group_subathon.command(name='resume') + # subathon resume + @group_subathon.command(name="resume") @cmds.is_moderator() async def cmd_resume(self, ctx: cmds.Context): community = await self.bot.profiles.fetch_community(ctx.broadcaster) @@ -455,9 +489,11 @@ class SubathonComponent(cmds.Component): await ctx.reply("No active subathon to resume") # subathon adjust - @group_subathon.command(name='adjust') + @group_subathon.command(name="adjust") @cmds.is_moderator() - async def cmd_subathon_adjust(self, ctx: Context, amount: int, *, user: Optional[twitchio.User] = None): + async def cmd_subathon_adjust( + self, ctx: Context, amount: int, *, user: Optional[twitchio.User] = None + ): """ Directly add or remove points from the subathon. If a user is provided, will adjust their contributed amount. @@ -476,8 +512,8 @@ class SubathonComponent(cmds.Component): if (active := await self.get_active_subathon(cid)) is not None: if user is not None: profile = await self.bot.profiles.fetch_profile(user) - name = user.display_name or profile.nickname or 'Unknown' - pid = profile.profileid + name = user.display_name or profile.nickname or "Unknown" + pid = profile.profileid else: profile = None name = None @@ -524,7 +560,8 @@ class SubathonComponent(cmds.Component): WARNING: This setting is retroactive and should generally not be changed. Accepts an integer. Example: !subathon config timecap 10 """ - @group_subathon.group(name='config', aliases=('option',), invoke_fallback=True) + + @group_subathon.group(name="config", aliases=("option",), invoke_fallback=True) @cmds.is_moderator() async def subathon_config_grp(self, ctx: Context, *, args: Optional[str] = None): community = await self.bot.profiles.fetch_community(ctx.broadcaster) @@ -544,38 +581,35 @@ class SubathonComponent(cmds.Component): sdata = active.subathondata # name - parts.append( - f"name=\"{sdata.name}\"" - ) + parts.append(f'name="{sdata.name}"') # timecamp cap = sdata.timecap or 0 caph = int(cap / 3600) - parts.append( - f"cap={caph} (hours, 0 means no cap)" - ) + parts.append(f"cap={caph} (hours, 0 means no cap)") # scores - scores = map(float, (sdata.sub1_score, sdata.sub2_score, sdata.sub3_score, sdata.bit_score)) - scorestr = ' '.join(map(str, scores)) - parts.append( - f"scores={scorestr} (t1, t2, t3, and 1 bit point scores)" + scores = map( + float, + (sdata.sub1_score, sdata.sub2_score, sdata.sub3_score, sdata.bit_score), ) + scorestr = " ".join(map(str, scores)) + parts.append(f"scores={scorestr} (t1, t2, t3, and 1 bit point scores)") # timescore - parts.append( - f"timescore={sdata.score_time} (seconds per point)" - ) + parts.append(f"timescore={sdata.score_time} (seconds per point)") # Combine - partstr = ' ; '.join(parts) + partstr = " ; ".join(parts) await ctx.reply( f"{partstr} ; Use {ctx.prefix}subathon config