Skip to content

Commit

Permalink
Add graphical watchlist editors (#19)
Browse files Browse the repository at this point in the history
* Create watchlist editor UI

* Don't forget this

* Add editor for user config too

* Remove fluff
  • Loading branch information
Ghostopheles authored Jun 30, 2024
1 parent 31e499e commit b1c5ca4
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 83 deletions.
18 changes: 18 additions & 0 deletions cogs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,24 @@ def has_key(cls, value):
SUPPORTED_PRODUCTS.gryphonb,
]

INTERNAL_BRANCHES = (
SUPPORTED_PRODUCTS.wowlivetest2,
SUPPORTED_PRODUCTS.wowdev,
SUPPORTED_PRODUCTS.wowdev2,
SUPPORTED_PRODUCTS.wowdev3,
SUPPORTED_PRODUCTS.wowv,
SUPPORTED_PRODUCTS.wowv2,
SUPPORTED_PRODUCTS.wowv3,
SUPPORTED_PRODUCTS.wowv4,
SUPPORTED_PRODUCTS.fenrise,
SUPPORTED_PRODUCTS.fenrisvendor1,
SUPPORTED_PRODUCTS.fenrisvendor2,
SUPPORTED_PRODUCTS.fenrisvendor3,
SUPPORTED_PRODUCTS.fenrisdev,
SUPPORTED_PRODUCTS.fenrisdev2,
SUPPORTED_PRODUCTS.gryphondev,
)

WOW_BRANCHES = [
SUPPORTED_PRODUCTS.wow,
SUPPORTED_PRODUCTS.wowt,
Expand Down
137 changes: 137 additions & 0 deletions cogs/ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import logging
import discord
import discord.ui as ui

from enum import Enum

from cogs.config import (
SUPPORTED_GAMES,
SUPPORTED_PRODUCTS,
TEST_BRANCHES,
INTERNAL_BRANCHES,
WOW_BRANCHES,
DIABLO_BRANCHES,
RUMBLE_BRANCHES,
BNET_BRANCHES,
WatcherConfig,
)

from cogs.guild_config import GuildCFG
from cogs.user_config import UserConfigFile

logger = logging.getLogger("discord.test")


class WatchlistMenuType(Enum):
GUILD = 1
USER = 2


def get_branches_for_game(game: SUPPORTED_GAMES):
match game:
case SUPPORTED_GAMES.Warcraft:
return WOW_BRANCHES
case SUPPORTED_GAMES.Diablo4:
return DIABLO_BRANCHES
case SUPPORTED_GAMES.Gryphon:
return RUMBLE_BRANCHES
case SUPPORTED_GAMES.BattleNet:
return BNET_BRANCHES
case _:
return None


GUILD_CONFIG = GuildCFG()
USER_CONFIG = UserConfigFile()


class GuildSelectMenu(ui.Select):
async def callback(self, interaction: discord.Interaction):
guild_id = interaction.guild_id
if interaction.data is None:
return

selected = interaction.data["values"]
if len(selected) > 0:
game = WatcherConfig.get_game_from_branch(selected[0])
branches = get_branches_for_game(game)
old_watchlist = GUILD_CONFIG.get_guild_watchlist(guild_id)
for branch in branches:
branch = branch.name
if branch in selected and branch not in old_watchlist:
GUILD_CONFIG.add_to_guild_watchlist(guild_id, branch)
elif branch in old_watchlist and branch not in selected:
GUILD_CONFIG.remove_from_guild_watchlist(guild_id, branch)

await interaction.response.defer(ephemeral=True, invisible=True)


class UserSelectMenu(ui.Select):
async def callback(self, interaction: discord.Interaction):
user_id = interaction.user.id
if interaction.data is None:
return

selected = interaction.data["values"]
if len(selected) > 0:
game = WatcherConfig.get_game_from_branch(selected[0])
with USER_CONFIG as cfg:

branches = get_branches_for_game(game)
old_watchlist = cfg.get_watchlist(user_id)
for branch in branches:
branch = branch.name
if branch in selected and branch not in old_watchlist:
cfg.subscribe(user_id, branch)
elif branch in old_watchlist and branch not in selected:
cfg.unsubscribe(user_id, branch)

await interaction.response.defer(ephemeral=True, invisible=True)


class WatchlistUI(ui.View):
@classmethod
def create_menu(
cls, watchlist: list[str], game: SUPPORTED_GAMES, menuType: WatchlistMenuType
):
branches = get_branches_for_game(game)
if branches is None:
return None

view = cls()

options = []
for branch in branches:
branch: SUPPORTED_PRODUCTS

if branch in TEST_BRANCHES:
description = "This is a test branch"
elif branch in INTERNAL_BRANCHES:
description = "This is an internal branch"
else:
description = None

option = discord.SelectOption(
label=f"{branch.value} ({branch.name})",
value=branch.name,
default=branch.name in watchlist,
description=description,
)
options.append(option)

min_values = 0

menuClass = (
GuildSelectMenu if menuType == WatchlistMenuType.GUILD else UserSelectMenu
)

menu = menuClass(
select_type=discord.ComponentType.string_select,
options=options,
min_values=min_values,
max_values=len(options),
)

view.add_item(menu)

return view
82 changes: 0 additions & 82 deletions cogs/user_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,85 +321,3 @@ def default(self, obj):
if isinstance(obj, UserConfigFile):
return obj.to_json()
return super().default(self, obj)


class ConfigFileDecoder(json.JSONDecoder):
def __init__(self, config_file: UserConfigFile, *args, **kwargs):
super().__init__(*args, **kwargs)
self.config_file = config_file

def decode(self, json_string):
parsed_data = super().decode(json_string)
if "users" in parsed_data and "lookup" in parsed_data and self.config_file:
return self.config_file.__populate()
return parsed_data


class UserCFG:
CONFIG = CacheConfig()

def is_valid_branch(self, branch: str):
return SUPPORTED_PRODUCTS.has_key(branch)

def lookup(self, branch: str) -> list[int]:
data = self.read()

return data["lookup"][branch]

def make_unique(self, obj: list) -> list:
return list(set(obj))

def subscribe(self, user_id: int, branch: str) -> tuple[bool, str]:
if not self.is_valid_branch(branch):
return False, "Invalid branch"

config = self.read()

lookup = config["lookup"][branch]
if user_id in lookup:
return False, "Already subscribed"

lookup.append(user_id)
lookup = self.make_unique(lookup)

user_id = str(user_id)
user_config = config["users"]
if not user_id in user_config.keys():
user_config[user_id] = self.get_default_user_entry()

watchlist = user_config[user_id]["watchlist"]
watchlist.append(branch)
watchlist = self.make_unique(watchlist)

self.write(config)
return True, "Success"

def get_subscribed(self, user_id: int):
config = self.read()

user_id = str(user_id)
users = config["users"]
if user_id not in users.keys():
users[user_id] = self.get_default_user_entry()

watchlist = config["users"][user_id]["watchlist"]
return watchlist

def unsubscribe(self, user_id: int, branch: str) -> tuple[bool, str]:
if not self.is_valid_branch(branch):
return False, "Invalid branch"

config = self.read()

lookup = config["lookup"][branch]
if user_id not in lookup:
return False, "Not subscribed"

lookup.remove(user_id)

user_id = str(user_id)
user_watchlist = config["users"][user_id]["watchlist"]
user_watchlist.remove(branch)

self.write(config)
return True, "Success"
50 changes: 49 additions & 1 deletion cogs/watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
from .utils import get_discord_timestamp
from .api.twitter import Twitter

START_LOOPS = True
from cogs.ui import WatchlistUI, WatchlistMenuType

START_LOOPS = False

logger = logging.getLogger("discord.cdn.watcher")

Expand Down Expand Up @@ -677,6 +679,28 @@ async def cdn_watchlist(self, ctx: bridge.BridgeApplicationContext):
message, ephemeral=True, delete_after=DELETE_AFTER
)

@watchlist_commands.command(name="edit")
@commands.cooldown(1, COOLDOWN, commands.BucketType.guild)
async def cdn_edit_watchlist(
self, ctx: discord.ApplicationContext, game: SUPPORTED_GAMES
):
"""Returns a graphical editor for your guild's watchlist"""
watchlist = self.guild_cfg.get_guild_watchlist(ctx.guild_id)
menu = WatchlistUI.create_menu(watchlist, game, WatchlistMenuType.GUILD)
if menu is None:
await ctx.respond(
"An error occurred while generating the watchlist editor.",
ephemeral=True,
delete_after=DELETE_AFTER,
)
return

message = f"""# {game.name} Watchlist Editor
Changes are saved when you click out of the menu.
"""

await ctx.respond(message, view=menu, ephemeral=True, delete_after=DELETE_AFTER)

channel_commands = discord.SlashCommandGroup(
name="channel", description="Notification channel commands", guild_only=True
)
Expand Down Expand Up @@ -822,6 +846,30 @@ async def user_unsubscribe(self, ctx: bridge.BridgeApplicationContext, branch: s
message, ephemeral=True, delete_after=DELETE_AFTER
)

@dm_commands.command(name="edit")
@commands.cooldown(1, COOLDOWN, commands.BucketType.user)
async def user_edit_subscribed(
self, ctx: discord.ApplicationContext, game: SUPPORTED_GAMES
):
"""Returns a graphical editor for your personal watchlist."""
with self.user_cfg as config:
watchlist = config.get_watchlist(ctx.author.id)

menu = WatchlistUI.create_menu(watchlist, game, WatchlistMenuType.USER)
if menu is None:
await ctx.respond(
"An error occurred while generating the watchlist editor.",
ephemeral=True,
delete_after=DELETE_AFTER,
)
return

message = f"""# {game.name} Watchlist Editor
Changes are saved when you click out of the menu.
"""

await ctx.respond(message, view=menu, ephemeral=True, delete_after=DELETE_AFTER)

@dm_commands.command(name="view")
@commands.cooldown(1, COOLDOWN, commands.BucketType.user)
async def user_subscribed(self, ctx: bridge.BridgeApplicationContext):
Expand Down

0 comments on commit b1c5ca4

Please sign in to comment.