Compare commits
9 Commits
feat-profi
...
feat-count
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a2ad77b91 | |||
| 3450f4a4b2 | |||
| d4870740a2 | |||
| 8991b1a641 | |||
| 79645177bd | |||
| 9b3b7265d3 | |||
| 3c0d527501 | |||
| 997804c6bf | |||
| 2cdd084bbe |
@@ -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!"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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!"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user