|
4 | 4 | from pathlib import Path |
5 | 5 |
|
6 | 6 | import discord |
7 | | -import pydantic |
8 | 7 | from redbot.core import commands |
9 | 8 | from redbot.core.i18n import Translator |
10 | 9 |
|
11 | 10 | from ..abc import MixinMeta |
12 | | -from ..common import formatter, models |
| 11 | +from ..common import formatter |
13 | 12 |
|
14 | 13 | _ = Translator("LevelUp", __file__) |
15 | 14 | log = logging.getLogger("red.levelup.dashboard") |
| 15 | +root = Path(__file__).parent |
| 16 | +static = root / "static" |
| 17 | +templates = root / "templates" |
16 | 18 |
|
17 | 19 |
|
18 | | -def dashboard_page(*args, **kwargs): |
19 | | - def decorator(func: t.Callable): |
| 20 | +def dashboard_page(*args: t.Any, **kwargs: t.Any) -> t.Callable[[t.Any], t.Any]: |
| 21 | + def decorator(func: t.Callable) -> t.Callable[[t.Any], t.Any]: |
20 | 22 | func.__dashboard_decorator_params__ = (args, kwargs) |
21 | 23 | return func |
22 | 24 |
|
@@ -74,12 +76,9 @@ async def get_dashboard_leaderboard( |
74 | 76 | "error_message": _("There is no data for the weekly leaderboard yet, please chat a bit first."), |
75 | 77 | } |
76 | 78 |
|
77 | | - parent = Path(__file__).parent |
78 | | - |
79 | | - source_path = parent / "templates" / "leaderboard.html" |
80 | | - static_dir = parent / "static" |
81 | | - js_path = static_dir / "js" / "leaderboard.js" |
82 | | - css_path = static_dir / "css" / "leaderboard.css" |
| 79 | + source_path = templates / "leaderboard.html" |
| 80 | + js_path = static / "js" / "leaderboard.js" |
| 81 | + css_path = static / "css" / "leaderboard.css" |
83 | 82 |
|
84 | 83 | # Inject JS and CSS into the HTML source for full page loads |
85 | 84 | source = ( |
@@ -133,131 +132,74 @@ async def get_dashboard_leaderboard( |
133 | 132 |
|
134 | 133 | @dashboard_page(name="leaderboard", description="Display the guild leaderboard.") |
135 | 134 | async def leaderboard_page( |
136 | | - self, user: discord.User, guild: discord.Guild, stat: str = None, query: t.Optional[str] = None, **kwargs |
| 135 | + self, user: discord.User, guild: discord.Guild, stat: str = None, **kwargs |
137 | 136 | ) -> t.Dict[str, t.Any]: |
138 | 137 | stat = stat if stat is not None and stat in {"exp", "messages", "voice", "stars"} else "exp" |
139 | | - return await self.get_dashboard_leaderboard(user, guild, "lb", stat, query, **kwargs) |
| 138 | + return await self.get_dashboard_leaderboard(user, guild, "lb", stat, **kwargs) |
140 | 139 |
|
141 | 140 | @dashboard_page(name="weekly", description="Display the guild weekly leaderboard.") |
142 | 141 | async def weekly_page( |
143 | | - self, user: discord.User, guild: discord.Guild, stat: str = None, query: t.Optional[str] = None, **kwargs |
| 142 | + self, user: discord.User, guild: discord.Guild, stat: str = None, **kwargs |
144 | 143 | ) -> t.Dict[str, t.Any]: |
145 | 144 | stat = stat if stat is not None and stat in {"exp", "messages", "voice", "stars"} else "exp" |
146 | | - return await self.get_dashboard_leaderboard(user, guild, "weekly", stat, query, **kwargs) |
147 | | - |
148 | | - async def save_settings(self, user: discord.User, guild: discord.Guild, data: dict, **kwargs): |
149 | | - member = guild.get_member(user.id) |
150 | | - if not member: |
151 | | - return { |
152 | | - "status": 1, |
153 | | - "error_title": _("Member not found"), |
154 | | - "error_message": _("You are not a member of this guild."), |
155 | | - } |
156 | | - if not await self.bot.is_admin(member): |
157 | | - return { |
158 | | - "status": 1, |
159 | | - "error_title": _("Insufficient permissions"), |
160 | | - "error_message": _("You need to be an admin to access this page."), |
161 | | - } |
| 145 | + return await self.get_dashboard_leaderboard(user, guild, "weekly", stat, **kwargs) |
162 | 146 |
|
163 | | - try: |
164 | | - new_conf = models.GuildSettings.model_validate(data) |
165 | | - except pydantic.ValidationError as e: |
166 | | - return { |
167 | | - "status": 1, |
168 | | - "error_title": _("Validation error"), |
169 | | - "error_message": str(e), |
170 | | - } |
171 | | - |
172 | | - conf = self.db.get_conf(guild) |
173 | | - for k, v in data.items(): |
174 | | - setattr(conf, k, getattr(new_conf, k, v)) |
175 | | - self.save() |
176 | | - return { |
177 | | - "status": 0, |
178 | | - "success_title": _("Settings saved"), |
179 | | - "success_message": _("The settings have been saved."), |
180 | | - } |
| 147 | + # @dashboard_page(name="settings", description="Configure the leveling system.", methods=("GET", "POST")) |
| 148 | + async def cog_settings(self, user: discord.User, guild: discord.Guild, **kwargs): |
| 149 | + import wtforms # pip install WTForms |
| 150 | + from flask_wtf import FlaskForm # pip install Flask-WTF |
181 | 151 |
|
182 | | - # @dashboard_page(name="settings", description="Configure the leveling system.") |
183 | | - async def get_cog_settings(self, user: discord.User, guild: discord.Guild, **kwargs): |
184 | | - if kwargs.get("save") and (data := kwargs.get("new_data")): |
185 | | - log.info(f"Saving settings for {guild.name} by {user.name}") |
186 | | - return await self.save_settings(user, guild, data, **kwargs) |
187 | 152 | log.info(f"Getting settings for {guild.name} by {user.name}") |
188 | 153 | member = guild.get_member(user.id) |
189 | 154 | if not member: |
| 155 | + log.warning(f"Member {user.name} not found in guild {guild.name}") |
190 | 156 | return { |
191 | 157 | "status": 1, |
192 | 158 | "error_title": _("Member not found"), |
193 | 159 | "error_message": _("You are not a member of this guild."), |
194 | 160 | } |
195 | 161 | if not await self.bot.is_admin(member): |
| 162 | + log.warning(f"Member {user.name} is not an admin in guild {guild.name}") |
196 | 163 | return { |
197 | 164 | "status": 1, |
198 | 165 | "error_title": _("Insufficient permissions"), |
199 | 166 | "error_message": _("You need to be an admin to access this page."), |
200 | 167 | } |
201 | 168 |
|
202 | 169 | conf = self.db.get_conf(guild) |
203 | | - settings = conf.model_dump(mode="json", exclude=["users", "users_weekly"]) |
204 | 170 |
|
205 | | - users = [] |
206 | | - for user in guild.members: |
207 | | - users.append( |
208 | | - { |
209 | | - "id": user.id, |
210 | | - "name": user.name, |
211 | | - "avatar": user.display_avatar.url, |
212 | | - } |
213 | | - ) |
| 171 | + class SettingsForm(kwargs["Form"]): |
| 172 | + def __init__(self): |
| 173 | + super().__init__(prefix="levelup_settings_form_") |
214 | 174 |
|
215 | | - emojis = [] |
216 | | - for emoji in guild.emojis: |
217 | | - emojis.append( |
218 | | - { |
219 | | - "id": emoji.id, |
220 | | - "name": emoji.name, |
221 | | - "url": emoji.url, |
222 | | - } |
| 175 | + # General settings |
| 176 | + enabled = wtforms.BooleanField(_("Enabled:"), default=conf.enabled) |
| 177 | + algo_base = wtforms.IntegerField( |
| 178 | + _("Algorithm Base:"), default=conf.algorithm.base, validators=[wtforms.validators.InputRequired()] |
223 | 179 | ) |
224 | | - |
225 | | - # Add roles data for the settings page |
226 | | - roles = [] |
227 | | - for role in sorted(guild.roles, reverse=True): |
228 | | - if role.is_default(): |
229 | | - continue |
230 | | - roles.append( |
231 | | - { |
232 | | - "id": role.id, |
233 | | - "name": role.name, |
234 | | - "color": str(role.color), |
235 | | - "position": role.position, |
236 | | - } |
| 180 | + algo_multiplier = wtforms.FloatField( |
| 181 | + _("Algorithm Multiplier:"), default=conf.algorithm.exp, validators=[wtforms.validators.InputRequired()] |
237 | 182 | ) |
238 | 183 |
|
239 | | - parent = Path(__file__).parent |
240 | | - source_path = parent / "templates" / "settings.html" |
241 | | - static_dir = parent / "static" |
242 | | - js_path = static_dir / "js" / "settings.js" |
243 | | - css_path = static_dir / "css" / "settings.css" |
| 184 | + # Submit button |
| 185 | + submit = wtforms.SubmitField(_("Save Settings")) |
244 | 186 |
|
245 | | - # Inject JS and CSS into the HTML source for full page loads |
246 | | - source = ( |
247 | | - f"<style>\n{css_path.read_text() if css_path.exists() else ''}\n</style>\n\n" |
248 | | - + source_path.read_text().strip() |
249 | | - + f"\n\n<script>\n{js_path.read_text()}\n</script>" |
250 | | - ) |
| 187 | + form: FlaskForm = SettingsForm() |
251 | 188 |
|
252 | | - payload = { |
| 189 | + # Handle form submission |
| 190 | + if form.validate_on_submit() and await form.validate_dpy_converters(): |
| 191 | + log.info(f"Form validated for {guild.name} by {user.name}") |
| 192 | + conf.enabled = form.enabled.data |
| 193 | + conf.algorithm.base = form.algo_base.data or 100 |
| 194 | + conf.algorithm.exp = form.algo_multiplier.data or 2.0 |
| 195 | + self.save() |
| 196 | + return { |
| 197 | + "status": 0, |
| 198 | + "notifications": [{"message": _("Settings saved"), "category": "success"}], |
| 199 | + "redirect_url": kwargs["request_url"], |
| 200 | + } |
| 201 | + source = (templates / "settings.html").read_text() |
| 202 | + return { |
253 | 203 | "status": 0, |
254 | | - "web_content": { |
255 | | - "source": source, |
256 | | - "expanded": True, |
257 | | - "settings": settings, |
258 | | - "users": users, |
259 | | - "emojis": emojis, |
260 | | - "roles": roles, |
261 | | - }, |
| 204 | + "web_content": {"source": source, "settings_form": form}, |
262 | 205 | } |
263 | | - return payload |
0 commit comments