Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add graphical watchlist editors #19

Merged
merged 4 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -653,6 +655,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 @@ -798,6 +822,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
Loading