Compare commits
10 Commits
feat-profi
...
feat/taskl
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e7a5c9b8a | |||
| 592017ba5e | |||
| 49a8cefeef | |||
| d4870740a2 | |||
| 8991b1a641 | |||
| 79645177bd | |||
| 9b3b7265d3 | |||
| 3c0d527501 | |||
| 997804c6bf | |||
| 2cdd084bbe |
@@ -287,13 +287,14 @@ CREATE TABLE tasklist(
|
||||
deleted_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ,
|
||||
last_updated_at TIMESTAMPTZ
|
||||
last_updated_at TIMESTAMPTZ,
|
||||
duration INTEGER
|
||||
);
|
||||
CREATE INDEX tasklist_users ON tasklist (userid);
|
||||
ALTER TABLE tasklist
|
||||
ADD CONSTRAINT fk_tasklist_users
|
||||
FOREIGN KEY (userid)
|
||||
REFERENCES user_config (userid)
|
||||
REFERENCES user_profiles (profileid)
|
||||
ON DELETE CASCADE
|
||||
NOT VALID;
|
||||
ALTER TABLE tasklist
|
||||
@@ -317,6 +318,20 @@ CREATE TABLE tasklist_reward_history(
|
||||
reward_count INTEGER
|
||||
);
|
||||
CREATE INDEX tasklist_reward_history_users ON tasklist_reward_history (userid, reward_time);
|
||||
|
||||
CREATE TABLE tasklist_current(
|
||||
taskid INTEGER PRIMARY KEY REFERENCES tasklist (taskid) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
started_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tasklist_planner(
|
||||
taskid INTEGER PRIMARY KEY REFERENCES tasklist (taskid) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
sortkey INTEGER
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
-- }}}
|
||||
|
||||
-- Reminder data {{{
|
||||
|
||||
2
src/gui
2
src/gui
Submodule src/gui updated: 40bc140355...62d2484914
@@ -92,6 +92,10 @@ class LionBot(Bot):
|
||||
def core(self):
|
||||
return self.get_cog('CoreCog')
|
||||
|
||||
@property
|
||||
def profiles(self):
|
||||
return self.get_cog('ProfileCog')
|
||||
|
||||
async def _handle_global_dispatch(self, event_name: str, *args, **kwargs):
|
||||
self.dispatch(event_name, *args, **kwargs)
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ if TYPE_CHECKING:
|
||||
from core.lion_member import LionMember
|
||||
from core.lion_user import LionUser
|
||||
from core.lion_guild import LionGuild
|
||||
from modules.profiles.profile import UserProfile
|
||||
from modules.profiles.community import Community
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -54,6 +56,8 @@ class LionContext(Context['LionBot']):
|
||||
lguild: 'LionGuild'
|
||||
lmember: 'LionMember'
|
||||
alion: 'LionUser | LionMember'
|
||||
profile: 'UserProfile'
|
||||
community: 'Community'
|
||||
|
||||
def __repr__(self):
|
||||
parts = {}
|
||||
|
||||
@@ -4,17 +4,21 @@ import json
|
||||
import os
|
||||
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
|
||||
from twitchio.ext import commands
|
||||
|
||||
from meta import CrocBot, LionCog
|
||||
from meta.LionBot import LionBot
|
||||
from meta import CrocBot, LionCog, LionContext, LionBot
|
||||
from meta.sockets import Channel, register_channel
|
||||
from utils.lib import strfdelta, utc_now
|
||||
from . import logger
|
||||
from .data import NowListData
|
||||
|
||||
from modules.profiles.profile import UserProfile
|
||||
|
||||
|
||||
class NowDoingChannel(Channel):
|
||||
name = 'NowList'
|
||||
@@ -25,19 +29,7 @@ class NowDoingChannel(Channel):
|
||||
|
||||
async def on_connection(self, websocket, event):
|
||||
await super().on_connection(websocket, event)
|
||||
for task in self.cog.tasks.values():
|
||||
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)
|
||||
await self.reload_tasklist(websocket=websocket)
|
||||
|
||||
def task_args(self, task: NowListData.Task):
|
||||
return (
|
||||
@@ -48,6 +40,14 @@ class NowDoingChannel(Channel):
|
||||
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):
|
||||
await self.send_event({
|
||||
'type': "DO",
|
||||
@@ -61,28 +61,28 @@ class NowDoingChannel(Channel):
|
||||
}
|
||||
}, websocket=websocket)
|
||||
|
||||
async def send_del(self, userid):
|
||||
async def send_del(self, userid, websocket=None):
|
||||
await self.send_event({
|
||||
'type': "DO",
|
||||
'method': "delTask",
|
||||
'args': {
|
||||
'userid': userid,
|
||||
}
|
||||
})
|
||||
}, websocket=websocket)
|
||||
|
||||
async def send_clear(self):
|
||||
async def send_clear(self, websocket=None):
|
||||
await self.send_event({
|
||||
'type': "DO",
|
||||
'method': "clearTasks",
|
||||
'args': {
|
||||
}
|
||||
})
|
||||
}, websocket=websocket)
|
||||
|
||||
|
||||
class NowDoingCog(LionCog):
|
||||
def __init__(self, bot: LionBot):
|
||||
self.bot = bot
|
||||
self.crocbot = bot.crocbot
|
||||
self.crocbot: CrocBot = bot.crocbot
|
||||
self.data = bot.db.load_registry(NowListData())
|
||||
self.channel = NowDoingChannel(self)
|
||||
register_channel(self.channel.name, self.channel)
|
||||
@@ -94,17 +94,82 @@ class NowDoingCog(LionCog):
|
||||
|
||||
async def cog_load(self):
|
||||
await self.data.init()
|
||||
|
||||
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.loaded.set()
|
||||
|
||||
async def cog_unload(self):
|
||||
self.loaded.clear()
|
||||
self.tasks.clear()
|
||||
if profiles := self.bot.get_cog('ProfileCog'):
|
||||
profiles.del_profile_migrator('task-migrator')
|
||||
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):
|
||||
if not self.loaded.is_set():
|
||||
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 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 self.user_profile_migration()
|
||||
await ctx.send(str(ctx.author.id))
|
||||
await ctx.reply("Userid -> profile migration done.")
|
||||
else:
|
||||
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, *, args: Optional[str] = None):
|
||||
userid = int(ctx.author.id)
|
||||
async def now(self, ctx: commands.Context | LionContext, profile: UserProfile, args: Optional[str] = None, edit=False):
|
||||
args = args.strip() if args else None
|
||||
userid = profile.profileid
|
||||
if args:
|
||||
existing = self.tasks.get(userid, None)
|
||||
await self.data.Task.table.delete_where(userid=userid)
|
||||
task = await self.data.Task.create(
|
||||
userid=userid,
|
||||
name=ctx.author.display_name,
|
||||
task=args,
|
||||
started_at=utc_now(),
|
||||
started_at=existing.started_at if (existing and edit) else utc_now(),
|
||||
)
|
||||
self.tasks[task.userid] = 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):
|
||||
if task.is_done:
|
||||
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"
|
||||
)
|
||||
|
||||
@commands.command(name='next')
|
||||
async def nownext(self, ctx: commands.Context, *, args: Optional[str] = None):
|
||||
userid = int(ctx.author.id)
|
||||
@commands.command(
|
||||
name='now',
|
||||
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)
|
||||
if args:
|
||||
if task:
|
||||
@@ -182,7 +278,7 @@ class NowDoingCog(LionCog):
|
||||
)
|
||||
self.tasks[task.userid] = 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:
|
||||
if task.is_done:
|
||||
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"
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
async def done(self, ctx: commands.Context):
|
||||
userid = int(ctx.author.id)
|
||||
@commands.command(
|
||||
name='next',
|
||||
)
|
||||
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.is_done:
|
||||
await ctx.send(
|
||||
@@ -222,9 +331,36 @@ class NowDoingCog(LionCog):
|
||||
"Show what you are currently working on with, e.g. !now Reading notes"
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
async def clear(self, ctx: commands.Context):
|
||||
userid = int(ctx.author.id)
|
||||
@commands.command(
|
||||
name='done',
|
||||
)
|
||||
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):
|
||||
await task.delete()
|
||||
await self.channel.send_del(userid)
|
||||
|
||||
@@ -8,10 +8,12 @@ from gui.cards import FocusTimerCard, BreakTimerCard
|
||||
if TYPE_CHECKING:
|
||||
from .timer import Timer, Stage
|
||||
from tracking.voice.cog import VoiceTrackerCog
|
||||
from modules.nowdoing.cog import NowDoingCog
|
||||
|
||||
|
||||
async def get_timer_card(bot: LionBot, timer: 'Timer', stage: 'Stage'):
|
||||
voicecog: 'VoiceTrackerCog' = bot.get_cog('VoiceTrackerCog')
|
||||
nowcog: 'NowDoingCog' = bot.get_cog('NowDoingCog')
|
||||
|
||||
name = timer.base_name
|
||||
if stage is not None:
|
||||
@@ -23,16 +25,22 @@ async def get_timer_card(bot: LionBot, timer: 'Timer', stage: 'Stage'):
|
||||
card_users = []
|
||||
guildid = timer.data.guildid
|
||||
for member in timer.members:
|
||||
if voicecog is not None:
|
||||
session = voicecog.get_session(guildid, member.id)
|
||||
tag = session.tag
|
||||
if session.start_time:
|
||||
session_duration = (utc_now() - session.start_time).total_seconds()
|
||||
else:
|
||||
session_duration = 0
|
||||
profile = await bot.get_cog('ProfileCog').fetch_profile_discord(member)
|
||||
task = nowcog.tasks.get(profile.profileid, None)
|
||||
tag = ''
|
||||
session_duration = 0
|
||||
|
||||
if task:
|
||||
tag = task.task
|
||||
session_duration = ((task.done_at or utc_now()) - task.started_at).total_seconds()
|
||||
else:
|
||||
session_duration = 0
|
||||
tag = None
|
||||
session = voicecog.get_session(guildid, member.id)
|
||||
if session:
|
||||
tag = session.tag
|
||||
if session.start_time:
|
||||
session_duration = (utc_now() - session.start_time).total_seconds()
|
||||
else:
|
||||
session_duration = 0
|
||||
|
||||
card_user = (
|
||||
(member.id, (member.avatar or member.default_avatar).key),
|
||||
|
||||
@@ -40,6 +40,17 @@ class ProfileCog(LionCog):
|
||||
async def cog_check(self, ctx):
|
||||
return True
|
||||
|
||||
async def bot_check_once(self, ctx: LionContext):
|
||||
"""
|
||||
Inject the contextual UserProfile and Community into the LionContext.
|
||||
|
||||
Creates the profile and community if they do not exist.
|
||||
"""
|
||||
if ctx.guild:
|
||||
ctx.community = await self.fetch_community_discord(ctx.guild)
|
||||
ctx.profile = await self.fetch_profile_discord(ctx.author)
|
||||
return True
|
||||
|
||||
# Profile API
|
||||
def add_profile_migrator(self, migrator, name=None):
|
||||
name = name or migrator.__name__
|
||||
@@ -92,7 +103,7 @@ class ProfileCog(LionCog):
|
||||
results.append(f"Migrated {len(twitch_rows)} attached twitch account(s).")
|
||||
|
||||
# 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!")
|
||||
return results
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@ from discord.ext import commands as cmds
|
||||
from discord import app_commands as appcmds
|
||||
from discord.app_commands.transformers import AppCommandOptionType as cmdopt
|
||||
|
||||
from data.queries import JOINTYPE
|
||||
from meta import LionBot, LionCog, LionContext
|
||||
from meta.CrocBot import CrocBot
|
||||
from meta.logger import log_wrap
|
||||
from meta.errors import UserInputError
|
||||
from modules.profiles.profile import UserProfile
|
||||
from utils.lib import utc_now, error_embed
|
||||
from utils.ui import ChoicedEnum, Transformed, AButton
|
||||
|
||||
@@ -126,6 +129,7 @@ class TasklistCog(LionCog):
|
||||
|
||||
def __init__(self, bot: LionBot):
|
||||
self.bot = bot
|
||||
self.crocbot: CrocBot = bot.crocbot
|
||||
self.data = bot.db.load_registry(TasklistData())
|
||||
self.babel = babel
|
||||
self.settings = TasklistSettings()
|
||||
@@ -138,10 +142,84 @@ class TasklistCog(LionCog):
|
||||
self.bot.core.guild_config.register_model_setting(self.settings.task_reward_limit)
|
||||
self.bot.add_view(TasklistCaller(self.bot))
|
||||
|
||||
self.bot.profiles.add_profile_migrator(self.migrate_profiles, name='tasklist-migrator')
|
||||
|
||||
configcog = self.bot.get_cog('ConfigCog')
|
||||
self.crossload_group(self.configure_group, configcog.config_group)
|
||||
|
||||
@LionCog.listener('on_tasks_completed')
|
||||
self._load_twitch_methods(self.crocbot)
|
||||
|
||||
async def cog_unload(self):
|
||||
self.live_tasklists.clear()
|
||||
if profiles := self.bot.get_cog('ProfileCog'):
|
||||
profiles.del_profile_migrator('tasklist-migrator')
|
||||
self._unload_twitch_methods(self.crocbot)
|
||||
|
||||
@log_wrap(action="Tasklist Profile Migration")
|
||||
async def migrate_profiles(self, source_profile: UserProfile, target_profile: UserProfile):
|
||||
"""
|
||||
Re-assign all tasklist tasks from source profile to target profile.
|
||||
TODO: Probably wants some elegant handling of the cached or running tasklists.
|
||||
"""
|
||||
results = ["(Tasklist)"]
|
||||
sourceid = source_profile.profileid
|
||||
targetid = target_profile.profileid
|
||||
updated = await self.data.Task.table.update_where(userid=sourceid).set(userid=targetid)
|
||||
if updated:
|
||||
results.append(
|
||||
f"Migrated {len(updated)} task row(s) from source profile."
|
||||
)
|
||||
for channel_lists in self.live_tasklists.get(sourceid, []):
|
||||
for tasklist in list(channel_lists.values()):
|
||||
await tasklist.close()
|
||||
self.bot.dispatch('tasklist_update', profileid=targetid, summon=False)
|
||||
else:
|
||||
results.append(
|
||||
"No tasks found in source profile, nothing to migrate!"
|
||||
)
|
||||
|
||||
return ' '.join(results)
|
||||
|
||||
async def user_profile_migration(self):
|
||||
"""
|
||||
Manual one-shot migration method from old Discord userids to the new profileids.
|
||||
"""
|
||||
# First collect all the distinct userids from the tasklist
|
||||
# Then create a map of userids to profileids, creating the profiles if required
|
||||
# Then do updates, we can just inefficiently do updates on each distinct userid
|
||||
# As long as the userids and profileids never overlap, this is fine. Fine for a one-shot
|
||||
|
||||
# Extract all the userids that exist in the table
|
||||
rows = await self.data.Task.table.select_where().select(
|
||||
userid="DISTINCT(userid)"
|
||||
).with_no_adapter()
|
||||
|
||||
# Fetch or create discord user profiles for them
|
||||
profile_map = {}
|
||||
for row in rows:
|
||||
userid = row['userid']
|
||||
if userid > 100000:
|
||||
# Assume a Discord snowflake
|
||||
profile = await UserProfile.fetch_from_discordid(self.bot, userid)
|
||||
|
||||
if not profile:
|
||||
try:
|
||||
user = self.bot.get_user(userid)
|
||||
if user is None:
|
||||
user = await self.bot.fetch_user(userid)
|
||||
except discord.HTTPException:
|
||||
logger.info(f"Skipping user {userid}")
|
||||
continue
|
||||
profile = await UserProfile.create_from_discord(self.bot, user)
|
||||
profile_map[userid] = profile
|
||||
|
||||
# Now iterate through
|
||||
for userid, profile in profile_map.items():
|
||||
logger.info(f"Migrating userid {userid} to profile {profile}")
|
||||
await self.data.Task.table.update_where(userid=userid).set(userid=profile.profileid)
|
||||
|
||||
# Temporarily disabling integration with userid driven Economy
|
||||
# @LionCog.listener('on_tasks_completed')
|
||||
@log_wrap(action="reward tasks completed")
|
||||
async def reward_tasks_completed(self, member: discord.Member, *taskids: int):
|
||||
async with self.bot.db.connection() as conn:
|
||||
@@ -170,6 +248,9 @@ class TasklistCog(LionCog):
|
||||
)
|
||||
|
||||
async def is_tasklist_channel(self, channel) -> bool:
|
||||
"""
|
||||
Check whether a given Discord channel is a tasklist channel
|
||||
"""
|
||||
if not channel.guild:
|
||||
return True
|
||||
channels = (await self.settings.tasklist_channels.get(channel.guild.id)).value
|
||||
@@ -186,12 +267,16 @@ class TasklistCog(LionCog):
|
||||
return (channel in channels) or (channel.id in private_channels) or (channel.category in channels)
|
||||
|
||||
async def call_tasklist(self, interaction: discord.Interaction):
|
||||
"""
|
||||
Given a Discord channel interaction, summon the interacting user's tasklist.
|
||||
"""
|
||||
await interaction.response.defer(thinking=True, ephemeral=True)
|
||||
channel = interaction.channel
|
||||
guild = channel.guild
|
||||
userid = interaction.user.id
|
||||
profile = await self.bot.profiles.fetch_profile_discord(interaction.user)
|
||||
profileid = profile.profileid
|
||||
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, userid)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, profileid)
|
||||
|
||||
if await self.is_tasklist_channel(channel):
|
||||
# Check we have permissions to send a regular message here
|
||||
@@ -213,7 +298,7 @@ class TasklistCog(LionCog):
|
||||
)
|
||||
await interaction.edit_original_response(embed=error)
|
||||
else:
|
||||
tasklistui = TasklistUI.fetch(tasklist, channel, guild, timeout=None)
|
||||
tasklistui = TasklistUI.fetch(tasklist, channel, guild, caller=interaction.user, timeout=None)
|
||||
await tasklistui.summon(force=True)
|
||||
await interaction.delete_original_response()
|
||||
else:
|
||||
@@ -222,14 +307,14 @@ class TasklistCog(LionCog):
|
||||
await tasklistui.run(interaction)
|
||||
|
||||
@LionCog.listener('on_tasklist_update')
|
||||
async def update_listening_tasklists(self, userid, channel=None, summon=True):
|
||||
async def update_listening_tasklists(self, profileid, channel=None, summon=True):
|
||||
"""
|
||||
Propagate a tasklist update to all persistent tasklist UIs for this user.
|
||||
|
||||
If channel is given, also summons the UI if the channel is a tasklist channel.
|
||||
"""
|
||||
# Do the given channel first, and summon if requested
|
||||
if channel and (tui := TasklistUI._live_[userid].get(channel.id, None)) is not None:
|
||||
if channel and (tui := TasklistUI._live_[profileid].get(channel.id, None)) is not None:
|
||||
try:
|
||||
if summon and await self.is_tasklist_channel(channel):
|
||||
await tui.summon()
|
||||
@@ -240,7 +325,7 @@ class TasklistCog(LionCog):
|
||||
await tui.close()
|
||||
|
||||
# Now do the rest of the listening channels
|
||||
listening = TasklistUI._live_[userid]
|
||||
listening = TasklistUI._live_[profileid]
|
||||
for cid, ui in list(listening.items()):
|
||||
if channel and channel.id == cid:
|
||||
# We already did this channel
|
||||
@@ -275,7 +360,7 @@ class TasklistCog(LionCog):
|
||||
async def tasklist_group(self, ctx: LionContext):
|
||||
raise NotImplementedError
|
||||
|
||||
async def _task_acmpl(self, userid: int, partial: str, multi=False) -> list[appcmds.Choice]:
|
||||
async def _task_acmpl(self, profileid: int, partial: str, multi=False) -> list[appcmds.Choice]:
|
||||
"""
|
||||
Generate a list of task Choices matching a given partial string.
|
||||
|
||||
@@ -284,7 +369,7 @@ class TasklistCog(LionCog):
|
||||
t = self.bot.translator.t
|
||||
|
||||
# Should usually be cached, so this won't trigger repetitive db access
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, userid)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, profileid)
|
||||
|
||||
# Special case for an empty tasklist
|
||||
if not tasklist.tasklist:
|
||||
@@ -392,13 +477,17 @@ class TasklistCog(LionCog):
|
||||
"""
|
||||
Shared autocomplete for single task parameters.
|
||||
"""
|
||||
return await self._task_acmpl(interaction.user.id, partial, multi=False)
|
||||
profile = await self.bot.profiles.fetch_profile_discord(interaction.user)
|
||||
profileid = profile.profileid
|
||||
return await self._task_acmpl(profileid, partial, multi=False)
|
||||
|
||||
async def tasks_acmpl(self, interaction: discord.Interaction, partial: str) -> list[appcmds.Choice]:
|
||||
"""
|
||||
Shared autocomplete for multiple task parameters.
|
||||
"""
|
||||
return await self._task_acmpl(interaction.user.id, partial, multi=True)
|
||||
profile = await self.bot.profiles.fetch_profile_discord(interaction.user)
|
||||
profileid = profile.profileid
|
||||
return await self._task_acmpl(profileid, partial, multi=True)
|
||||
|
||||
@tasklist_group.command(
|
||||
name=_p('cmd:tasks_new', "new"),
|
||||
@@ -422,7 +511,7 @@ class TasklistCog(LionCog):
|
||||
if not ctx.interaction:
|
||||
return
|
||||
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.author.id)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.profile.profileid)
|
||||
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
|
||||
|
||||
# Fetch parent task if required
|
||||
@@ -453,9 +542,9 @@ class TasklistCog(LionCog):
|
||||
)
|
||||
await ctx.interaction.edit_original_response(
|
||||
embed=embed,
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.author.id] else TasklistCaller(self.bot)
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.profile.profileid] else TasklistCaller(self.bot)
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=ctx.author.id, channel=ctx.channel)
|
||||
self.bot.dispatch('tasklist_update', profileid=ctx.profile.profileid, channel=ctx.channel)
|
||||
|
||||
tasklist_new_cmd.autocomplete('parent')(task_acmpl)
|
||||
|
||||
@@ -523,7 +612,7 @@ class TasklistCog(LionCog):
|
||||
raise UserInputError(error)
|
||||
|
||||
# Contents successfully parsed, update the tasklist.
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.author.id)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.profile.profileid)
|
||||
|
||||
taskinfo = tasklist.parse_tasklist(lines)
|
||||
|
||||
@@ -572,9 +661,9 @@ class TasklistCog(LionCog):
|
||||
)
|
||||
await ctx.interaction.edit_original_response(
|
||||
embed=embed,
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.author.id] else TasklistCaller(self.bot)
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.profile.profileid] else TasklistCaller(self.bot)
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=ctx.author.id, channel=ctx.channel)
|
||||
self.bot.dispatch('tasklist_update', profileid=ctx.profile.profileid, channel=ctx.channel)
|
||||
|
||||
@tasklist_group.command(
|
||||
name=_p('cmd:tasks_edit', "edit"),
|
||||
@@ -600,7 +689,7 @@ class TasklistCog(LionCog):
|
||||
t = self.bot.translator.t
|
||||
if not ctx.interaction:
|
||||
return
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.author.id)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.profile.profileid)
|
||||
|
||||
# Fetch task to edit
|
||||
tid = tasklist.parse_label(taskstr) if taskstr else None
|
||||
@@ -651,12 +740,12 @@ class TasklistCog(LionCog):
|
||||
await interaction.response.send_message(
|
||||
embed=embed,
|
||||
view=(
|
||||
discord.utils.MISSING if ctx.channel.id in TasklistUI._live_[ctx.author.id]
|
||||
discord.utils.MISSING if ctx.channel.id in TasklistUI._live_[ctx.profile.profileid]
|
||||
else TasklistCaller(self.bot)
|
||||
),
|
||||
ephemeral=True
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=ctx.author.id, channel=ctx.channel)
|
||||
self.bot.dispatch('tasklist_update', profileid=ctx.profile.profileid, channel=ctx.channel)
|
||||
|
||||
if new_content or new_parent:
|
||||
# Manual edit route
|
||||
@@ -688,17 +777,17 @@ class TasklistCog(LionCog):
|
||||
async def tasklist_clear_cmd(self, ctx: LionContext):
|
||||
t = ctx.bot.translator.t
|
||||
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.author.id)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.profile.profileid)
|
||||
await tasklist.update_tasklist(deleted_at=utc_now())
|
||||
await ctx.reply(
|
||||
t(_p(
|
||||
'cmd:tasks_clear|resp:success',
|
||||
"Your tasklist has been cleared."
|
||||
)),
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.author.id] else TasklistCaller(self.bot),
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.profile.profileid] else TasklistCaller(self.bot),
|
||||
ephemeral=True
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=ctx.author.id, channel=ctx.channel)
|
||||
self.bot.dispatch('tasklist_update', profileid=ctx.profile.profileid, channel=ctx.channel)
|
||||
|
||||
@tasklist_group.command(
|
||||
name=_p('cmd:tasks_remove', "remove"),
|
||||
@@ -748,7 +837,7 @@ class TasklistCog(LionCog):
|
||||
|
||||
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
|
||||
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.author.id)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.profile.profileid)
|
||||
|
||||
conditions = []
|
||||
if taskidstr:
|
||||
@@ -784,7 +873,7 @@ class TasklistCog(LionCog):
|
||||
elif completed is False:
|
||||
conditions.append(self.data.Task.completed_at == NULL)
|
||||
|
||||
tasks = await self.data.Task.fetch_where(*conditions, userid=ctx.author.id)
|
||||
tasks = await self.data.Task.fetch_where(*conditions, userid=ctx.profile.profileid)
|
||||
if not tasks:
|
||||
await ctx.interaction.edit_original_response(
|
||||
embed=error_embed(t(_p(
|
||||
@@ -813,9 +902,9 @@ class TasklistCog(LionCog):
|
||||
)
|
||||
await ctx.interaction.edit_original_response(
|
||||
embed=embed,
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.author.id] else TasklistCaller(self.bot)
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.profile.profileid] else TasklistCaller(self.bot)
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=ctx.author.id, channel=ctx.channel)
|
||||
self.bot.dispatch('tasklist_update', profileid=ctx.profile.profileid, channel=ctx.channel)
|
||||
|
||||
tasklist_remove_cmd.autocomplete('taskidstr')(tasks_acmpl)
|
||||
|
||||
@@ -844,7 +933,7 @@ class TasklistCog(LionCog):
|
||||
|
||||
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
|
||||
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.author.id)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.profile.profileid)
|
||||
|
||||
try:
|
||||
taskids = tasklist.parse_labels(taskidstr)
|
||||
@@ -889,9 +978,9 @@ class TasklistCog(LionCog):
|
||||
)
|
||||
await ctx.interaction.edit_original_response(
|
||||
embed=embed,
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.author.id] else TasklistCaller(self.bot)
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.profile.profileid] else TasklistCaller(self.bot)
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=ctx.author.id, channel=ctx.channel)
|
||||
self.bot.dispatch('tasklist_update', profileid=ctx.profile.profileid, channel=ctx.channel)
|
||||
|
||||
tasklist_tick_cmd.autocomplete('taskidstr')(tasks_acmpl)
|
||||
|
||||
@@ -920,7 +1009,7 @@ class TasklistCog(LionCog):
|
||||
|
||||
await ctx.interaction.response.defer(thinking=True, ephemeral=True)
|
||||
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.author.id)
|
||||
tasklist = await Tasklist.fetch(self.bot, self.data, ctx.profile.profileid)
|
||||
|
||||
try:
|
||||
taskids = tasklist.parse_labels(taskidstr)
|
||||
@@ -962,9 +1051,9 @@ class TasklistCog(LionCog):
|
||||
)
|
||||
await ctx.interaction.edit_original_response(
|
||||
embed=embed,
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.author.id] else TasklistCaller(self.bot)
|
||||
view=None if ctx.channel.id in TasklistUI._live_[ctx.profile.profileid] else TasklistCaller(self.bot)
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=ctx.author.id, channel=ctx.channel)
|
||||
self.bot.dispatch('tasklist_update', profileid=ctx.profile.profileid, channel=ctx.channel)
|
||||
|
||||
tasklist_untick_cmd.autocomplete('taskidstr')(tasks_acmpl)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from data.columns import Integer, String, Timestamp, Bool
|
||||
|
||||
|
||||
class TasklistData(Registry):
|
||||
|
||||
class Task(RowModel):
|
||||
"""
|
||||
Row model describing a single task in a tasklist.
|
||||
@@ -14,21 +15,17 @@ class TasklistData(Registry):
|
||||
CREATE TABLE tasklist(
|
||||
taskid SERIAL PRIMARY KEY,
|
||||
userid BIGINT NOT NULL REFERENCES user_config ON DELETE CASCADE,
|
||||
profileid INTEGER NOT NULL REFERENCES user_profiles ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
parentid INTEGER REFERENCES tasklist (taskid) ON DELETE SET NULL,
|
||||
content TEXT NOT NULL,
|
||||
rewarded BOOL DEFAULT FALSE,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ,
|
||||
duration INTEGER,
|
||||
last_updated_at TIMESTAMPTZ
|
||||
);
|
||||
CREATE INDEX tasklist_users ON tasklist (userid);
|
||||
|
||||
CREATE TABLE tasklist_channels(
|
||||
guildid BIGINT NOT NULL REFERENCES guild_config (guildid) ON DELETE CASCADE,
|
||||
channelid BIGINT NOT NULL
|
||||
);
|
||||
CREATE INDEX tasklist_channels_guilds ON tasklist_channels (guildid);
|
||||
"""
|
||||
_tablename_ = "tasklist"
|
||||
|
||||
@@ -41,5 +38,26 @@ class TasklistData(Registry):
|
||||
created_at = Timestamp()
|
||||
deleted_at = Timestamp()
|
||||
last_updated_at = Timestamp()
|
||||
duration = Integer()
|
||||
|
||||
"""
|
||||
Schema
|
||||
------
|
||||
|
||||
CREATE TABLE tasklist_channels(
|
||||
guildid BIGINT NOT NULL REFERENCES guild_config (guildid) ON DELETE CASCADE,
|
||||
channelid BIGINT NOT NULL
|
||||
);
|
||||
CREATE INDEX tasklist_channels_guilds ON tasklist_channels (guildid);
|
||||
"""
|
||||
channels = Table('tasklist_channels')
|
||||
|
||||
"""
|
||||
Schema
|
||||
------
|
||||
CREATE TABLE current_tasks(
|
||||
taskid PRIMARY KEY REFERENCES tasklist (taskid) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
last_started_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
"""
|
||||
current_tasks = Table('current_tasks')
|
||||
|
||||
23
src/modules/tasklist/migration.sql
Normal file
23
src/modules/tasklist/migration.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
ALTER TABLE tasklist
|
||||
DROP CONSTRAINT fk_tasklist_users;
|
||||
|
||||
ALTER TABLE tasklist
|
||||
ADD CONSTRAINT fk_tasklist_users
|
||||
FOREIGN KEY (userid)
|
||||
REFERENCES user_profiles (profileid)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
NOT VALID;
|
||||
ALTER TABLE tasklist
|
||||
ADD COLUMN duration INTEGER;
|
||||
|
||||
|
||||
CREATE TABLE tasklist_current(
|
||||
taskid INTEGER PRIMARY KEY REFERENCES tasklist (taskid) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
started_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tasklist_planner(
|
||||
taskid INTEGER PRIMARY KEY REFERENCES tasklist (taskid) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
sortkey INTEGER
|
||||
);
|
||||
@@ -232,13 +232,18 @@ class TasklistUI(BasePager):
|
||||
|
||||
def __init__(self,
|
||||
tasklist: Tasklist,
|
||||
channel: discord.abc.Messageable, guild: Optional[discord.Guild] = None, **kwargs):
|
||||
channel: discord.abc.Messageable,
|
||||
guild: Optional[discord.Guild] = None,
|
||||
caller: Optional[discord.User | discord.Member] = None,
|
||||
**kwargs):
|
||||
kwargs.setdefault('timeout', 600)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.bot = tasklist.bot
|
||||
self.tasklist = tasklist
|
||||
self.labelled = tasklist.labelled
|
||||
self.caller = caller
|
||||
# NOTE: This is now a profiled
|
||||
self.userid = tasklist.userid
|
||||
self.channel = channel
|
||||
self.guild = guild
|
||||
@@ -449,9 +454,10 @@ class TasklistUI(BasePager):
|
||||
cascade=True,
|
||||
completed_at=utc_now()
|
||||
)
|
||||
if self.guild:
|
||||
if (member := self.guild.get_member(self.userid)):
|
||||
self.bot.dispatch('tasks_completed', member, *(t.taskid for t in to_complete))
|
||||
# TODO: Removed economy integration
|
||||
# if self.guild:
|
||||
# if (member := self.guild.get_member(self.userid)):
|
||||
# self.bot.dispatch('tasks_completed', member, *(t.taskid for t in to_complete))
|
||||
if to_uncomplete:
|
||||
await self.tasklist.update_tasks(
|
||||
*(t.taskid for t in to_uncomplete),
|
||||
@@ -475,7 +481,7 @@ class TasklistUI(BasePager):
|
||||
if shared_root:
|
||||
self._subtree_root = labelled[shared_root].taskid
|
||||
|
||||
self.bot.dispatch('tasklist_update', userid=self.userid, channel=self.channel, summon=False)
|
||||
self.bot.dispatch('tasklist_update', profileid=self.userid, channel=self.channel, summon=False)
|
||||
|
||||
async def _delete_menu(self, interaction: discord.Interaction, selected: Select, subtree: bool):
|
||||
await interaction.response.defer()
|
||||
@@ -486,7 +492,7 @@ class TasklistUI(BasePager):
|
||||
cascade=True,
|
||||
deleted_at=utc_now()
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=self.userid, channel=self.channel, summon=False)
|
||||
self.bot.dispatch('tasklist_update', profileid=self.userid, channel=self.channel, summon=False)
|
||||
|
||||
async def _edit_menu(self, interaction: discord.Interaction, selected: Select, subtree: bool):
|
||||
if not selected.values:
|
||||
@@ -513,7 +519,7 @@ class TasklistUI(BasePager):
|
||||
self._last_parentid = new_parentid
|
||||
if not subtree:
|
||||
self._subtree_root = new_parentid
|
||||
self.bot.dispatch('tasklist_update', userid=self.userid, channel=self.channel, summon=False)
|
||||
self.bot.dispatch('tasklist_update', profileid=self.userid, channel=self.channel, summon=False)
|
||||
|
||||
await interaction.response.send_modal(editor)
|
||||
|
||||
@@ -606,7 +612,7 @@ class TasklistUI(BasePager):
|
||||
self._subtree_root = pid
|
||||
await interaction.response.defer()
|
||||
await self.tasklist.create_task(new_task, parentid=pid)
|
||||
self.bot.dispatch('tasklist_update', userid=self.userid, channel=self.channel, summon=False)
|
||||
self.bot.dispatch('tasklist_update', profileid=self.userid, channel=self.channel, summon=False)
|
||||
|
||||
await press.response.send_modal(editor)
|
||||
|
||||
@@ -667,7 +673,7 @@ class TasklistUI(BasePager):
|
||||
|
||||
@editor.add_callback
|
||||
async def editor_callback(interaction: discord.Interaction):
|
||||
self.bot.dispatch('tasklist_update', userid=self.userid, channel=self.channel, summon=False)
|
||||
self.bot.dispatch('tasklist_update', profileid=self.userid, channel=self.channel, summon=False)
|
||||
|
||||
if sum(len(line) for line in editor.lines.values()) + len(editor.lines) >= 4000:
|
||||
await press.response.send_message(
|
||||
@@ -698,7 +704,7 @@ class TasklistUI(BasePager):
|
||||
await self.tasklist.update_tasklist(
|
||||
deleted_at=utc_now(),
|
||||
)
|
||||
self.bot.dispatch('tasklist_update', userid=self.userid, channel=self.channel, summon=False)
|
||||
self.bot.dispatch('tasklist_update', profileid=self.userid, channel=self.channel, summon=False)
|
||||
|
||||
async def clear_button_refresh(self):
|
||||
self.clear_button.label = self.bot.translator.t(_p(
|
||||
@@ -771,11 +777,12 @@ class TasklistUI(BasePager):
|
||||
|
||||
# ----- UI Flow -----
|
||||
def access_check(self, userid):
|
||||
return userid == self.userid
|
||||
return userid in (self.userid, self.caller.id if self.caller else None)
|
||||
|
||||
async def interaction_check(self, interaction: discord.Interaction):
|
||||
t = self.bot.translator.t
|
||||
if not self.access_check(interaction.user.id):
|
||||
interaction_profile = await self.bot.profiles.fetch_profile_discord(interaction.user)
|
||||
if not self.access_check(interaction_profile.profileid):
|
||||
embed = discord.Embed(
|
||||
description=t(_p(
|
||||
'ui:tasklist|error:wrong_user',
|
||||
@@ -812,10 +819,7 @@ class TasklistUI(BasePager):
|
||||
total = len(tasks)
|
||||
completed = sum(t.completed_at is not None for t in tasks)
|
||||
|
||||
if self.guild:
|
||||
user = self.guild.get_member(self.userid)
|
||||
else:
|
||||
user = self.bot.get_user(self.userid)
|
||||
user = self.caller
|
||||
user_name = user.name if user else str(self.userid)
|
||||
user_colour = user.colour if user else discord.Color.orange()
|
||||
|
||||
|
||||
@@ -654,7 +654,7 @@ class VoiceTrackerCog(LionCog):
|
||||
|
||||
# ----- Commands -----
|
||||
@cmds.hybrid_command(
|
||||
name=_p('cmd:now', "now"),
|
||||
name="tag",
|
||||
description=_p(
|
||||
'cmd:now|desc',
|
||||
"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
|
||||
if result.get('error', None):
|
||||
# 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']}")
|
||||
|
||||
if result.get('state', None) != self.auth.state:
|
||||
|
||||
Reference in New Issue
Block a user