9 Commits

7 changed files with 423 additions and 86 deletions

View File

@@ -5,12 +5,16 @@ from datetime import timedelta
import discord import discord
from discord.ext import commands as cmds from discord.ext import commands as cmds
from discord import app_commands as appcmds
import twitchio import twitchio
from twitchio.ext import commands from twitchio.ext import commands
from data.queries import ORDER from data.queries import ORDER
from meta import LionCog, LionBot, CrocBot from meta import LionCog, LionBot, CrocBot, LionContext
from modules.profiles.community import Community
from modules.profiles.profile import UserProfile
from utils.lib import utc_now from utils.lib import utc_now
from . import logger from . import logger
from .data import CounterData from .data import CounterData
@@ -25,6 +29,11 @@ class PERIOD(Enum):
YEAR = ('this year', 'y', 'year', 'yearly') YEAR = ('this year', 'y', 'year', 'yearly')
class ORIGIN(Enum):
DISCORD = 'discord'
TWITCH = 'twitch'
def counter_cmd_factory( def counter_cmd_factory(
counter: str, counter: str,
response: str, response: str,
@@ -32,10 +41,16 @@ def counter_cmd_factory(
context: Optional[str] = None context: Optional[str] = None
): ):
context = context or f"cmd: {counter}" context = context or f"cmd: {counter}"
async def counter_cmd(cog, ctx: commands.Context, *, args: Optional[str] = None): async def counter_cmd(
userid = int(ctx.author.id) cog,
channelid = int((await ctx.channel.user()).id) ctx: commands.Context | LionContext,
period, start_time = await cog.parse_period(channelid, '', default=default_period) origin: ORIGIN,
author: UserProfile,
community: Community,
args: Optional[str]
):
userid = author.profileid
period, start_time = await cog.parse_period(community, '', default=default_period)
args = (args or '').strip(" 󠀀 ") args = (args or '').strip(" 󠀀 ")
splits = args.split(maxsplit=1) splits = args.split(maxsplit=1)
@@ -69,13 +84,25 @@ def counter_cmd_factory(
) )
) )
async def lb_cmd(cog, ctx: commands.Context, *, args: str = ''): async def lb_cmd(
user = await ctx.channel.user() cog,
await ctx.reply(await cog.formatted_lb(counter, args, int(user.id))) ctx: commands.Context | LionContext,
origin: ORIGIN,
author: UserProfile,
community: Community,
args: Optional[str]
):
await ctx.reply(await cog.formatted_lb(counter, args, community, origin))
async def undo_cmd(cog, ctx: commands.Context): async def undo_cmd(
userid = int(ctx.author.id) cog,
channelid = int((await ctx.channel.user()).id) ctx: commands.Context | LionContext,
origin: ORIGIN,
author: UserProfile,
community: Community,
args: Optional[str]
):
userid = author.profileid
_counter = await cog.fetch_counter(counter) _counter = await cog.fetch_counter(counter)
query = cog.data.CounterEntry.fetch_where( query = cog.data.CounterEntry.fetch_where(
counterid=_counter.counterid, counterid=_counter.counterid,
@@ -113,6 +140,9 @@ class CounterCog(LionCog):
await self.load_counters() await self.load_counters()
self.loaded.set() self.loaded.set()
profiles = self.bot.get_cog('ProfileCog')
profiles.add_profile_migrator(self.migrate_profiles, name='counters')
async def cog_unload(self): async def cog_unload(self):
self._unload_twitch_methods(self.crocbot) self._unload_twitch_methods(self.crocbot)
@@ -124,18 +154,49 @@ class CounterCog(LionCog):
counter.name, counter.name,
row.response row.response
) )
cmds = [] twitch_cmds = []
main_cmd = commands.command(name=row.name)(counter_cb) disc_cmds = []
cmds.append(main_cmd) twitch_cmds.append(
if row.lbname: commands.command(
lb_cmd = commands.command(name=row.lbname)(lb_cb) name=row.name
cmds.append(lb_cmd) )(self.twitch_callback(counter_cb))
if row.undoname: )
undo_cmd = commands.command(name=row.undoname)(undo_cb) disc_cmds.append(
cmds.append(undo_cmd) cmds.hybrid_command(
name=row.name
)(self.discord_callback(counter_cb))
)
for cmd in cmds: if row.lbname:
twitch_cmds.append(
commands.command(
name=row.lbname
)(self.twitch_callback(lb_cb))
)
disc_cmds.append(
cmds.hybrid_command(
name=row.lbname
)(self.discord_callback(lb_cb))
)
if row.undoname:
twitch_cmds.append(
commands.command(
name=row.undoname
)(self.twitch_callback(undo_cb))
)
disc_cmds.append(
cmds.hybrid_command(
name=row.undoname
)(self.discord_callback(undo_cb))
)
for cmd in twitch_cmds:
self.add_twitch_command(self.crocbot, cmd) self.add_twitch_command(self.crocbot, cmd)
for cmd in disc_cmds:
# cmd.cog = self
self.bot.remove_command(cmd.name)
self.bot.add_command(cmd)
print(f"Adding command: {cmd}")
logger.info(f"(Re)Loaded {len(rows)} counter commands!") logger.info(f"(Re)Loaded {len(rows)} counter commands!")
@@ -152,6 +213,87 @@ class CounterCog(LionCog):
f"Loaded {len(self.counters)} counters." f"Loaded {len(self.counters)} counters."
) )
async def migrate_profiles(self, source_profile: UserProfile, target_profile: UserProfile):
"""
Move source profile entries to target profile entries
"""
results = ["(Counters)"]
rows = await self.data.CounterEntry.table.update_where(userid=source_profile.profileid).set(userid=target_profile.profileid)
if rows:
results.append(
f"Migrated {len(rows)} counter entries from source profile."
)
else:
results.append(
"No counter entries to migrate in source profile."
)
return ' '.join(results)
async def user_profile_migration(self):
"""
Manual single-use migration method from the old userid format to the new profileid format.
"""
async with self.bot.db.connection() as conn:
self.bot.db.conn = conn
async with conn.transaction():
entries = await self.data.CounterEntry.fetch_where()
for entry in entries:
if entry.userid > 1000:
# Assume userid is a twitch userid
profile = await UserProfile.fetch_from_twitchid(self.bot, entry.userid)
if not profile:
# Need to create
users = await self.crocbot.fetch_users(ids=[entry.userid])
if not users:
continue
user = users[0]
profile = await UserProfile.create_from_twitch(self.bot, user)
await entry.update(userid=profile.profileid)
logger.info("Completed single-shot user profile migration")
# General API
def twitch_callback(self, callback):
"""
Generate a Twitch command callback from the given general callback.
General callback must be of the form
callback(cog, ctx: GeneralContext, origin: ORIGIN, author: Profile, comm: Community, args: Optional[str])
Return will be a command callback of the form
callback(cog, ctx: Context, *, args: Optional[str] = None)
"""
async def command_callback(cog: CounterCog, ctx: commands.Context, *, args: Optional[str] = None):
profiles = cog.bot.get_cog('ProfileCog')
# Compute author profile
author = await profiles.fetch_profile_twitch(ctx.author)
# Compute community profile
community = await profiles.fetch_community_twitch(await ctx.channel.user())
return await callback(cog, ctx, ORIGIN.TWITCH, author, community, args)
return command_callback
def discord_callback(self, callback):
"""
Generate a Discord command callback from the given general callback.
General callback must be of the form
callback(cog, ctx: GeneralContext, origin: ORIGIN, author: Profile, comm: Community, args: Optional[str])
Return will be a command callback of the form
callback(cog, ctx: LionContext, *, args: Optional[str] = None)
"""
cog = self
async def command_callback(ctx: LionContext, *, args: Optional[str] = None):
profiles = cog.bot.get_cog('ProfileCog')
# Compute author profile
author = await profiles.fetch_profile_discord(ctx.author)
# Compute community profile
community = await profiles.fetch_community_discord(ctx.guild)
return await callback(cog, ctx, ORIGIN.DISCORD, author, community, args)
return command_callback
# Counters API # Counters API
async def fetch_counter(self, counter: str) -> CounterData.Counter: async def fetch_counter(self, counter: str) -> CounterData.Counter:
@@ -218,6 +360,14 @@ class CounterCog(LionCog):
results = await query results = await query
return results[0]['counter_total'] if results else 0 return results[0]['counter_total'] if results else 0
# Manage commands
@commands.command()
async def countermigration(self, ctx: commands.Context, *, args: Optional[str]=None):
if not (ctx.author.is_mod or ctx.author.is_broadcaster):
return
await self.user_profile_migration()
await ctx.reply("Counter userid->profileid migration done.")
# Counters commands # Counters commands
@commands.command() @commands.command()
async def counter(self, ctx: commands.Context, name: str, subcmd: Optional[str], *, args: Optional[str]=None): async def counter(self, ctx: commands.Context, name: str, subcmd: Optional[str], *, args: Optional[str]=None):
@@ -225,6 +375,10 @@ class CounterCog(LionCog):
return return
name = name.lower() name = name.lower()
profiles = self.bot.get_cog('ProfileCog')
author = await profiles.fetch_profile_twitch(ctx.author)
userid = author.profileid
community = await profiles.fetch_community_twitch(await ctx.channel.user())
if subcmd is None or subcmd == 'show': if subcmd is None or subcmd == 'show':
# Show # Show
@@ -241,15 +395,14 @@ class CounterCog(LionCog):
return return
await self.add_to_counter( await self.add_to_counter(
name, name,
int(ctx.author.id), userid,
value, value,
context='cmd: counter add' context='cmd: counter add'
) )
total = await self.totals(name) total = await self.totals(name)
await ctx.reply(f"'{name}' counter is now: {total}") await ctx.reply(f"'{name}' counter is now: {total}")
elif subcmd == 'lb': elif subcmd == 'lb':
user = await ctx.channel.user() lbstr = await self.formatted_lb(name, args or '', community)
lbstr = await self.formatted_lb(name, args or '', int(user.id))
await ctx.reply(lbstr) await ctx.reply(lbstr)
elif subcmd == 'clear': elif subcmd == 'clear':
await self.reset_counter(name) await self.reset_counter(name)
@@ -292,7 +445,7 @@ class CounterCog(LionCog):
else: else:
await ctx.reply(f"Unrecognised subcommand {subcmd}. Supported subcommands: 'show', 'add', 'lb', 'clear', 'alias'.") await ctx.reply(f"Unrecognised subcommand {subcmd}. Supported subcommands: 'show', 'add', 'lb', 'clear', 'alias'.")
async def parse_period(self, userid: int, periodstr: str, default=PERIOD.STREAM): async def parse_period(self, community: Community, periodstr: str, default=PERIOD.STREAM):
if periodstr: if periodstr:
period = next((period for period in PERIOD if periodstr.lower() in period.value), None) period = next((period for period in PERIOD if periodstr.lower() in period.value), None)
if period is None: if period is None:
@@ -306,9 +459,13 @@ class CounterCog(LionCog):
if period is PERIOD.ALL: if period is PERIOD.ALL:
start_time = None start_time = None
elif period is PERIOD.STREAM: elif period is PERIOD.STREAM:
streams = await self.crocbot.fetch_streams(user_ids=[userid]) twitches = await community.twitch_channels()
if streams: stream = None
stream = streams[0] if twitches:
twitch = twitches[0]
streams = await self.crocbot.fetch_streams(user_ids=[int(twitch.channelid)])
stream = streams[0] if streams else None
if stream:
start_time = stream.started_at start_time = stream.started_at
else: else:
period = PERIOD.ALL period = PERIOD.ALL
@@ -327,21 +484,33 @@ class CounterCog(LionCog):
return (period, start_time) return (period, start_time)
async def formatted_lb(self, counter: str, periodstr: str, channelid: int): async def formatted_lb(
self,
counter: str,
periodstr: str,
community: Community,
origin: ORIGIN = ORIGIN.TWITCH
):
period, start_time = await self.parse_period(channelid, periodstr) period, start_time = await self.parse_period(community, periodstr)
lb = await self.leaderboard(counter, start_time=start_time) lb = await self.leaderboard(counter, start_time=start_time)
if lb: if lb:
userids = list(lb.keys()) name_map = {}
users = await self.crocbot.fetch_users(ids=userids) for userid in lb.keys():
name_map = {user.id: user.display_name for user in users} profile = await UserProfile.fetch(self.bot, userid)
name = await profile.get_name()
name_map[userid] = name
parts = [] parts = []
for userid, total in lb.items(): items = list(lb.items())
prefix = 'top 10 ' if len(items) > 10 else ''
items = items[:10]
for userid, total in items:
name = name_map.get(userid, str(userid)) name = name_map.get(userid, str(userid))
part = f"{name}: {total}" part = f"{name}: {total}"
parts.append(part) parts.append(part)
lbstr = '; '.join(parts) lbstr = '; '.join(parts)
return f"{counter} {period.value[-1]} leaderboard --- {lbstr}" return f"{counter} {period.value[-1]} {prefix}leaderboard --- {lbstr}"
else: else:
return f"{counter} {period.value[-1]} leaderboard is empty!" return f"{counter} {period.value[-1]} leaderboard is empty!"

View File

@@ -4,17 +4,21 @@ import json
import os import os
from typing import Optional from typing import Optional
from attr import dataclass import discord
from discord.ext import commands as cmds
from discord import app_commands as appcmds
import twitchio import twitchio
from twitchio.ext import commands from twitchio.ext import commands
from meta import CrocBot, LionCog from meta import CrocBot, LionCog, LionContext, LionBot
from meta.LionBot import LionBot
from meta.sockets import Channel, register_channel from meta.sockets import Channel, register_channel
from utils.lib import strfdelta, utc_now from utils.lib import strfdelta, utc_now
from . import logger from . import logger
from .data import NowListData from .data import NowListData
from modules.profiles.profile import UserProfile
class NowDoingChannel(Channel): class NowDoingChannel(Channel):
name = 'NowList' name = 'NowList'
@@ -25,19 +29,7 @@ class NowDoingChannel(Channel):
async def on_connection(self, websocket, event): async def on_connection(self, websocket, event):
await super().on_connection(websocket, event) await super().on_connection(websocket, event)
for task in self.cog.tasks.values(): await self.reload_tasklist(websocket=websocket)
await self.send_set(*self.task_args(task), websocket=websocket)
async def send_test_set(self):
tasks = [
(0, 'Tester0', "Testing Tasklist", True),
(1, 'Tester1', "Getting Confused", False),
(2, "Tester2", "Generating Bugs", True),
(3, "Tester3", "Fixing Bugs", False),
(4, "Tester4", "Pushing the red button", False),
]
for task in tasks:
await self.send_set(*task)
def task_args(self, task: NowListData.Task): def task_args(self, task: NowListData.Task):
return ( return (
@@ -48,6 +40,14 @@ class NowDoingChannel(Channel):
task.done_at.isoformat() if task.done_at else None, task.done_at.isoformat() if task.done_at else None,
) )
async def reload_tasklist(self, websocket=None):
"""
Clear tasklist and re-send current tasks.
"""
await self.send_clear(websocket=websocket)
for task in self.cog.tasks.values():
await self.send_set(*self.task_args(task), websocket=websocket)
async def send_set(self, userid, name, task, start_at, end_at, websocket=None): async def send_set(self, userid, name, task, start_at, end_at, websocket=None):
await self.send_event({ await self.send_event({
'type': "DO", 'type': "DO",
@@ -61,28 +61,28 @@ class NowDoingChannel(Channel):
} }
}, websocket=websocket) }, websocket=websocket)
async def send_del(self, userid): async def send_del(self, userid, websocket=None):
await self.send_event({ await self.send_event({
'type': "DO", 'type': "DO",
'method': "delTask", 'method': "delTask",
'args': { 'args': {
'userid': userid, 'userid': userid,
} }
}) }, websocket=websocket)
async def send_clear(self): async def send_clear(self, websocket=None):
await self.send_event({ await self.send_event({
'type': "DO", 'type': "DO",
'method': "clearTasks", 'method': "clearTasks",
'args': { 'args': {
} }
}) }, websocket=websocket)
class NowDoingCog(LionCog): class NowDoingCog(LionCog):
def __init__(self, bot: LionBot): def __init__(self, bot: LionBot):
self.bot = bot self.bot = bot
self.crocbot = bot.crocbot self.crocbot: CrocBot = bot.crocbot
self.data = bot.db.load_registry(NowListData()) self.data = bot.db.load_registry(NowListData())
self.channel = NowDoingChannel(self) self.channel = NowDoingChannel(self)
register_channel(self.channel.name, self.channel) register_channel(self.channel.name, self.channel)
@@ -94,17 +94,82 @@ class NowDoingCog(LionCog):
async def cog_load(self): async def cog_load(self):
await self.data.init() await self.data.init()
await self.load_tasks() await self.load_tasks()
self.bot.get_cog('ProfileCog').add_profile_migrator(self.migrate_profiles, name='task-migrator')
self._load_twitch_methods(self.crocbot) self._load_twitch_methods(self.crocbot)
self.loaded.set() self.loaded.set()
async def cog_unload(self): async def cog_unload(self):
self.loaded.clear() self.loaded.clear()
self.tasks.clear() self.tasks.clear()
if profiles := self.bot.get_cog('ProfileCog'):
profiles.del_profile_migrator('task-migrator')
self._unload_twitch_methods(self.crocbot) self._unload_twitch_methods(self.crocbot)
async def migrate_profiles(self, source_profile: UserProfile, target_profile: UserProfile):
"""
Move current source task to target profile if there's room for it, otherwise annihilate
"""
await self.load_tasks()
source_task = self.tasks.pop(source_profile.profileid, None)
results = ["(Tasklist)"]
if source_task:
target_task = self.tasks.get(target_profile.profileid, None)
if target_task and (target_task.is_done or target_task.started_at < source_task.started_at):
# If target is done, remove it so we can overwrite
results.append("Removed older task from target profile.")
await target_task.delete()
target_task = None
if not target_task:
# Update source task with new profile id
await source_task.update(userid=target_profile.profileid)
target_task = source_task
await self.channel.send_set(*self.channel.task_args(target_task))
results.append("Migrated 1 currently running task from source profile.")
else:
# If there is a target task we can't overwrite, just delete the source task
await source_task.delete()
results.append("Ignoring and removing older task from source profile.")
self.tasks.pop(source_profile.profileid, None)
await self.channel.send_del(source_profile.profileid)
else:
results.append("No running task in source profile, nothing to migrate!")
await self.load_tasks()
return ' '.join(results)
async def user_profile_migration(self):
"""
Manual single-use migration method from the old userid format to the new profileid format.
"""
await self.load_tasks()
for userid, task in self.tasks.items():
userid = int(userid)
if userid > 1000:
# Assume it is a twitch userid
profile = await UserProfile.fetch_from_twitchid(self.bot, userid)
if not profile:
# Create a new profile with this twitch user
users = await self.crocbot.fetch_users(ids=[userid])
if not users:
continue
user = users[0]
profile = await UserProfile.create_from_twitch(self.bot, user)
if not await self.data.Task.fetch(profile.profileid):
await task.update(userid=profile.profileid)
else:
await task.delete()
await self.load_tasks()
await self.channel.reload_tasklist()
async def cog_check(self, ctx): async def cog_check(self, ctx):
if not self.loaded.is_set(): if not self.loaded.is_set():
await ctx.reply("Tasklists are still loading! Please wait a moment~") await ctx.reply("Tasklists are still loading! Please wait a moment~")
@@ -123,25 +188,27 @@ class NowDoingCog(LionCog):
# await self.channel.send_test_set() # await self.channel.send_test_set()
# await ctx.send(f"Hello {ctx.author.name}! This command does something, we aren't sure what yet.") # await ctx.send(f"Hello {ctx.author.name}! This command does something, we aren't sure what yet.")
# await ctx.send(str(list(self.tasks.items())[0])) # await ctx.send(str(list(self.tasks.items())[0]))
await self.user_profile_migration()
await ctx.send(str(ctx.author.id)) await ctx.send(str(ctx.author.id))
await ctx.reply("Userid -> profile migration done.")
else: else:
await ctx.send(f"Hello {ctx.author.name}! I don't think you have permission to test that.") await ctx.send(f"Hello {ctx.author.name}! I don't think you have permission to test that.")
@commands.command(aliases=['task', 'check']) async def now(self, ctx: commands.Context | LionContext, profile: UserProfile, args: Optional[str] = None, edit=False):
async def now(self, ctx: commands.Context, *, args: Optional[str] = None):
userid = int(ctx.author.id)
args = args.strip() if args else None args = args.strip() if args else None
userid = profile.profileid
if args: if args:
existing = self.tasks.get(userid, None)
await self.data.Task.table.delete_where(userid=userid) await self.data.Task.table.delete_where(userid=userid)
task = await self.data.Task.create( task = await self.data.Task.create(
userid=userid, userid=userid,
name=ctx.author.display_name, name=await profile.get_name(),
task=args, task=args,
started_at=utc_now(), started_at=existing.started_at if (existing and edit) else utc_now(),
) )
self.tasks[task.userid] = task self.tasks[task.userid] = task
await self.channel.send_set(*self.channel.task_args(task)) await self.channel.send_set(*self.channel.task_args(task))
await ctx.send(f"Updated your current task, good luck!") await ctx.send("Updated your current task, good luck!")
elif task := self.tasks.get(userid, None): elif task := self.tasks.get(userid, None):
if task.is_done: if task.is_done:
done_ago = strfdelta(utc_now() - task.done_at) done_ago = strfdelta(utc_now() - task.done_at)
@@ -159,9 +226,38 @@ class NowDoingCog(LionCog):
"Show what you are currently working on with, e.g. !now Reading notes" "Show what you are currently working on with, e.g. !now Reading notes"
) )
@commands.command(name='next') @commands.command(
async def nownext(self, ctx: commands.Context, *, args: Optional[str] = None): name='now',
userid = int(ctx.author.id) aliases=['task', 'check']
)
async def twi_now(self, ctx: commands.Context, *, args: Optional[str] = None):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_twitch(ctx.author)
await self.now(ctx, profile, args)
@cmds.hybrid_command(
name='now',
aliases=['task', 'check']
)
async def disc_now(self, ctx: LionContext, *, args: Optional[str] = None):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_discord(ctx.author)
await self.now(ctx, profile, args)
@commands.command(
name='edit',
)
async def twi_edit(self, ctx: commands.Context, *, args: Optional[str] = None):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_twitch(ctx.author)
await self.now(ctx, profile, args, edit=True)
@cmds.hybrid_command(
name='edit',
)
async def disc_edit(self, ctx: LionContext, *, args: Optional[str] = None):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_discord(ctx.author)
await self.now(ctx, profile, args, edit=True)
async def nownext(self, ctx: commands.Context | LionContext, profile: UserProfile, args: Optional[str]):
userid = profile.profileid
task = self.tasks.get(userid, None) task = self.tasks.get(userid, None)
if args: if args:
if task: if task:
@@ -176,13 +272,13 @@ class NowDoingCog(LionCog):
await self.data.Task.table.delete_where(userid=userid) await self.data.Task.table.delete_where(userid=userid)
task = await self.data.Task.create( task = await self.data.Task.create(
userid=userid, userid=userid,
name=ctx.author.display_name, name=await profile.get_name(),
task=args, task=args,
started_at=utc_now(), started_at=utc_now(),
) )
self.tasks[task.userid] = task self.tasks[task.userid] = task
await self.channel.send_set(*self.channel.task_args(task)) await self.channel.send_set(*self.channel.task_args(task))
await ctx.send(f"Next task set, good luck!" + ' ' + prefix) await ctx.send("Next task set, good luck!" + ' ' + prefix)
elif task: elif task:
if task.is_done: if task.is_done:
done_ago = strfdelta(utc_now() - task.done_at) done_ago = strfdelta(utc_now() - task.done_at)
@@ -200,9 +296,22 @@ class NowDoingCog(LionCog):
"Show what you are currently working on with, e.g. !now Reading notes" "Show what you are currently working on with, e.g. !now Reading notes"
) )
@commands.command() @commands.command(
async def done(self, ctx: commands.Context): name='next',
userid = int(ctx.author.id) )
async def twi_next(self, ctx: commands.Context, *, args: Optional[str] = None):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_twitch(ctx.author)
await self.nownext(ctx, profile, args)
@cmds.hybrid_command(
name='next',
)
async def disc_next(self, ctx: LionContext, *, args: Optional[str] = None):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_discord(ctx.author)
await self.nownext(ctx, profile, args)
async def done(self, ctx: commands.Context | LionContext, profile: UserProfile):
userid = profile.profileid
if task := self.tasks.get(userid, None): if task := self.tasks.get(userid, None):
if task.is_done: if task.is_done:
await ctx.send( await ctx.send(
@@ -222,9 +331,36 @@ class NowDoingCog(LionCog):
"Show what you are currently working on with, e.g. !now Reading notes" "Show what you are currently working on with, e.g. !now Reading notes"
) )
@commands.command() @commands.command(
async def clear(self, ctx: commands.Context): name='done',
userid = int(ctx.author.id) )
async def twi_done(self, ctx: commands.Context):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_twitch(ctx.author)
await self.done(ctx, profile)
@cmds.hybrid_command(
name='done',
)
async def disc_done(self, ctx: LionContext):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_discord(ctx.author)
await self.done(ctx, profile)
@commands.command(
name='clear',
)
async def twi_clear(self, ctx: commands.Context):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_twitch(ctx.author)
await self.clear(ctx, profile)
@cmds.hybrid_command(
name='clear',
)
async def disc_clear(self, ctx: LionContext):
profile = await self.bot.get_cog('ProfileCog').fetch_profile_discord(ctx.author)
await self.clear(ctx, profile)
async def clear(self, ctx: commands.Context | LionContext, profile):
userid = profile.profileid
if task := self.tasks.pop(userid, None): if task := self.tasks.pop(userid, None):
await task.delete() await task.delete()
await self.channel.send_del(userid) await self.channel.send_del(userid)

View File

@@ -8,10 +8,12 @@ from gui.cards import FocusTimerCard, BreakTimerCard
if TYPE_CHECKING: if TYPE_CHECKING:
from .timer import Timer, Stage from .timer import Timer, Stage
from tracking.voice.cog import VoiceTrackerCog from tracking.voice.cog import VoiceTrackerCog
from modules.nowdoing.cog import NowDoingCog
async def get_timer_card(bot: LionBot, timer: 'Timer', stage: 'Stage'): async def get_timer_card(bot: LionBot, timer: 'Timer', stage: 'Stage'):
voicecog: 'VoiceTrackerCog' = bot.get_cog('VoiceTrackerCog') voicecog: 'VoiceTrackerCog' = bot.get_cog('VoiceTrackerCog')
nowcog: 'NowDoingCog' = bot.get_cog('NowDoingCog')
name = timer.base_name name = timer.base_name
if stage is not None: if stage is not None:
@@ -23,16 +25,22 @@ async def get_timer_card(bot: LionBot, timer: 'Timer', stage: 'Stage'):
card_users = [] card_users = []
guildid = timer.data.guildid guildid = timer.data.guildid
for member in timer.members: for member in timer.members:
if voicecog is not None: profile = await bot.get_cog('ProfileCog').fetch_profile_discord(member)
session = voicecog.get_session(guildid, member.id) task = nowcog.tasks.get(profile.profileid, None)
tag = session.tag tag = ''
if session.start_time: session_duration = 0
session_duration = (utc_now() - session.start_time).total_seconds()
else: if task:
session_duration = 0 tag = task.task
session_duration = ((task.done_at or utc_now()) - task.started_at).total_seconds()
else: else:
session_duration = 0 session = voicecog.get_session(guildid, member.id)
tag = None if session:
tag = session.tag
if session.start_time:
session_duration = (utc_now() - session.start_time).total_seconds()
else:
session_duration = 0
card_user = ( card_user = (
(member.id, (member.avatar or member.default_avatar).key), (member.id, (member.avatar or member.default_avatar).key),

View File

@@ -92,7 +92,7 @@ class ProfileCog(LionCog):
results.append(f"Migrated {len(twitch_rows)} attached twitch account(s).") results.append(f"Migrated {len(twitch_rows)} attached twitch account(s).")
# And then mark the old profile as migrated # And then mark the old profile as migrated
await source_profile.update(migrated=target_profile.profileid) await source_profile.profile_row.update(migrated=target_profile.profileid)
results.append("Marking old profile as migrated.. finished!") results.append("Marking old profile as migrated.. finished!")
return results return results

View File

@@ -31,6 +31,30 @@ class UserProfile:
def __repr__(self): def __repr__(self):
return f"<UserProfile profileid={self.profileid} profile={self.profile_row}>" return f"<UserProfile profileid={self.profileid} profile={self.profile_row}>"
async def get_name(self):
# TODO: Store a preferred name in the profile preferences
# TODO Should have a multi-fetch system
name = None
twitches = await self.twitch_accounts()
if twitches:
users = await self.bot.crocbot.fetch_users(
ids=[int(twitch.userid) for twitch in twitches]
)
if users:
user = users[0]
name = user.display_name
if not name:
discords = await self.discord_accounts()
if discords:
user = await self.bot.fetch_user(discords[0].userid)
name = user.display_name
if not name:
name = 'Unknown'
return name
async def attach_discord(self, user: discord.User | discord.Member): async def attach_discord(self, user: discord.User | discord.Member):
""" """
Attach a new discord user to this profile. Attach a new discord user to this profile.

View File

@@ -654,7 +654,7 @@ class VoiceTrackerCog(LionCog):
# ----- Commands ----- # ----- Commands -----
@cmds.hybrid_command( @cmds.hybrid_command(
name=_p('cmd:now', "now"), name="tag",
description=_p( description=_p(
'cmd:now|desc', 'cmd:now|desc',
"Describe what you are working on, or see what your friends are working on!" "Describe what you are working on, or see what your friends are working on!"

View File

@@ -56,7 +56,7 @@ class UserAuthFlow:
result = await self._comm_task result = await self._comm_task
if result.get('error', None): if result.get('error', None):
# TODO Custom auth errors # TODO Custom auth errors
# This is only documented to occure when the user denies the auth # This is only documented to occur when the user denies the auth
raise SafeCancellation(f"Could not authenticate user! Reason: {result['error_description']}") raise SafeCancellation(f"Could not authenticate user! Reason: {result['error_description']}")
if result.get('state', None) != self.auth.state: if result.get('state', None) != self.auth.state: