Skip to content

Default Settings Types

GigaJoyce provides several ready-to-use Setting implementations under settings.DefaultTypes.
Each type is documented directly from its source docstring, and below you’ll find practical examples of how to compose them into real modules.


settings.DefaultTypes.arr

ArraySetting

Bases: Setting[List[Any]]

A composite setting that manages an array of values interactively.

This setting renders an embed with Add, Remove, and Confirm UI, delegates item creation/edition to a child Setting, and optionally exposes additional “general” settings as extra buttons.

Notes
  • The visual content can be customized via embed_override or update_fn.
  • If locales=True and module_name is set, text goes through the module translator for i18n.
Source code in settings/DefaultTypes/arr.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
class ArraySetting(Setting[List[Any]]):
    """A composite setting that manages an array of values interactively.

    This setting renders an embed with **Add**, **Remove**, and **Confirm** UI,
    delegates item creation/edition to a child `Setting`, and optionally exposes
    additional “general” settings as extra buttons.

    Notes:
        - The visual content can be customized via `embed_override` or
          `update_fn`.
        - If `locales=True` and `module_name` is set, text goes through the
          module translator for i18n.
    """

    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        child: Setting[Any],
        value: Optional[List[Any]] = None,
        general_settings: Optional[List[Setting[Any]]] = None,  # Botões extras de configurações gerais
        embed_override: Optional[Embed] = None,
        update_fn: Optional[Callable[[List[T]], Embed]] = None,
        locales: Optional[bool] = False,
        module_name: Optional[str] = None,
    ):
        """Initialize an `ArraySetting`.

        Args:
            name: Human‑readable name.
            description: Short explanation for help/UX.
            id: Unique persistence key.
            child: Setting used to create/edit **each** array item.
            value: Initial list value.
            general_settings: Optional settings exposed as extra buttons.
            embed_override: Static embed to render instead of the default.
            update_fn: Callable that builds a custom embed from current values.
            locales: Enable i18n of display strings.
            module_name: Module name for translation/emoji lookups.
        """
        super().__init__(name=name, description=description, id=id, locales=locales, module_name=module_name, type_="array")
        self.child = child
        self.value = value or []
        self.general_settings = general_settings or []  # Lista de configurações gerais
        self.embed_override = embed_override
        self.update_fn = update_fn
        self.locales = locales
        self.module_name = module_name

    async def run(self, view: InteractionView) -> List[T]:
        """Run the interactive UI to modify the array.

        Flow:
            1. Render the embed (using `update_fn` or default renderer).
            2. **Add** → delegates to `self.child.run(...)` and appends result.
            3. **Remove** → shows a Select to remove an item by index.
            4. **General** buttons → delegates to each provided setting.
            5. **Confirm** → persists current state and ends the view.

        Args:
            view: Active `InteractionView` to render in.

        Returns:
            The final list of values.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        translate_module = lambda value: value

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            locale_result = self.apply_locale(translate_module=translate_module)
            if isinstance(locale_result, tuple) and len(locale_result) == 3:
                name, description, kwargs = locale_result
            else:
                name, description, kwargs = self.name, self.description, self.kwargs

        if hasattr(self, "child"):
            self.propagate_locales(self.child)

        current_values = self.value or []

        def update_embed():
            if self.update_fn:
                return self.update_fn(current_values)
            elif self.embed_override:
                embed = self.embed_override
                embed.clear_fields()
                for field in self.parse_to_field_list(translate):
                    embed.add_field(name=field["name"], value=field["value"], inline=field["inline"])
                return embed
            else:
                embed = Embed(
                    title=translate("array_setting.title", setting_name=name),
                    description=description,
                    color=0x00FF00,
                )
                for field in self.parse_to_field_list(translate):
                    embed.add_field(name=field["name"], value=field["value"], inline=field["inline"])
                return embed

        embed = update_embed()
        interaction_view = view.clone()


        async def add_callback(interaction: Interaction):
            await interaction.response.defer()
            cloned_view = interaction_view.clone()
            result = await self.child.run(cloned_view)
            cloned_view.destroy()
            if result is not None:
                current_values.append(result)
                self.value = current_values
                await interaction.edit_original_response(embed=update_embed(), view=interaction_view)

        async def remove_callback(interaction: Interaction):
            """
            Callback para remover itens do ArraySetting.
            """
            await interaction.response.defer()
            if not current_values:
                await interaction.followup.send(translate("no_items_to_remove"), ephemeral=True)
                return

            cloned_view = interaction_view.clone()

            # Criando as opções para o Select
            options = [
                SelectOption(label=f"{index + 1}", value=str(index))
                for index in range(len(current_values))
            ]
            select = Select(
                placeholder=translate("select_to_remove"),
                options=options,
                custom_id="remove_select",
            )

            async def remove_value_callback(selected_interaction: Interaction):
                """
                Callback interno para manipular a seleção e remoção de valores.
                """
                await selected_interaction.response.defer()
                try:
                    index = int(selected_interaction.data["values"][0])
                    if 0 <= index < len(current_values):
                        # Removendo o valor selecionado
                        current_values.pop(index)
                        self.value = current_values

                    # Atualizando o embed com os valores restantes
                    await interaction.edit_original_response(
                        embed=update_embed(),
                        view=interaction_view,
                    )
                except Exception as e:
                    view.client.logger.error(f"Erro while removing the value: {e}")
                    await interaction.followup.send(
                        translate("error_removing_value"), ephemeral=True
                    )

            # Associando o callback ao Select
            select.callback = remove_value_callback

            # Atualizando a view para incluir o Select
            cloned_view.clear_items()
            cloned_view.add_item(select)

            await interaction.edit_original_response(embed=update_embed(), view=cloned_view)



        async def general_setting_callback(interaction: Interaction, setting: Setting[Any]):
            await interaction.response.defer()
            cloned_view = interaction_view.clone()
            result = await setting.run(cloned_view)
            cloned_view.destroy()
            if result is not None:
                setting.value = result
                await interaction.edit_original_response(embed=update_embed(), view=interaction_view)

        async def confirm_callback(interaction: Interaction):
            await interaction.response.defer()
            embed = Embed(
                title=translate("settings.confirmed"),
                description=translate("settings.saved_successfully"),
                color=0x00FF00,
            )
            await interaction.edit_original_response(embed=embed, view=None)
            interaction_view.stop()

        add_button = Button(label=translate("add"), style=ButtonStyle.primary, custom_id="add")
        add_button.callback = add_callback

        remove_button = Button(label=translate("remove"), style=ButtonStyle.danger, custom_id="remove")
        remove_button.callback = remove_callback

        confirm_button = Button(label=translate("confirm"), style=ButtonStyle.success, custom_id="confirm")
        confirm_button.callback = confirm_callback


        for general_setting in self.general_settings:
            view.client.logger.info(f"General Seetting: {general_setting}, name: {general_setting.name}, id: {general_setting.id}")
            general_button = Button(
                label=translate_module(general_setting.name),
                style=ButtonStyle.secondary,
                custom_id=f"general_{general_setting.id}",
            )
            general_button.callback = lambda interaction, setting=general_setting: general_setting_callback(interaction, setting)
            interaction_view.add_item(general_button)

        interaction_view.add_item(add_button)
        interaction_view.add_item(remove_button)
        interaction_view.add_item(confirm_button)


        await view.update(embed=embed, components=interaction_view.children)

        await interaction_view.wait()
        return current_values

    def parse_to_field_list(self, translate_module: Optional[callable] = None) -> List[dict]:
        """
        Converts the array values into fields for the embed.
        """
        print(f"Valor do self.value: {self.value}")
        inlined = len(self.value) > 5
        fields = []
        for index, val in enumerate(self.value):
            if translate_module:
                parsed = (
                    self.parse_to_field(val, translate_module)
                    if self.parse_to_field
                    else (str(val) if not isinstance(val, dict) else "\n".join(f"{k}: {v}" for k, v in val.items()))
                )
            else:
                parsed = (
                    self.parse_to_field(val)
                    if self.parse_to_field
                    else (str(val) if not isinstance(val, dict) else "\n".join(f"{k}: {v}" for k, v in val.items()))
                )
            fields.append({"name": f"{index + 1}", "value": parsed, "inline": inlined})
        return fields

    def parse_to_field(self, value: Any, translator: Optional[callable] = None) -> str:
        """
        Converts a single array value into a displayable string format.
        """
        if hasattr(self.child, "parse_to_field") and callable(getattr(self.child, "parse_to_field")):
            if translator:
                return self.child.parse_to_field(value, translator=translator)
            else:
                return self.child.parse_to_field(value)
        elif isinstance(value, dict):
            return "\n".join(f"{k}: {v}" for k, v in value.items())
        else:
            return str(value)


    def parse_to_database(self, value: List[Any]) -> List[Any]:
        """
        Converts the array values into a format suitable for database storage.
        """
        if not isinstance(value, list):
            raise TypeError("Expected value to be a list.")

        self.logger = getattr(self, 'logger', None) or print  # Use logger if available, else fallback to print
        serialized_data = []

        for item in value:
            try:
                if hasattr(self.child, "parse_to_database") and callable(getattr(self.child, "parse_to_database")):
                    self.logger(f"Parsing item with child parse_to_database: {item}")
                    serialized_item = self.child.parse_to_database(item)
                    self.logger(f"Serialized item: {serialized_item}")
                else:
                    self.logger(f"Child does not have parse_to_database. Using raw item: {item}")
                    serialized_item = item
                serialized_data.append(serialized_item)
            except Exception as e:
                self.logger(f"Error while parsing item: {e}")
                raise

        return serialized_data


    def parse_from_database(self, config: List[Any]) -> List[T]:
        """
        Reconstructs the array values from the database.
        """
        if isinstance(config, list):
            return [self.child.parse(val) for val in config]
        return []

    async def parse(self, config: Any, client, guild_data: Any, guild: DiscordGuild) -> List[T]:
        """
        Parses the configuration data from the database or input.
        """
        if isinstance(config, list):
            parsed_values = []
            for item in config:
                parsed_values.append(await self.child.parse(item, client, guild_data, guild))
            return parsed_values
        return []

    def clone(self) -> "ArraySetting":
        return ArraySetting(
            name=self.name,
            description=self.description,
            id=self.id,
            child=self.child.clone(),
            value=self.value[:],
            embed_override=self.embed_override,
            general_settings=self.general_settings,
            update_fn=self.update_fn,
            locales=self.locales,
            module_name=self.module_name,
        )

__init__(name, description, id, child, value=None, general_settings=None, embed_override=None, update_fn=None, locales=False, module_name=None)

Initialize an ArraySetting.

Parameters:

Name Type Description Default
name str

Human‑readable name.

required
description str

Short explanation for help/UX.

required
id str

Unique persistence key.

required
child Setting[Any]

Setting used to create/edit each array item.

required
value Optional[List[Any]]

Initial list value.

None
general_settings Optional[List[Setting[Any]]]

Optional settings exposed as extra buttons.

None
embed_override Optional[Embed]

Static embed to render instead of the default.

None
update_fn Optional[Callable[[List[T]], Embed]]

Callable that builds a custom embed from current values.

None
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module name for translation/emoji lookups.

None
Source code in settings/DefaultTypes/arr.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    child: Setting[Any],
    value: Optional[List[Any]] = None,
    general_settings: Optional[List[Setting[Any]]] = None,  # Botões extras de configurações gerais
    embed_override: Optional[Embed] = None,
    update_fn: Optional[Callable[[List[T]], Embed]] = None,
    locales: Optional[bool] = False,
    module_name: Optional[str] = None,
):
    """Initialize an `ArraySetting`.

    Args:
        name: Human‑readable name.
        description: Short explanation for help/UX.
        id: Unique persistence key.
        child: Setting used to create/edit **each** array item.
        value: Initial list value.
        general_settings: Optional settings exposed as extra buttons.
        embed_override: Static embed to render instead of the default.
        update_fn: Callable that builds a custom embed from current values.
        locales: Enable i18n of display strings.
        module_name: Module name for translation/emoji lookups.
    """
    super().__init__(name=name, description=description, id=id, locales=locales, module_name=module_name, type_="array")
    self.child = child
    self.value = value or []
    self.general_settings = general_settings or []  # Lista de configurações gerais
    self.embed_override = embed_override
    self.update_fn = update_fn
    self.locales = locales
    self.module_name = module_name

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

parse(config, client, guild_data, guild) async

Parses the configuration data from the database or input.

Source code in settings/DefaultTypes/arr.py
308
309
310
311
312
313
314
315
316
317
async def parse(self, config: Any, client, guild_data: Any, guild: DiscordGuild) -> List[T]:
    """
    Parses the configuration data from the database or input.
    """
    if isinstance(config, list):
        parsed_values = []
        for item in config:
            parsed_values.append(await self.child.parse(item, client, guild_data, guild))
        return parsed_values
    return []

parse_from_database(config)

Reconstructs the array values from the database.

Source code in settings/DefaultTypes/arr.py
300
301
302
303
304
305
306
def parse_from_database(self, config: List[Any]) -> List[T]:
    """
    Reconstructs the array values from the database.
    """
    if isinstance(config, list):
        return [self.child.parse(val) for val in config]
    return []

parse_to_database(value)

Converts the array values into a format suitable for database storage.

Source code in settings/DefaultTypes/arr.py
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
def parse_to_database(self, value: List[Any]) -> List[Any]:
    """
    Converts the array values into a format suitable for database storage.
    """
    if not isinstance(value, list):
        raise TypeError("Expected value to be a list.")

    self.logger = getattr(self, 'logger', None) or print  # Use logger if available, else fallback to print
    serialized_data = []

    for item in value:
        try:
            if hasattr(self.child, "parse_to_database") and callable(getattr(self.child, "parse_to_database")):
                self.logger(f"Parsing item with child parse_to_database: {item}")
                serialized_item = self.child.parse_to_database(item)
                self.logger(f"Serialized item: {serialized_item}")
            else:
                self.logger(f"Child does not have parse_to_database. Using raw item: {item}")
                serialized_item = item
            serialized_data.append(serialized_item)
        except Exception as e:
            self.logger(f"Error while parsing item: {e}")
            raise

    return serialized_data

parse_to_field(value, translator=None)

Converts a single array value into a displayable string format.

Source code in settings/DefaultTypes/arr.py
258
259
260
261
262
263
264
265
266
267
268
269
270
def parse_to_field(self, value: Any, translator: Optional[callable] = None) -> str:
    """
    Converts a single array value into a displayable string format.
    """
    if hasattr(self.child, "parse_to_field") and callable(getattr(self.child, "parse_to_field")):
        if translator:
            return self.child.parse_to_field(value, translator=translator)
        else:
            return self.child.parse_to_field(value)
    elif isinstance(value, dict):
        return "\n".join(f"{k}: {v}" for k, v in value.items())
    else:
        return str(value)

parse_to_field_list(translate_module=None)

Converts the array values into fields for the embed.

Source code in settings/DefaultTypes/arr.py
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def parse_to_field_list(self, translate_module: Optional[callable] = None) -> List[dict]:
    """
    Converts the array values into fields for the embed.
    """
    print(f"Valor do self.value: {self.value}")
    inlined = len(self.value) > 5
    fields = []
    for index, val in enumerate(self.value):
        if translate_module:
            parsed = (
                self.parse_to_field(val, translate_module)
                if self.parse_to_field
                else (str(val) if not isinstance(val, dict) else "\n".join(f"{k}: {v}" for k, v in val.items()))
            )
        else:
            parsed = (
                self.parse_to_field(val)
                if self.parse_to_field
                else (str(val) if not isinstance(val, dict) else "\n".join(f"{k}: {v}" for k, v in val.items()))
            )
        fields.append({"name": f"{index + 1}", "value": parsed, "inline": inlined})
    return fields

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Run the interactive UI to modify the array.

Flow
  1. Render the embed (using update_fn or default renderer).
  2. Add → delegates to self.child.run(...) and appends result.
  3. Remove → shows a Select to remove an item by index.
  4. General buttons → delegates to each provided setting.
  5. Confirm → persists current state and ends the view.

Parameters:

Name Type Description Default
view InteractionView

Active InteractionView to render in.

required

Returns:

Type Description
List[T]

The final list of values.

Source code in settings/DefaultTypes/arr.py
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
async def run(self, view: InteractionView) -> List[T]:
    """Run the interactive UI to modify the array.

    Flow:
        1. Render the embed (using `update_fn` or default renderer).
        2. **Add** → delegates to `self.child.run(...)` and appends result.
        3. **Remove** → shows a Select to remove an item by index.
        4. **General** buttons → delegates to each provided setting.
        5. **Confirm** → persists current state and ends the view.

    Args:
        view: Active `InteractionView` to render in.

    Returns:
        The final list of values.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    translate_module = lambda value: value

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        locale_result = self.apply_locale(translate_module=translate_module)
        if isinstance(locale_result, tuple) and len(locale_result) == 3:
            name, description, kwargs = locale_result
        else:
            name, description, kwargs = self.name, self.description, self.kwargs

    if hasattr(self, "child"):
        self.propagate_locales(self.child)

    current_values = self.value or []

    def update_embed():
        if self.update_fn:
            return self.update_fn(current_values)
        elif self.embed_override:
            embed = self.embed_override
            embed.clear_fields()
            for field in self.parse_to_field_list(translate):
                embed.add_field(name=field["name"], value=field["value"], inline=field["inline"])
            return embed
        else:
            embed = Embed(
                title=translate("array_setting.title", setting_name=name),
                description=description,
                color=0x00FF00,
            )
            for field in self.parse_to_field_list(translate):
                embed.add_field(name=field["name"], value=field["value"], inline=field["inline"])
            return embed

    embed = update_embed()
    interaction_view = view.clone()


    async def add_callback(interaction: Interaction):
        await interaction.response.defer()
        cloned_view = interaction_view.clone()
        result = await self.child.run(cloned_view)
        cloned_view.destroy()
        if result is not None:
            current_values.append(result)
            self.value = current_values
            await interaction.edit_original_response(embed=update_embed(), view=interaction_view)

    async def remove_callback(interaction: Interaction):
        """
        Callback para remover itens do ArraySetting.
        """
        await interaction.response.defer()
        if not current_values:
            await interaction.followup.send(translate("no_items_to_remove"), ephemeral=True)
            return

        cloned_view = interaction_view.clone()

        # Criando as opções para o Select
        options = [
            SelectOption(label=f"{index + 1}", value=str(index))
            for index in range(len(current_values))
        ]
        select = Select(
            placeholder=translate("select_to_remove"),
            options=options,
            custom_id="remove_select",
        )

        async def remove_value_callback(selected_interaction: Interaction):
            """
            Callback interno para manipular a seleção e remoção de valores.
            """
            await selected_interaction.response.defer()
            try:
                index = int(selected_interaction.data["values"][0])
                if 0 <= index < len(current_values):
                    # Removendo o valor selecionado
                    current_values.pop(index)
                    self.value = current_values

                # Atualizando o embed com os valores restantes
                await interaction.edit_original_response(
                    embed=update_embed(),
                    view=interaction_view,
                )
            except Exception as e:
                view.client.logger.error(f"Erro while removing the value: {e}")
                await interaction.followup.send(
                    translate("error_removing_value"), ephemeral=True
                )

        # Associando o callback ao Select
        select.callback = remove_value_callback

        # Atualizando a view para incluir o Select
        cloned_view.clear_items()
        cloned_view.add_item(select)

        await interaction.edit_original_response(embed=update_embed(), view=cloned_view)



    async def general_setting_callback(interaction: Interaction, setting: Setting[Any]):
        await interaction.response.defer()
        cloned_view = interaction_view.clone()
        result = await setting.run(cloned_view)
        cloned_view.destroy()
        if result is not None:
            setting.value = result
            await interaction.edit_original_response(embed=update_embed(), view=interaction_view)

    async def confirm_callback(interaction: Interaction):
        await interaction.response.defer()
        embed = Embed(
            title=translate("settings.confirmed"),
            description=translate("settings.saved_successfully"),
            color=0x00FF00,
        )
        await interaction.edit_original_response(embed=embed, view=None)
        interaction_view.stop()

    add_button = Button(label=translate("add"), style=ButtonStyle.primary, custom_id="add")
    add_button.callback = add_callback

    remove_button = Button(label=translate("remove"), style=ButtonStyle.danger, custom_id="remove")
    remove_button.callback = remove_callback

    confirm_button = Button(label=translate("confirm"), style=ButtonStyle.success, custom_id="confirm")
    confirm_button.callback = confirm_callback


    for general_setting in self.general_settings:
        view.client.logger.info(f"General Seetting: {general_setting}, name: {general_setting.name}, id: {general_setting.id}")
        general_button = Button(
            label=translate_module(general_setting.name),
            style=ButtonStyle.secondary,
            custom_id=f"general_{general_setting.id}",
        )
        general_button.callback = lambda interaction, setting=general_setting: general_setting_callback(interaction, setting)
        interaction_view.add_item(general_button)

    interaction_view.add_item(add_button)
    interaction_view.add_item(remove_button)
    interaction_view.add_item(confirm_button)


    await view.update(embed=embed, components=interaction_view.children)

    await interaction_view.wait()
    return current_values

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

tags_setting = ArraySetting(
    name="Allowed tags",
    description="List of allowed tags for message filtering",
    id="allowed_tags",
    child=StringSettingFile(
        name="Tag",
        description="One tag entry",
        id="tag_entry"
    )
)

settings.DefaultTypes.boolean

BooleanSetting

Bases: Setting[bool]

Represents a boolean setting that can be modified via interactions.

Source code in settings/DefaultTypes/boolean.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
class BooleanSetting(Setting[bool]):
    """
    Represents a boolean setting that can be modified via interactions.
    """

    def __init__(self, name: str, description: str, id: str, value: Optional[bool] = None, color: str = "#ffffff", locales: Optional[bool] = False, module_name: Optional[str] = None):
        """Initialize a `BooleanSetting`.

        Args:
            name: Display name.
            description: Short explanation.
            id: Persistence key.
            value: Initial value (defaults to `False`).
            color: Hex color to render the embed.
            locales: Enable i18n of display strings.
            module_name: Module context for translations.
        """
        super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="boolean")
        self.value = value or False
        self.color = color
        self.locales = locales
        self.module_name = module_name

    async def run(self, view: InteractionView) -> bool:
        """Render two buttons to toggle the boolean value.

        Returns the final value when the view ends (or current value on timeout).

        Args:
            view: Active `InteractionView`.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            localized = self.apply_locale(translate_module=translate_module)
            # If apply_locale returns a dict with keys 'name', 'description', 'kwargs'
            if isinstance(localized, dict):
                name = localized.get("name", name)
                description = localized.get("description", description)
                kwargs = localized.get("kwargs", kwargs)

        value = self.value
        embed = Embed(

            title=translate("boolean_setting.title", setting_name=name),
            description=description,
            color=int(self.color.lstrip("#"), 16)
        ).add_field(
            name=translate("current_value"),
            value=translate("enabled") if value else translate("disabled")
        )

        enable = Button(label=translate("enable"), custom_id="activate", style=ButtonStyle.secondary, disabled=value)
        disable = Button(label=translate("disable"), custom_id="deactivate", style=ButtonStyle.secondary, disabled=not value)


        async def button_callback(interaction: Interaction):
            await interaction.response.defer()
            nonlocal value
            value = not value
            view.stop()


        enable.callback = button_callback
        disable.callback = button_callback

        await view.update(embed=embed, components=[enable, disable])

        await view.wait()

        if not view.is_finished():
            # Handle timeout
            embed = Embed(
                title=translate("boolean_setting.title", setting_name=self.name),
                description=translate("timeout"),
                color=int(self.color.lstrip("#"), 16)
            )
            await view.update(embed=embed, components= [])

        return value

    def parse_to_database(self, value: bool) -> bool:
        """
        Prepares the channel for storage in the database.
        """
        return value

__init__(name, description, id, value=None, color='#ffffff', locales=False, module_name=None)

Initialize a BooleanSetting.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

Short explanation.

required
id str

Persistence key.

required
value Optional[bool]

Initial value (defaults to False).

None
color str

Hex color to render the embed.

'#ffffff'
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module context for translations.

None
Source code in settings/DefaultTypes/boolean.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def __init__(self, name: str, description: str, id: str, value: Optional[bool] = None, color: str = "#ffffff", locales: Optional[bool] = False, module_name: Optional[str] = None):
    """Initialize a `BooleanSetting`.

    Args:
        name: Display name.
        description: Short explanation.
        id: Persistence key.
        value: Initial value (defaults to `False`).
        color: Hex color to render the embed.
        locales: Enable i18n of display strings.
        module_name: Module context for translations.
    """
    super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="boolean")
    self.value = value or False
    self.color = color
    self.locales = locales
    self.module_name = module_name

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

clone()

Return a shallow copy of this setting instance.

The copy is constructed by re‑invoking the concrete class __init__ with parameters that exist as attributes on self.

Returns:

Type Description
Setting[T]

A new instance of the concrete Setting subclass.

Source code in settings/Setting.py
230
231
232
233
234
235
236
237
238
239
240
241
242
def clone(self) -> "Setting[T]":
    """Return a shallow copy of this setting instance.

    The copy is constructed by re‑invoking the concrete class `__init__`
    with parameters that exist as attributes on `self`.

    Returns:
        A new instance of the concrete `Setting` subclass.
    """
    cls = self.__class__
    init_params = cls.__init__.__code__.co_varnames[1:]  # Ignora 'self'
    init_args = {key: getattr(self, key) for key in init_params if hasattr(self, key)}
    return cls(**init_args)

parse(config, client, guild_data, guild) async

Optional async hook to parse a value with context.

Subclasses may override this to perform context‑aware parsing (e.g. resolve IDs to Discord objects). Default behavior returns config as‑is.

Parameters:

Name Type Description Default
config Any

Raw config value from DB or input.

required
client ExtendedClient

Extended bot client.

required
guild_data Any

Raw guild document (for extra context if needed).

required
guild Guild

Hydrated guild wrapper.

required

Returns:

Type Description
Awaitable[T]

The parsed value (usually of type T).

Source code in settings/Setting.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
async def parse(self, config: Any, client: ExtendedClient, guild_data: Any, guild: Guild) -> Awaitable[T]:
    """Optional async hook to parse a value with context.

    Subclasses may override this to perform context‑aware parsing (e.g.
    resolve IDs to Discord objects). Default behavior returns `config` as‑is.

    Args:
        config: Raw config value from DB or input.
        client: Extended bot client.
        guild_data: Raw guild document (for extra context if needed).
        guild: Hydrated guild wrapper.

    Returns:
        The parsed value (usually of type `T`).
    """
    return config

parse_from_database(config)

Reconstruct the runtime value from the DB representation.

This is the inverse of parse_to_database.

Parameters:

Name Type Description Default
config Any

Raw stored value from the database.

required

Returns:

Type Description
T

The deserialized runtime value of type T.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def parse_from_database(self, config: Any) -> T:
    """Reconstruct the runtime value from the DB representation.

    This is the inverse of `parse_to_database`.

    Args:
        config: Raw stored value from the database.

    Returns:
        The deserialized runtime value of type `T`.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

parse_to_database(value)

Prepares the channel for storage in the database.

Source code in settings/DefaultTypes/boolean.py
94
95
96
97
98
def parse_to_database(self, value: bool) -> bool:
    """
    Prepares the channel for storage in the database.
    """
    return value

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Render two buttons to toggle the boolean value.

Returns the final value when the view ends (or current value on timeout).

Parameters:

Name Type Description Default
view InteractionView

Active InteractionView.

required
Source code in settings/DefaultTypes/boolean.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
async def run(self, view: InteractionView) -> bool:
    """Render two buttons to toggle the boolean value.

    Returns the final value when the view ends (or current value on timeout).

    Args:
        view: Active `InteractionView`.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        localized = self.apply_locale(translate_module=translate_module)
        # If apply_locale returns a dict with keys 'name', 'description', 'kwargs'
        if isinstance(localized, dict):
            name = localized.get("name", name)
            description = localized.get("description", description)
            kwargs = localized.get("kwargs", kwargs)

    value = self.value
    embed = Embed(

        title=translate("boolean_setting.title", setting_name=name),
        description=description,
        color=int(self.color.lstrip("#"), 16)
    ).add_field(
        name=translate("current_value"),
        value=translate("enabled") if value else translate("disabled")
    )

    enable = Button(label=translate("enable"), custom_id="activate", style=ButtonStyle.secondary, disabled=value)
    disable = Button(label=translate("disable"), custom_id="deactivate", style=ButtonStyle.secondary, disabled=not value)


    async def button_callback(interaction: Interaction):
        await interaction.response.defer()
        nonlocal value
        value = not value
        view.stop()


    enable.callback = button_callback
    disable.callback = button_callback

    await view.update(embed=embed, components=[enable, disable])

    await view.wait()

    if not view.is_finished():
        # Handle timeout
        embed = Embed(
            title=translate("boolean_setting.title", setting_name=self.name),
            description=translate("timeout"),
            color=int(self.color.lstrip("#"), 16)
        )
        await view.update(embed=embed, components= [])

    return value

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

remove_previous_role = BooleanSetting(
    name="Remove previous role",
    description="Whether the bot should remove old roles when assigning new ones",
    id="remove_previous_role",
    value=False,
)

settings.DefaultTypes.channel

ChannelSetting

Bases: Setting[GuildChannel]

Represents a setting for selecting a Discord channel.

Source code in settings/DefaultTypes/channel.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
class ChannelSetting(Setting[GuildChannel]):
    """
    Represents a setting for selecting a Discord channel.
    """

    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        channel_types: Optional[List[type]] = None,
        value: Optional[GuildChannel] = None,
        max_values: int = 1,
        min_values: int = 1,
        color: str = "#ffffff",
        locales: Optional[bool] = False,
        module_name: Optional[str] = None,
    ):
        super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="channel")
        self.channel_types = channel_types or [TextChannel]
        self.value = value
        self.max_values = max_values
        self.min_values = min_values
        self.color = color
        self.locales = locales
        self.module_name = module_name

    async def run(self, view: InteractionView) -> Optional[GuildChannel]:
        """Initialize a `ChannelSetting`.

        Args:
            name: Display name.
            description: Description text.
            id: Persistence key.
            channel_types: Allowed channel classes (default: `[TextChannel]`).
            value: Preselected channel.
            max_values: Max selections in the Select.
            min_values: Min selections in the Select.
            color: Hex embed color.
            locales: Enable i18n of display strings.
            module_name: Module context for translations.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)


        # Placeholder e mensagem inicial
        select_placeholder = translate("select_channel.placeholder")
        current_channel = (
            translate("select_channel.current", channel_name=self.value.name)
            if self.value
            else translate("select_channel.none")
        )

        embed = Embed(
            title=translate("settings.configure", setting_name=name),
            description=f"{description}\n\n{current_channel}",
            color=int(self.color.lstrip("#"), 16),
        )

        # Menu de seleção de canais
        channel_options = [
            SelectOption(label=channel.name, value=str(channel.id))
            for channel in view.interaction.guild.channels
            if isinstance(channel, tuple(self.channel_types))
        ]
        if not channel_options:
            await view.interaction.response.send_message(
                translate("select_channel.no_channels"), ephemeral=True
            )
            return None

        channel_select_menu = Select(
            placeholder=select_placeholder,
            min_values=self.min_values,
            max_values=self.max_values,
            options=channel_options,
        )

        # Botão de confirmação
        confirm_button = Button(
            label=translate("confirm"),
            style=ButtonStyle.success,
        )

        async def confirm_callback(interaction):
            """
            Callback para confirmar a seleção de canal.
            """
            selected_channel_id = channel_select_menu.values[0] 
            selected_channel = view.interaction.guild.get_channel(int(selected_channel_id))
            if selected_channel:
                self.value = selected_channel
                embed.description = translate(
                    "select_channel.updated", channel_name=selected_channel.name
                )
                await interaction.response.edit_message(embed=embed, view=None)
                view.stop()
            else:
                await interaction.response.send_message(
                    translate("select_channel.error"), ephemeral=True
                )

        async def timeout_callback():
            """
            Callback para timeout.
            """
            embed.description = translate("select_channel.timeout")
            await view.interaction.edit_original_message(embed=embed, view=None)

        # Configuração do menu e callbacks
        channel_select_menu.callback = confirm_callback
        view.add_item(channel_select_menu)
        view.add_item(confirm_button)

        # Envia a interação
        await view.send(embed=embed)
        await view.wait(timeout_callback)

        return self.value

    def parse_to_database(self, value: GuildChannel) -> str:
        """
        Prepares the channel for storage in the database.
        """
        return str(value.id)

    async def parse(self, config: str, guild: DiscordGuild) -> Optional[GuildChannel]:
        """
        Parses a channel ID into a GuildChannel object.
        """
        try:
            return await guild.fetch_channel(int(config))
        except Exception:
            return None

    def parse_to_field(self, value: GuildChannel) -> str:
        """
        Converts the value to a display-friendly string.
        """
        return f"Name: {value.name}\nID: {value.id}"

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

clone()

Return a shallow copy of this setting instance.

The copy is constructed by re‑invoking the concrete class __init__ with parameters that exist as attributes on self.

Returns:

Type Description
Setting[T]

A new instance of the concrete Setting subclass.

Source code in settings/Setting.py
230
231
232
233
234
235
236
237
238
239
240
241
242
def clone(self) -> "Setting[T]":
    """Return a shallow copy of this setting instance.

    The copy is constructed by re‑invoking the concrete class `__init__`
    with parameters that exist as attributes on `self`.

    Returns:
        A new instance of the concrete `Setting` subclass.
    """
    cls = self.__class__
    init_params = cls.__init__.__code__.co_varnames[1:]  # Ignora 'self'
    init_args = {key: getattr(self, key) for key in init_params if hasattr(self, key)}
    return cls(**init_args)

parse(config, guild) async

Parses a channel ID into a GuildChannel object.

Source code in settings/DefaultTypes/channel.py
148
149
150
151
152
153
154
155
async def parse(self, config: str, guild: DiscordGuild) -> Optional[GuildChannel]:
    """
    Parses a channel ID into a GuildChannel object.
    """
    try:
        return await guild.fetch_channel(int(config))
    except Exception:
        return None

parse_from_database(config)

Reconstruct the runtime value from the DB representation.

This is the inverse of parse_to_database.

Parameters:

Name Type Description Default
config Any

Raw stored value from the database.

required

Returns:

Type Description
T

The deserialized runtime value of type T.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def parse_from_database(self, config: Any) -> T:
    """Reconstruct the runtime value from the DB representation.

    This is the inverse of `parse_to_database`.

    Args:
        config: Raw stored value from the database.

    Returns:
        The deserialized runtime value of type `T`.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

parse_to_database(value)

Prepares the channel for storage in the database.

Source code in settings/DefaultTypes/channel.py
142
143
144
145
146
def parse_to_database(self, value: GuildChannel) -> str:
    """
    Prepares the channel for storage in the database.
    """
    return str(value.id)

parse_to_field(value)

Converts the value to a display-friendly string.

Source code in settings/DefaultTypes/channel.py
157
158
159
160
161
def parse_to_field(self, value: GuildChannel) -> str:
    """
    Converts the value to a display-friendly string.
    """
    return f"Name: {value.name}\nID: {value.id}"

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Initialize a ChannelSetting.

Parameters:

Name Type Description Default
name

Display name.

required
description

Description text.

required
id

Persistence key.

required
channel_types

Allowed channel classes (default: [TextChannel]).

required
value

Preselected channel.

required
max_values

Max selections in the Select.

required
min_values

Min selections in the Select.

required
color

Hex embed color.

required
locales

Enable i18n of display strings.

required
module_name

Module context for translations.

required
Source code in settings/DefaultTypes/channel.py
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
async def run(self, view: InteractionView) -> Optional[GuildChannel]:
    """Initialize a `ChannelSetting`.

    Args:
        name: Display name.
        description: Description text.
        id: Persistence key.
        channel_types: Allowed channel classes (default: `[TextChannel]`).
        value: Preselected channel.
        max_values: Max selections in the Select.
        min_values: Min selections in the Select.
        color: Hex embed color.
        locales: Enable i18n of display strings.
        module_name: Module context for translations.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        name, description, kwargs = self.apply_locale(translate_module=translate_module)


    # Placeholder e mensagem inicial
    select_placeholder = translate("select_channel.placeholder")
    current_channel = (
        translate("select_channel.current", channel_name=self.value.name)
        if self.value
        else translate("select_channel.none")
    )

    embed = Embed(
        title=translate("settings.configure", setting_name=name),
        description=f"{description}\n\n{current_channel}",
        color=int(self.color.lstrip("#"), 16),
    )

    # Menu de seleção de canais
    channel_options = [
        SelectOption(label=channel.name, value=str(channel.id))
        for channel in view.interaction.guild.channels
        if isinstance(channel, tuple(self.channel_types))
    ]
    if not channel_options:
        await view.interaction.response.send_message(
            translate("select_channel.no_channels"), ephemeral=True
        )
        return None

    channel_select_menu = Select(
        placeholder=select_placeholder,
        min_values=self.min_values,
        max_values=self.max_values,
        options=channel_options,
    )

    # Botão de confirmação
    confirm_button = Button(
        label=translate("confirm"),
        style=ButtonStyle.success,
    )

    async def confirm_callback(interaction):
        """
        Callback para confirmar a seleção de canal.
        """
        selected_channel_id = channel_select_menu.values[0] 
        selected_channel = view.interaction.guild.get_channel(int(selected_channel_id))
        if selected_channel:
            self.value = selected_channel
            embed.description = translate(
                "select_channel.updated", channel_name=selected_channel.name
            )
            await interaction.response.edit_message(embed=embed, view=None)
            view.stop()
        else:
            await interaction.response.send_message(
                translate("select_channel.error"), ephemeral=True
            )

    async def timeout_callback():
        """
        Callback para timeout.
        """
        embed.description = translate("select_channel.timeout")
        await view.interaction.edit_original_message(embed=embed, view=None)

    # Configuração do menu e callbacks
    channel_select_menu.callback = confirm_callback
    view.add_item(channel_select_menu)
    view.add_item(confirm_button)

    # Envia a interação
    await view.send(embed=embed)
    await view.wait(timeout_callback)

    return self.value

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

log_channel = ChannelSetting(
    name="Log channel",
    description="Channel where logs will be sent",
    id="log_channel"
)

settings.DefaultTypes.dynamicSelect

DynamicSelectSetting

Bases: Setting[List[str]]

Split a list into rows of size.

Source code in settings/DefaultTypes/dynamicSelect.py
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class DynamicSelectSetting(Setting[List[str]]):
    """Split a list into rows of `size`."""

    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        get_fn: Callable[[Member], List[str]],
        max_values: Optional[int] = 1,
        min_values: Optional[int] = 1,
        style: Optional[str] = "StringSelectMenu",
        value: Optional[List[str]] = None,
        permission: Optional[int] = None,
    ):
        """Initialize a `DynamicSelectSetting`.

        Args:
            name: Display name.
            description: Description text.
            id: Persistence key.
            get_fn: Callable that returns option labels for the current member.
            max_values: Maximum options selectable.
            min_values: Minimum options selectable.
            style: "StringSelectMenu" or "Button".
            value: Initial selection.
            permission: Optional permission flag/bit.
        """
        super().__init__(name=name, description=description, id=id, permission=permission, type_="dynamicSelect")

        self.get_fn = get_fn
        self.max_values = max_values
        self.min_values = min_values
        self.style = style
        self.value = value or []
        self.permission = permission


    async def run(self, view: InteractionView) -> List[str]:
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)

        member = await self.bot.guild_manager.fetch_member(view.interaction.member.id, view.interaction.guild.id)
        options = await self.get_fn(member)

        if not options:
            embed = Embed(
                title=translate("dynamic_select.no_options"),
                color=0xFF0000
            )
            await view.interaction.response.send_message(embed=embed, ephemeral=True)
            return []

        if self.style == "StringSelectMenu":
            return await self._run_string_select_menu(view.interaction, options, translate, (name, description, kwargs))
        elif self.style == "Button":
            return await self._run_button_style(view.interaction, options, translate, (name, description, kwargs))
        else:
            raise ValueError("Unsupported style for DynamicSelectSetting")

    async def _run_string_select_menu(self, interaction: Interaction, options: List[str], translate: Callable, describers: Tuple[str]) -> List[str]:
        name, description, kwargs = describers

        select_menu = SelectMenu(
            custom_id="select",
            placeholder=translate("dynamic_select.placeholder"),
            options=[SelectOption(label=option, value=option) for option in options[:25]],
            max_values=min(self.max_values, len(options)),
            min_values=min(self.min_values, len(options)),
        )
        row = ActionRow(components=[select_menu])

        embed = Embed(
            title=translate("dynamic_select.configure", setting_name=name),
            description=description,
            color=0x00FF00
        )

        view = InteractionView(interaction)
        await view.update(embed=embed, components=[row])

        async def on_select(inter: Interaction):
            await inter.response.defer()
            self.value = inter.data.get("values", [])
            await view.stop()

        view.on("select", on_select)
        await view.wait()

        return self.value

    async def _run_button_style(self, interaction: Interaction, options: List[str], translate: Callable, describers: Tuple[str]) -> List[str]:
        name, description, kwargs = describers

        buttons = [
            Button(label=option, custom_id=option, style=ButtonStyle.primary)
            for option in options[:20]
        ]
        rows = [ActionRow(components=row) for row in chunk_array(buttons, 5)]

        embed = Embed(
            title=translate("dynamic_select.configure", setting_name=name),
            description=description,
            color=0x00FF00
        )

        view = InteractionView(interaction)
        await view.update(embed=embed, components=rows)

        async def on_button_click(inter: Interaction):
            await inter.response.defer()
            self.value = [inter.data["custom_id"]]
            await view.stop()

        view.on("any", on_button_click)
        await view.wait()

        return self.value

    def clone(self) -> "DynamicSelectSetting":
        return DynamicSelectSetting(
            name=self.name,
            description=self.description,
            id=self.id,
            get_fn=self.get_fn,
            max_values=self.max_values,
            min_values=self.min_values,
            style=self.style,
            value=self.value,
            bot=self.bot,
        )

__init__(name, description, id, get_fn, max_values=1, min_values=1, style='StringSelectMenu', value=None, permission=None)

Initialize a DynamicSelectSetting.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

Description text.

required
id str

Persistence key.

required
get_fn Callable[[Member], List[str]]

Callable that returns option labels for the current member.

required
max_values Optional[int]

Maximum options selectable.

1
min_values Optional[int]

Minimum options selectable.

1
style Optional[str]

"StringSelectMenu" or "Button".

'StringSelectMenu'
value Optional[List[str]]

Initial selection.

None
permission Optional[int]

Optional permission flag/bit.

None
Source code in settings/DefaultTypes/dynamicSelect.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    get_fn: Callable[[Member], List[str]],
    max_values: Optional[int] = 1,
    min_values: Optional[int] = 1,
    style: Optional[str] = "StringSelectMenu",
    value: Optional[List[str]] = None,
    permission: Optional[int] = None,
):
    """Initialize a `DynamicSelectSetting`.

    Args:
        name: Display name.
        description: Description text.
        id: Persistence key.
        get_fn: Callable that returns option labels for the current member.
        max_values: Maximum options selectable.
        min_values: Minimum options selectable.
        style: "StringSelectMenu" or "Button".
        value: Initial selection.
        permission: Optional permission flag/bit.
    """
    super().__init__(name=name, description=description, id=id, permission=permission, type_="dynamicSelect")

    self.get_fn = get_fn
    self.max_values = max_values
    self.min_values = min_values
    self.style = style
    self.value = value or []
    self.permission = permission

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

parse(config, client, guild_data, guild) async

Optional async hook to parse a value with context.

Subclasses may override this to perform context‑aware parsing (e.g. resolve IDs to Discord objects). Default behavior returns config as‑is.

Parameters:

Name Type Description Default
config Any

Raw config value from DB or input.

required
client ExtendedClient

Extended bot client.

required
guild_data Any

Raw guild document (for extra context if needed).

required
guild Guild

Hydrated guild wrapper.

required

Returns:

Type Description
Awaitable[T]

The parsed value (usually of type T).

Source code in settings/Setting.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
async def parse(self, config: Any, client: ExtendedClient, guild_data: Any, guild: Guild) -> Awaitable[T]:
    """Optional async hook to parse a value with context.

    Subclasses may override this to perform context‑aware parsing (e.g.
    resolve IDs to Discord objects). Default behavior returns `config` as‑is.

    Args:
        config: Raw config value from DB or input.
        client: Extended bot client.
        guild_data: Raw guild document (for extra context if needed).
        guild: Hydrated guild wrapper.

    Returns:
        The parsed value (usually of type `T`).
    """
    return config

parse_from_database(config)

Reconstruct the runtime value from the DB representation.

This is the inverse of parse_to_database.

Parameters:

Name Type Description Default
config Any

Raw stored value from the database.

required

Returns:

Type Description
T

The deserialized runtime value of type T.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def parse_from_database(self, config: Any) -> T:
    """Reconstruct the runtime value from the DB representation.

    This is the inverse of `parse_to_database`.

    Args:
        config: Raw stored value from the database.

    Returns:
        The deserialized runtime value of type `T`.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

parse_to_database(value)

Serialize the runtime value into a DB‑friendly representation.

This is the inverse of parse_from_database.

Parameters:

Name Type Description Default
value T

The runtime value.

required

Returns:

Type Description
Any

A JSON‑serializable value suitable for your database.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
def parse_to_database(self, value: T) -> Any:
    """Serialize the runtime value into a DB‑friendly representation.

    This is the inverse of `parse_from_database`.

    Args:
        value: The runtime value.

    Returns:
        A JSON‑serializable value suitable for your database.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

chunk_array(arr, size)

Split a list into rows of size.

Source code in settings/DefaultTypes/dynamicSelect.py
15
16
17
def chunk_array(arr: List[Any], size: int) -> List[List[Any]]:
    """Split a list into rows of `size`."""
    return [arr[i:i + size] for i in range(0, len(arr), size)]

Example

category_setting = DynamicSelectSetting(
    name="Category selector",
    description="Pick a category from a dynamic list",
    id="category_selector",
    options_fn=lambda: ["News", "Updates", "Events"]
)

settings.DefaultTypes.embed

EmbedSettingFile

Bases: Setting[Embed]

Interactive setting for building a Discord Embed with a wizard.

Source code in settings/DefaultTypes/embed.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
class EmbedSettingFile(Setting[Embed]):
    """Interactive setting for building a Discord `Embed` with a wizard."""

    def __init__(self, name: str, description: str, id: str, module_name: str = None, locales: Optional[bool] = False, value: Embed = None):
        """Initialize an `EmbedSettingFile`.

        Args:
            name: Display name.
            description: UX text.
            id: Persistence key.
            module_name: Module context for translations.
            locales: Enable i18n of display strings.
            value: Initial embed (used to prefill the editor).
        """
        super().__init__(name=name, description=description, id=id, type_="embed")
        self.value = value
        self.module_name = module_name
        self.locales = locales

    async def run(self, view: InteractionView) -> Embed:
        """Open the interactive embed creator and return the result.

        Args:
            view: Active `InteractionView`.

        Returns:
            The resulting `Embed`.

        Raises:
            ValueError: If creation is aborted or fails.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)

        embed = await EmbedCreator(
            view=view,
            check=lambda m: m.author.id == view.interaction.user.id,
            options={
                "shouldComplete": True,
                "data": self.value.to_dict() if self.value else None,
            },
        ).catch(lambda _: None
                )

        if not embed:
            raise ValueError(translate("error.failed_to_create_embed"))

        return embed

    def parse_to_database(self, value: Embed) -> dict:
        """
        Converts the embed to a database-storable format.

        Args:
            value (Embed): The embed to be parsed.

        Returns:
            dict: The parsed embed in dictionary format.
        """
        return value.to_dict()

    def parse(self, config: dict) -> Embed:
        """
        Parses a stored configuration back into an Embed object.

        Args:
            config (dict): The configuration dictionary.

        Returns:
            Embed: The parsed Embed object.
        """
        return Embed.from_dict(config)

    def parse_to_field(self, value: Embed, translate: Callable) -> str:
        """
        Converts the embed to a concise string representation.

        Args:
            value (Embed): The embed to be represented.

        Returns:
            str: A concise description of the embed.
        """
        description = value.description or translate("embed.no_description")
        truncated_description = (
            description[:55] + "..." if len(description) > 55 else description
        )
        return f"{translate('embed.title')}: {value.title}\n{translate('embed.description')}: {truncated_description}"

    def clone(self) -> "EmbedSettingFile":
        """
        Clones the current embed setting.

        Returns:
            EmbedSettingFile: A clone of this setting.
        """
        return EmbedSettingFile(
            name=self.name, description=self.description, id=self.id, bot=self.bot, value=self.value, locales=self.locales, module_name=self.module_name
        )

__init__(name, description, id, module_name=None, locales=False, value=None)

Initialize an EmbedSettingFile.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

UX text.

required
id str

Persistence key.

required
module_name str

Module context for translations.

None
locales Optional[bool]

Enable i18n of display strings.

False
value Embed

Initial embed (used to prefill the editor).

None
Source code in settings/DefaultTypes/embed.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def __init__(self, name: str, description: str, id: str, module_name: str = None, locales: Optional[bool] = False, value: Embed = None):
    """Initialize an `EmbedSettingFile`.

    Args:
        name: Display name.
        description: UX text.
        id: Persistence key.
        module_name: Module context for translations.
        locales: Enable i18n of display strings.
        value: Initial embed (used to prefill the editor).
    """
    super().__init__(name=name, description=description, id=id, type_="embed")
    self.value = value
    self.module_name = module_name
    self.locales = locales

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

clone()

Clones the current embed setting.

Returns:

Name Type Description
EmbedSettingFile EmbedSettingFile

A clone of this setting.

Source code in settings/DefaultTypes/embed.py
102
103
104
105
106
107
108
109
110
111
def clone(self) -> "EmbedSettingFile":
    """
    Clones the current embed setting.

    Returns:
        EmbedSettingFile: A clone of this setting.
    """
    return EmbedSettingFile(
        name=self.name, description=self.description, id=self.id, bot=self.bot, value=self.value, locales=self.locales, module_name=self.module_name
    )

parse(config)

Parses a stored configuration back into an Embed object.

Parameters:

Name Type Description Default
config dict

The configuration dictionary.

required

Returns:

Name Type Description
Embed Embed

The parsed Embed object.

Source code in settings/DefaultTypes/embed.py
74
75
76
77
78
79
80
81
82
83
84
def parse(self, config: dict) -> Embed:
    """
    Parses a stored configuration back into an Embed object.

    Args:
        config (dict): The configuration dictionary.

    Returns:
        Embed: The parsed Embed object.
    """
    return Embed.from_dict(config)

parse_from_database(config)

Reconstruct the runtime value from the DB representation.

This is the inverse of parse_to_database.

Parameters:

Name Type Description Default
config Any

Raw stored value from the database.

required

Returns:

Type Description
T

The deserialized runtime value of type T.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def parse_from_database(self, config: Any) -> T:
    """Reconstruct the runtime value from the DB representation.

    This is the inverse of `parse_to_database`.

    Args:
        config: Raw stored value from the database.

    Returns:
        The deserialized runtime value of type `T`.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

parse_to_database(value)

Converts the embed to a database-storable format.

Parameters:

Name Type Description Default
value Embed

The embed to be parsed.

required

Returns:

Name Type Description
dict dict

The parsed embed in dictionary format.

Source code in settings/DefaultTypes/embed.py
62
63
64
65
66
67
68
69
70
71
72
def parse_to_database(self, value: Embed) -> dict:
    """
    Converts the embed to a database-storable format.

    Args:
        value (Embed): The embed to be parsed.

    Returns:
        dict: The parsed embed in dictionary format.
    """
    return value.to_dict()

parse_to_field(value, translate)

Converts the embed to a concise string representation.

Parameters:

Name Type Description Default
value Embed

The embed to be represented.

required

Returns:

Name Type Description
str str

A concise description of the embed.

Source code in settings/DefaultTypes/embed.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def parse_to_field(self, value: Embed, translate: Callable) -> str:
    """
    Converts the embed to a concise string representation.

    Args:
        value (Embed): The embed to be represented.

    Returns:
        str: A concise description of the embed.
    """
    description = value.description or translate("embed.no_description")
    truncated_description = (
        description[:55] + "..." if len(description) > 55 else description
    )
    return f"{translate('embed.title')}: {value.title}\n{translate('embed.description')}: {truncated_description}"

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Open the interactive embed creator and return the result.

Parameters:

Name Type Description Default
view InteractionView

Active InteractionView.

required

Returns:

Type Description
Embed

The resulting Embed.

Raises:

Type Description
ValueError

If creation is aborted or fails.

Source code in settings/DefaultTypes/embed.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
async def run(self, view: InteractionView) -> Embed:
    """Open the interactive embed creator and return the result.

    Args:
        view: Active `InteractionView`.

    Returns:
        The resulting `Embed`.

    Raises:
        ValueError: If creation is aborted or fails.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        name, description, kwargs = self.apply_locale(translate_module=translate_module)

    embed = await EmbedCreator(
        view=view,
        check=lambda m: m.author.id == view.interaction.user.id,
        options={
            "shouldComplete": True,
            "data": self.value.to_dict() if self.value else None,
        },
    ).catch(lambda _: None
            )

    if not embed:
        raise ValueError(translate("error.failed_to_create_embed"))

    return embed

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

welcome_embed = EmbedSetting(
    name="Welcome embed",
    description="Embed shown to new members",
    id="welcome_embed"
)

settings.DefaultTypes.member

MemberSetting

Bases: Setting[GuildMember]

Interactive setting to pick a guild member.

Source code in settings/DefaultTypes/member.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
class MemberSetting(Setting[GuildMember]):
    """Interactive setting to pick a guild member."""
    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        max_select: Optional[int] = 1,
        min_select: Optional[int] = 1,
        placeholder: Optional[str] = None,
        embed_description: Optional[str] = None,
        color: Optional[str] = "#ffffff",
        value: Optional[GuildMember] = None,
        locales: Optional[bool] = False,
        module_name: Optional[str] = None,
    ):
        """Initialize a `MemberSetting`.

        Args:
            name: Display name.
            description: Description text.
            id: Persistence key.
            max_select: Max users selectable.
            min_select: Min users selectable.
            placeholder: Custom placeholder text.
            embed_description: Override for the embed description.
            color: Hex embed color.
            value: Preselected member.
            locales: Enable i18n of display strings.
            module_name: Module context for translations.
        """
        super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="member")
        self.max = max_select
        self.min = min_select
        self.placeholder = placeholder or "Selecione um membro"
        self.embed_description = embed_description
        self.color = color
        self.value = value
        self.locales = locales
        self.module_name = module_name


    async def run(self, view: InteractionView) -> GuildMember:
        """
        Runs the interactive session for selecting a guild member.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)

        member_select_menu = StringSelectMenu(
            custom_id="select",
            placeholder=translate("member.placeholder"),
            min_values=self.min,
            max_values=self.max,
            options=[],
        )
        row = ActionRow(components=[member_select_menu])
        embed = Embed(
            title=translate("member.title", setting_name=name),
            description=description or translate("member.description"),
            color=int(self.color.lstrip("#"), 16),
        )

        row_view = InteractionView()
        row_view.add_item = row
        await view.update({"embeds": [embed], "components": [row_view.children]})

        async def handle_select(menu_interaction: SelectMenuInteraction):
            """
            Handles the user selection.
            """
            await menu_interaction.response.defer()
            selected_members = [
                f"{view.interaction.guild.get_member(user_id).mention}" for user_id in menu_interaction.values
            ]
            if len(selected_members) > 1:
                embed.description = translate(
                    "member.multiple_selected", members=", ".join(selected_members)
                )
            else:
                embed.description = translate(
                    "member.single_selected", member=selected_members[0]
                )
            await view.update({"embeds": [embed], "components": []})
            view.stop()
            return view.interaction.guild.get_member(menu_interaction.values[0])

        view.on("select", handle_select)
        await view.wait()

        return None

    async def parse(self, config: str, client: Any, data: Any, guild: Guild) -> GuildMember:
        """
        Parses a member ID from the configuration and fetches the member.
        """
        member = guild.get_member(int(config)) or await guild.fetch_member(int(config))
        if not member:
            raise ValueError("Member not found")
        return member

    def parse_to_database(self, value: GuildMember) -> str:
        """
        Parses the member to its database representation.
        """
        return str(value.id)

    def parse_to_field(self, value: GuildMember) -> str:
        """
        Parses the member to a displayable field.
        """
        ...

    def clone(self) -> "MemberSetting":
        """
        Clones the current setting instance.
        """
        return MemberSetting(
            name=self.name,
            description=self.description,
            id=self.id,
            max_select=self.max,
            min_select=self.min,
            placeholder=self.placeholder,
            embed_description=self.embed_description,
            color=self.color,
            value=self.value,
        )

__init__(name, description, id, max_select=1, min_select=1, placeholder=None, embed_description=None, color='#ffffff', value=None, locales=False, module_name=None)

Initialize a MemberSetting.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

Description text.

required
id str

Persistence key.

required
max_select Optional[int]

Max users selectable.

1
min_select Optional[int]

Min users selectable.

1
placeholder Optional[str]

Custom placeholder text.

None
embed_description Optional[str]

Override for the embed description.

None
color Optional[str]

Hex embed color.

'#ffffff'
value Optional[GuildMember]

Preselected member.

None
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module context for translations.

None
Source code in settings/DefaultTypes/member.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    max_select: Optional[int] = 1,
    min_select: Optional[int] = 1,
    placeholder: Optional[str] = None,
    embed_description: Optional[str] = None,
    color: Optional[str] = "#ffffff",
    value: Optional[GuildMember] = None,
    locales: Optional[bool] = False,
    module_name: Optional[str] = None,
):
    """Initialize a `MemberSetting`.

    Args:
        name: Display name.
        description: Description text.
        id: Persistence key.
        max_select: Max users selectable.
        min_select: Min users selectable.
        placeholder: Custom placeholder text.
        embed_description: Override for the embed description.
        color: Hex embed color.
        value: Preselected member.
        locales: Enable i18n of display strings.
        module_name: Module context for translations.
    """
    super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="member")
    self.max = max_select
    self.min = min_select
    self.placeholder = placeholder or "Selecione um membro"
    self.embed_description = embed_description
    self.color = color
    self.value = value
    self.locales = locales
    self.module_name = module_name

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

clone()

Clones the current setting instance.

Source code in settings/DefaultTypes/member.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def clone(self) -> "MemberSetting":
    """
    Clones the current setting instance.
    """
    return MemberSetting(
        name=self.name,
        description=self.description,
        id=self.id,
        max_select=self.max,
        min_select=self.min,
        placeholder=self.placeholder,
        embed_description=self.embed_description,
        color=self.color,
        value=self.value,
    )

parse(config, client, data, guild) async

Parses a member ID from the configuration and fetches the member.

Source code in settings/DefaultTypes/member.py
114
115
116
117
118
119
120
121
async def parse(self, config: str, client: Any, data: Any, guild: Guild) -> GuildMember:
    """
    Parses a member ID from the configuration and fetches the member.
    """
    member = guild.get_member(int(config)) or await guild.fetch_member(int(config))
    if not member:
        raise ValueError("Member not found")
    return member

parse_from_database(config)

Reconstruct the runtime value from the DB representation.

This is the inverse of parse_to_database.

Parameters:

Name Type Description Default
config Any

Raw stored value from the database.

required

Returns:

Type Description
T

The deserialized runtime value of type T.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def parse_from_database(self, config: Any) -> T:
    """Reconstruct the runtime value from the DB representation.

    This is the inverse of `parse_to_database`.

    Args:
        config: Raw stored value from the database.

    Returns:
        The deserialized runtime value of type `T`.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

parse_to_database(value)

Parses the member to its database representation.

Source code in settings/DefaultTypes/member.py
123
124
125
126
127
def parse_to_database(self, value: GuildMember) -> str:
    """
    Parses the member to its database representation.
    """
    return str(value.id)

parse_to_field(value)

Parses the member to a displayable field.

Source code in settings/DefaultTypes/member.py
129
130
131
132
133
def parse_to_field(self, value: GuildMember) -> str:
    """
    Parses the member to a displayable field.
    """
    ...

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Runs the interactive session for selecting a guild member.

Source code in settings/DefaultTypes/member.py
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
async def run(self, view: InteractionView) -> GuildMember:
    """
    Runs the interactive session for selecting a guild member.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        name, description, kwargs = self.apply_locale(translate_module=translate_module)

    member_select_menu = StringSelectMenu(
        custom_id="select",
        placeholder=translate("member.placeholder"),
        min_values=self.min,
        max_values=self.max,
        options=[],
    )
    row = ActionRow(components=[member_select_menu])
    embed = Embed(
        title=translate("member.title", setting_name=name),
        description=description or translate("member.description"),
        color=int(self.color.lstrip("#"), 16),
    )

    row_view = InteractionView()
    row_view.add_item = row
    await view.update({"embeds": [embed], "components": [row_view.children]})

    async def handle_select(menu_interaction: SelectMenuInteraction):
        """
        Handles the user selection.
        """
        await menu_interaction.response.defer()
        selected_members = [
            f"{view.interaction.guild.get_member(user_id).mention}" for user_id in menu_interaction.values
        ]
        if len(selected_members) > 1:
            embed.description = translate(
                "member.multiple_selected", members=", ".join(selected_members)
            )
        else:
            embed.description = translate(
                "member.single_selected", member=selected_members[0]
            )
        await view.update({"embeds": [embed], "components": []})
        view.stop()
        return view.interaction.guild.get_member(menu_interaction.values[0])

    view.on("select", handle_select)
    await view.wait()

    return None

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

admin_member = MemberSetting(
    name="Admin user",
    description="The user with administrator privileges",
    id="admin_user"
)

settings.DefaultTypes.number

NumberSetting

Bases: Setting[int]

Interactive numeric setting.

Allows the user to input a single integer value through an interactive flow, validating bounds and showing a preview of the new value before saving.

Source code in settings/DefaultTypes/number.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
class NumberSetting(Setting[int]):
    """Interactive numeric setting.

    Allows the user to input a single integer value through an interactive flow,
    validating bounds and showing a preview of the new value before saving.
    """

    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        value: Optional[int] = None,
        minValue: Optional[int] = None,
        maxValue: Optional[int] = None,
        color: Optional[str] = "#ffffff",
        locales: Optional[bool] = False,
        module_name: Optional[str] = None,
    ):
        """Initialize a NumberSetting.

        Args:
            name: Display name.
            description: UX description.
            id: Persistence key.
            value: Initial value.
            minValue: Minimum allowed value (inclusive).
            maxValue: Maximum allowed value (inclusive).
            color: Hex color for the embed.
            locales: Enable i18n of display strings.
            module_name: Module context for translations.
        """
        super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="number")
        self.value = value
        self.minValue = minValue if minValue is not None else -2**31
        self.maxValue = maxValue if maxValue is not None else 2**31 - 1
        self.color = color
        self.locales = locales
        self.module_name = module_name

    async def run(self, view: InteractionView) -> int:
        """
        Runs the interactive session for configuring the numerical setting.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)

        def update_embed(new_value=None):
            """
            Updates the embed to reflect the current or new value.
            """
            embed = Embed(
                title=translate("number_setting.title", setting_name=name),
                description=description,
                color=int(self.color.lstrip("#"), 16),
            )
            embed.add_field(
                name=translate("number_setting.current_value"),
                value=str(self.value) if self.value is not None else translate("number_setting.not_defined"),
                inline=False,
            )
            if new_value is not None:
                embed.add_field(
                    name=translate("number_setting.new_value"),
                    value=str(new_value),
                    inline=False,
                )
            return embed

        embed = update_embed()
        view.clear_items()

        # Botão para definir o valor
        set_button = Button(
            label=translate("number_setting.set_value"),
            custom_id="set_value",
            style=ButtonStyle.primary,
        )

        async def handle_set(inter: Interaction):
            """
            Handles setting a new numerical value.
            """
            await inter.response.defer()
            embed.description = translate("number_setting.enter_value")
            await view.update(embed=embed, components=[])

            channel = inter.channel  # Garantir que estamos em um canal válido
            if not isinstance(channel, TextChannel):
                raise ValueError("Invalid channel type for awaiting messages.")

            try:
                def check(msg):
                    return msg.author == inter.user and msg.channel == channel

                message = await view.client.wait_for("message", timeout=60.0, check=check)
                number = int(message.content.strip())

            except ValueError:
                embed.description = translate("number_setting.invalid_number")
                await view.update(embed=embed, components=[])
                return
            except asyncio.TimeoutError:
                embed.description = translate("number_setting.timeout")
                await view.update(embed=embed, components=[])
                raise TimeoutError(translate("number_setting.timeout_error"))

            if not (self.minValue <= number <= self.maxValue):
                embed.description = translate(
                    "number_setting.value_out_of_bounds",
                    value=number,
                    minValue=self.minValue,
                    maxValue=self.maxValue,
                )
                await view.update(embed=embed, components=[])
                return

            # Atualiza o embed com o novo valor
            self.value = number
            final_embed = update_embed(new_value=number)
            await view.update(embed=final_embed, components=[])
            view.stop()
            return number

        set_button.callback = handle_set
        view.add_item(set_button)

        # Enviar o embed inicial usando `view.update`
        await view.update(embed=embed, components=[set_button])

        await view.wait()

        if self.value is None:
            embed.description = translate("number_setting.timeout")
            await view.update(embed=embed, components=[])
            raise TimeoutError(translate("number_setting.timeout_error"))

        return self.value

    def parse_to_database(self, value: int) -> int:
        """
        Parse the numerical value for database storage.
        """
        return value

    def parse_from_database(self, config: Any) -> int:
        """
        Parse the numerical value from database format.
        """
        return int(config)

    def parse_to_field(self, value: int, translator: Optional[Callable] = None) -> str:
        """
        Parse the value to a displayable string format.
        """
        label = translator('current_value') if translator is not None else "Current value"
        return f"{label}: {value}"

    def clone(self) -> "NumberSetting":
        """Return a shallow clone preserving bounds and i18n context."""
        return NumberSetting(
            name=self.name,
            description=self.description,
            id=self.id,
            value=self.value,
            minValue=self.minValue,
            maxValue=self.maxValue,
            color=self.color,
            locales=self.locales,
            module_name=self.module_name,
        )

__init__(name, description, id, value=None, minValue=None, maxValue=None, color='#ffffff', locales=False, module_name=None)

Initialize a NumberSetting.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

UX description.

required
id str

Persistence key.

required
value Optional[int]

Initial value.

None
minValue Optional[int]

Minimum allowed value (inclusive).

None
maxValue Optional[int]

Maximum allowed value (inclusive).

None
color Optional[str]

Hex color for the embed.

'#ffffff'
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module context for translations.

None
Source code in settings/DefaultTypes/number.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    value: Optional[int] = None,
    minValue: Optional[int] = None,
    maxValue: Optional[int] = None,
    color: Optional[str] = "#ffffff",
    locales: Optional[bool] = False,
    module_name: Optional[str] = None,
):
    """Initialize a NumberSetting.

    Args:
        name: Display name.
        description: UX description.
        id: Persistence key.
        value: Initial value.
        minValue: Minimum allowed value (inclusive).
        maxValue: Maximum allowed value (inclusive).
        color: Hex color for the embed.
        locales: Enable i18n of display strings.
        module_name: Module context for translations.
    """
    super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="number")
    self.value = value
    self.minValue = minValue if minValue is not None else -2**31
    self.maxValue = maxValue if maxValue is not None else 2**31 - 1
    self.color = color
    self.locales = locales
    self.module_name = module_name

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

clone()

Return a shallow clone preserving bounds and i18n context.

Source code in settings/DefaultTypes/number.py
178
179
180
181
182
183
184
185
186
187
188
189
190
def clone(self) -> "NumberSetting":
    """Return a shallow clone preserving bounds and i18n context."""
    return NumberSetting(
        name=self.name,
        description=self.description,
        id=self.id,
        value=self.value,
        minValue=self.minValue,
        maxValue=self.maxValue,
        color=self.color,
        locales=self.locales,
        module_name=self.module_name,
    )

parse(config, client, guild_data, guild) async

Optional async hook to parse a value with context.

Subclasses may override this to perform context‑aware parsing (e.g. resolve IDs to Discord objects). Default behavior returns config as‑is.

Parameters:

Name Type Description Default
config Any

Raw config value from DB or input.

required
client ExtendedClient

Extended bot client.

required
guild_data Any

Raw guild document (for extra context if needed).

required
guild Guild

Hydrated guild wrapper.

required

Returns:

Type Description
Awaitable[T]

The parsed value (usually of type T).

Source code in settings/Setting.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
async def parse(self, config: Any, client: ExtendedClient, guild_data: Any, guild: Guild) -> Awaitable[T]:
    """Optional async hook to parse a value with context.

    Subclasses may override this to perform context‑aware parsing (e.g.
    resolve IDs to Discord objects). Default behavior returns `config` as‑is.

    Args:
        config: Raw config value from DB or input.
        client: Extended bot client.
        guild_data: Raw guild document (for extra context if needed).
        guild: Hydrated guild wrapper.

    Returns:
        The parsed value (usually of type `T`).
    """
    return config

parse_from_database(config)

Parse the numerical value from database format.

Source code in settings/DefaultTypes/number.py
165
166
167
168
169
def parse_from_database(self, config: Any) -> int:
    """
    Parse the numerical value from database format.
    """
    return int(config)

parse_to_database(value)

Parse the numerical value for database storage.

Source code in settings/DefaultTypes/number.py
159
160
161
162
163
def parse_to_database(self, value: int) -> int:
    """
    Parse the numerical value for database storage.
    """
    return value

parse_to_field(value, translator=None)

Parse the value to a displayable string format.

Source code in settings/DefaultTypes/number.py
171
172
173
174
175
176
def parse_to_field(self, value: int, translator: Optional[Callable] = None) -> str:
    """
    Parse the value to a displayable string format.
    """
    label = translator('current_value') if translator is not None else "Current value"
    return f"{label}: {value}"

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Runs the interactive session for configuring the numerical setting.

Source code in settings/DefaultTypes/number.py
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
async def run(self, view: InteractionView) -> int:
    """
    Runs the interactive session for configuring the numerical setting.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        name, description, kwargs = self.apply_locale(translate_module=translate_module)

    def update_embed(new_value=None):
        """
        Updates the embed to reflect the current or new value.
        """
        embed = Embed(
            title=translate("number_setting.title", setting_name=name),
            description=description,
            color=int(self.color.lstrip("#"), 16),
        )
        embed.add_field(
            name=translate("number_setting.current_value"),
            value=str(self.value) if self.value is not None else translate("number_setting.not_defined"),
            inline=False,
        )
        if new_value is not None:
            embed.add_field(
                name=translate("number_setting.new_value"),
                value=str(new_value),
                inline=False,
            )
        return embed

    embed = update_embed()
    view.clear_items()

    # Botão para definir o valor
    set_button = Button(
        label=translate("number_setting.set_value"),
        custom_id="set_value",
        style=ButtonStyle.primary,
    )

    async def handle_set(inter: Interaction):
        """
        Handles setting a new numerical value.
        """
        await inter.response.defer()
        embed.description = translate("number_setting.enter_value")
        await view.update(embed=embed, components=[])

        channel = inter.channel  # Garantir que estamos em um canal válido
        if not isinstance(channel, TextChannel):
            raise ValueError("Invalid channel type for awaiting messages.")

        try:
            def check(msg):
                return msg.author == inter.user and msg.channel == channel

            message = await view.client.wait_for("message", timeout=60.0, check=check)
            number = int(message.content.strip())

        except ValueError:
            embed.description = translate("number_setting.invalid_number")
            await view.update(embed=embed, components=[])
            return
        except asyncio.TimeoutError:
            embed.description = translate("number_setting.timeout")
            await view.update(embed=embed, components=[])
            raise TimeoutError(translate("number_setting.timeout_error"))

        if not (self.minValue <= number <= self.maxValue):
            embed.description = translate(
                "number_setting.value_out_of_bounds",
                value=number,
                minValue=self.minValue,
                maxValue=self.maxValue,
            )
            await view.update(embed=embed, components=[])
            return

        # Atualiza o embed com o novo valor
        self.value = number
        final_embed = update_embed(new_value=number)
        await view.update(embed=final_embed, components=[])
        view.stop()
        return number

    set_button.callback = handle_set
    view.add_item(set_button)

    # Enviar o embed inicial usando `view.update`
    await view.update(embed=embed, components=[set_button])

    await view.wait()

    if self.value is None:
        embed.description = translate("number_setting.timeout")
        await view.update(embed=embed, components=[])
        raise TimeoutError(translate("number_setting.timeout_error"))

    return self.value

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

multiplier = NumberSetting(
    name="XP Multiplier",
    description="Multiplier applied to all XP gains",
    id="xp_multiplier",
    value=1,
    minValue=0.1,
    maxValue=10,
)

settings.DefaultTypes.option

OptionSetting

Bases: Setting[T], Generic[T]

Single-choice setting built from a list of child Settings.

Each option is represented by a child Setting used only for metadata (name/id/value). The user selects one option via buttons.

Source code in settings/DefaultTypes/option.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
class OptionSetting(Setting[T], Generic[T]):
    """Single-choice setting built from a list of child `Setting`s.

    Each option is represented by a child `Setting` used only for metadata
    (name/id/value). The user selects one option via buttons.
    """

    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        options: List[Setting[T]],
        value: Optional[T] = None,
        permission: Optional[T] = None,
        locales: Optional[bool] = False,
        module_name: Optional[str] = None
    ):
        """Initialize an OptionSetting.

        Args:
            name: Display name.
            description: UX description.
            id: Persistence key.
            options: List of child settings representing choices.
            value: Initial selected value.
            permission: Optional permission flag/bit.
            locales: Enable i18n of display strings.
            module_name: Module context for translations.
        """
        super().__init__(name=name, description=description, id=id, locales=locales, module_name=module_name, permission=permission, type_="option")
        self.options = options
        self.value = value
        self.permission = permission
        self.locales = locales
        self.module_name = module_name

        if self.locales and self.module_name:
            self._apply_locales_to_options()

    def _apply_locales_to_options(self):
        """
        Propaga as configurações de 'locales' e 'module_name' para as opções.
        """
        for option in self.options:
            if isinstance(option, Setting):
                option.locales = self.locales
                option.module_name = self.module_name

    def apply_locale(self, translate_module: Callable[[str], str]):
        """
        Applies the modular translation to all fields, including options.
        """
        super().apply_locale(translate_module) 

        for option in self.options:
            if isinstance(option, Setting):
                option.apply_locale(translate_module)

    async def run(self, view: InteractionView) -> T:
        """Render the selector and return the chosen option's value.

        Args:
            view: Active interaction view.

        Returns:
            The selected option value.

        Raises:
            TimeoutError: When the user does not choose in time.
        """
        language = await view.client.translator.get_language(view.interaction.guild.id)
        translate = view.client.translator.get_global(language)

        if self.module_name and self.locales:
            translate_module = (
                view.client.translator.get_module_translations(self.module_name, language)
                if self.locales
                else lambda key, **kwargs: key
            )

            self.apply_locale(translate_module)

        buttons = [
            Button(
                label=option.name,
                custom_id=option.id,
                style=ButtonStyle.primary,
            )
            for option in self.options
        ]
        rows = [ActionRow(*buttons)]

        rows_view = View()
        rows_view.add_item(rows)
        # Embed with options description
        embed = Embed(
            title=translate("option_setting.title", setting_name=self.name),
            description=self.description,
            color=0xFFFFFF,
        )
        await view.update({"embeds": [embed], "components": rows_view.children})

        # Await user selection
        result = await self._await_selection(view, translate)
        if result is None:
            embed.description = translate("option_setting.timeout")
            await view.update({"embeds": [embed], "components": []})
            raise TimeoutError(translate("option_setting.timeout_error"))

        return result

    async def _await_selection(self, view: InteractionView, translate: Callable[[str, Any], str]) -> Optional[T]:
        """
        Handles the selection of an option asynchronously.
        """
        selection = None

        def handle_selection(inter: Interaction):
            nonlocal selection
            selected_id = inter.data["custom_id"]
            selected_option = next((opt for opt in self.options if opt.id == selected_id), None)
            if selected_option:
                selection = selected_option.value

        view.on("any", handle_selection)
        await view.wait()

        if selection is not None:
            selected_option = next((opt for opt in self.options if opt.value == selection), None)
            success_embed = Embed(
                title=translate("option_setting.selected"),
                description=translate("option_setting.success", option_name=selected_option.name),
                color=0x00FF00,
            )
            await view.update({"embeds": [success_embed], "components": []})

        return selection

    def parse_to_database(self, value: T, translator: Optional[callable] = None) -> str:
        """
        Parse the selected value for database storage.
        """
        return str(value)

    def parse_from_database(self, config: str) -> T:
        """
        Parse the value from the database configuration.
        """
        return next((opt.value for opt in self.options if str(opt.value) == config), None)

    def parse_to_field(self, value: T) -> str:
        """
        Parse the value to a displayable string format.
        """
        option = next((opt for opt in self.options if opt.value == value), None)
        return option.name if option else "N/A"

    def clone(self) -> "OptionSetting[T]":
        """Return a clone of this selector (options are shallow‑cloned).

        Notes:
            We clone each child option if it has `clone()`, otherwise we reuse
            the instance (safe for read‑only metadata).
        """
        cloned_options: List[Setting[T]] = [
            (opt.clone() if hasattr(opt, "clone") and callable(opt.clone) else opt)
            for opt in self.options
        ]
        return OptionSetting(
            name=self.name,
            description=self.description,
            id=self.id,
            options=cloned_options,
            value=self.value,
            permission=self.permission,
            locales=self.locales,
            module_name=self.module_name,
        )

__init__(name, description, id, options, value=None, permission=None, locales=False, module_name=None)

Initialize an OptionSetting.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

UX description.

required
id str

Persistence key.

required
options List[Setting[T]]

List of child settings representing choices.

required
value Optional[T]

Initial selected value.

None
permission Optional[T]

Optional permission flag/bit.

None
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module context for translations.

None
Source code in settings/DefaultTypes/option.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    options: List[Setting[T]],
    value: Optional[T] = None,
    permission: Optional[T] = None,
    locales: Optional[bool] = False,
    module_name: Optional[str] = None
):
    """Initialize an OptionSetting.

    Args:
        name: Display name.
        description: UX description.
        id: Persistence key.
        options: List of child settings representing choices.
        value: Initial selected value.
        permission: Optional permission flag/bit.
        locales: Enable i18n of display strings.
        module_name: Module context for translations.
    """
    super().__init__(name=name, description=description, id=id, locales=locales, module_name=module_name, permission=permission, type_="option")
    self.options = options
    self.value = value
    self.permission = permission
    self.locales = locales
    self.module_name = module_name

    if self.locales and self.module_name:
        self._apply_locales_to_options()

apply_locale(translate_module)

Applies the modular translation to all fields, including options.

Source code in settings/DefaultTypes/option.py
64
65
66
67
68
69
70
71
72
def apply_locale(self, translate_module: Callable[[str], str]):
    """
    Applies the modular translation to all fields, including options.
    """
    super().apply_locale(translate_module) 

    for option in self.options:
        if isinstance(option, Setting):
            option.apply_locale(translate_module)

clone()

Return a clone of this selector (options are shallow‑cloned).

Notes

We clone each child option if it has clone(), otherwise we reuse the instance (safe for read‑only metadata).

Source code in settings/DefaultTypes/option.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def clone(self) -> "OptionSetting[T]":
    """Return a clone of this selector (options are shallow‑cloned).

    Notes:
        We clone each child option if it has `clone()`, otherwise we reuse
        the instance (safe for read‑only metadata).
    """
    cloned_options: List[Setting[T]] = [
        (opt.clone() if hasattr(opt, "clone") and callable(opt.clone) else opt)
        for opt in self.options
    ]
    return OptionSetting(
        name=self.name,
        description=self.description,
        id=self.id,
        options=cloned_options,
        value=self.value,
        permission=self.permission,
        locales=self.locales,
        module_name=self.module_name,
    )

parse(config, client, guild_data, guild) async

Optional async hook to parse a value with context.

Subclasses may override this to perform context‑aware parsing (e.g. resolve IDs to Discord objects). Default behavior returns config as‑is.

Parameters:

Name Type Description Default
config Any

Raw config value from DB or input.

required
client ExtendedClient

Extended bot client.

required
guild_data Any

Raw guild document (for extra context if needed).

required
guild Guild

Hydrated guild wrapper.

required

Returns:

Type Description
Awaitable[T]

The parsed value (usually of type T).

Source code in settings/Setting.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
async def parse(self, config: Any, client: ExtendedClient, guild_data: Any, guild: Guild) -> Awaitable[T]:
    """Optional async hook to parse a value with context.

    Subclasses may override this to perform context‑aware parsing (e.g.
    resolve IDs to Discord objects). Default behavior returns `config` as‑is.

    Args:
        config: Raw config value from DB or input.
        client: Extended bot client.
        guild_data: Raw guild document (for extra context if needed).
        guild: Hydrated guild wrapper.

    Returns:
        The parsed value (usually of type `T`).
    """
    return config

parse_from_database(config)

Parse the value from the database configuration.

Source code in settings/DefaultTypes/option.py
160
161
162
163
164
def parse_from_database(self, config: str) -> T:
    """
    Parse the value from the database configuration.
    """
    return next((opt.value for opt in self.options if str(opt.value) == config), None)

parse_to_database(value, translator=None)

Parse the selected value for database storage.

Source code in settings/DefaultTypes/option.py
154
155
156
157
158
def parse_to_database(self, value: T, translator: Optional[callable] = None) -> str:
    """
    Parse the selected value for database storage.
    """
    return str(value)

parse_to_field(value)

Parse the value to a displayable string format.

Source code in settings/DefaultTypes/option.py
166
167
168
169
170
171
def parse_to_field(self, value: T) -> str:
    """
    Parse the value to a displayable string format.
    """
    option = next((opt for opt in self.options if opt.value == value), None)
    return option.name if option else "N/A"

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Render the selector and return the chosen option's value.

Parameters:

Name Type Description Default
view InteractionView

Active interaction view.

required

Returns:

Type Description
T

The selected option value.

Raises:

Type Description
TimeoutError

When the user does not choose in time.

Source code in settings/DefaultTypes/option.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
async def run(self, view: InteractionView) -> T:
    """Render the selector and return the chosen option's value.

    Args:
        view: Active interaction view.

    Returns:
        The selected option value.

    Raises:
        TimeoutError: When the user does not choose in time.
    """
    language = await view.client.translator.get_language(view.interaction.guild.id)
    translate = view.client.translator.get_global(language)

    if self.module_name and self.locales:
        translate_module = (
            view.client.translator.get_module_translations(self.module_name, language)
            if self.locales
            else lambda key, **kwargs: key
        )

        self.apply_locale(translate_module)

    buttons = [
        Button(
            label=option.name,
            custom_id=option.id,
            style=ButtonStyle.primary,
        )
        for option in self.options
    ]
    rows = [ActionRow(*buttons)]

    rows_view = View()
    rows_view.add_item(rows)
    # Embed with options description
    embed = Embed(
        title=translate("option_setting.title", setting_name=self.name),
        description=self.description,
        color=0xFFFFFF,
    )
    await view.update({"embeds": [embed], "components": rows_view.children})

    # Await user selection
    result = await self._await_selection(view, translate)
    if result is None:
        embed.description = translate("option_setting.timeout")
        await view.update({"embeds": [embed], "components": []})
        raise TimeoutError(translate("option_setting.timeout_error"))

    return result

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

theme_setting = OptionSetting(
    name="Theme",
    description="Choose a theme for your server",
    id="theme",
    options=["Dark", "Light", "Classic"],
    value="Dark"
)

settings.DefaultTypes.role

RoleSetting

Bases: Setting[Role]

Interactive role selector.

Renders a dropdown with all non‑managed roles in the guild and persists the selected role.

Source code in settings/DefaultTypes/role.py
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
class RoleSetting(Setting[Role]):
    """Interactive role selector.

    Renders a dropdown with all non‑managed roles in the guild and persists the
    selected role.
    """
    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        max_values: Optional[int] = 1,
        min_values: Optional[int] = 1,
        placeholder: Optional[str] = None,
        embed_description: Optional[str] = None,
        color: str = "#ffffff",
        value: Optional[Role] = None,
        locales: Optional[bool] = False,
        module_name: Optional[str] = None
    ):
        """Initialize a RoleSetting.

        Args:
            name: Display name.
            description: UX description.
            id: Persistence key.
            max_values: Max selectable roles.
            min_values: Min selectable roles.
            placeholder: Placeholder for the select.
            embed_description: Optional description override.
            color: Hex color for the embed.
            value: Initial role.
            locales: Enable i18n of display strings.
            module_name: Module context for translations.
        """
        super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="role")
        self.max_values = max_values
        self.min_values = min_values
        self.placeholder = placeholder
        self.embed_description = embed_description
        self.description = description
        self.name = name
        self.color = color
        self.value = value
        self.locales = locales
        self.module_name = module_name

    async def run(self, view: InteractionView) -> Role:
        """Render the dropdown and return the chosen role.

        Args:
            view: Active interaction view.

        Returns:
            The selected `Role`.

        Raises:
            TimeoutError: If the selection is not completed in time.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)

        placeholder = self.placeholder or translate("role_setting.placeholder")
        embed = Embed(
            title=translate("role_setting.title", setting_name=name),
            description=description or translate("role_setting.description"),
            color=int(self.color.lstrip("#"), 16),
        )

        roles = view.interaction.guild.roles
        options = [
            SelectOption(label=role.name, value=str(role.id)) for role in roles if not role.managed
        ]

        select = Select(
            placeholder=placeholder,
            options=options,
            custom_id=f"select-{view.view_id}",  # Adiciona o ID único
            max_values=self.max_values,
            min_values=self.min_values,
        )

        view.clear_items()
        view.add_item(select)

        await view.update(embed=embed, components=view.children)

        async def handle_select(inter: Interaction):
            await inter.response.defer()
            selected_role_id = inter.data["values"][0]
            selected_role = view.interaction.guild.get_role(int(selected_role_id))
            if not selected_role:
                return

            self.value = selected_role
            embed.description = translate("role_setting.selected", role_name=selected_role.name)

            await view.update(embed=embed, components=[])
            view.stop()

            return self.value

        select.callback = handle_select

        await view.wait()

        if not self.value:
            raise TimeoutError(translate("role_setting.timeout_error"))

        return self.value



    def parse_to_database(self, value: Role) -> str:
        """
        Parse the role to a database-friendly format.
        """
        return value.id

    async def parse(self, config: Any, client: ExtendedClient, guild_data: Any, guild: Guild) -> Role:
        """
        Parse the role from the database configuration.
        """
        return f"<@&{config}>"

    def parse_to_field(self, value: Role, translator: Optional[callable] = None) -> str:
        """
        Parse the role to a displayable string format.
        """
        if translator:
            return f'{translator("role_setting.display_value")}: <@{value.id}>'
        else:
            return f'Role: <@{value.id}>'


    def clone(self) -> "RoleSetting":
        """Return a shallow clone preserving selector configuration."""
        return RoleSetting(
            name=self.name,
            description=self.description,
            id=self.id,
            max_values=self.max_values,
            min_values=self.min_values,
            placeholder=self.placeholder,
            embed_description=self.embed_description,
            color=self.color,
            value=self.value,
            locales=self.locales,
            module_name=self.module_name
        )

__init__(name, description, id, max_values=1, min_values=1, placeholder=None, embed_description=None, color='#ffffff', value=None, locales=False, module_name=None)

Initialize a RoleSetting.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

UX description.

required
id str

Persistence key.

required
max_values Optional[int]

Max selectable roles.

1
min_values Optional[int]

Min selectable roles.

1
placeholder Optional[str]

Placeholder for the select.

None
embed_description Optional[str]

Optional description override.

None
color str

Hex color for the embed.

'#ffffff'
value Optional[Role]

Initial role.

None
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module context for translations.

None
Source code in settings/DefaultTypes/role.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    max_values: Optional[int] = 1,
    min_values: Optional[int] = 1,
    placeholder: Optional[str] = None,
    embed_description: Optional[str] = None,
    color: str = "#ffffff",
    value: Optional[Role] = None,
    locales: Optional[bool] = False,
    module_name: Optional[str] = None
):
    """Initialize a RoleSetting.

    Args:
        name: Display name.
        description: UX description.
        id: Persistence key.
        max_values: Max selectable roles.
        min_values: Min selectable roles.
        placeholder: Placeholder for the select.
        embed_description: Optional description override.
        color: Hex color for the embed.
        value: Initial role.
        locales: Enable i18n of display strings.
        module_name: Module context for translations.
    """
    super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="role")
    self.max_values = max_values
    self.min_values = min_values
    self.placeholder = placeholder
    self.embed_description = embed_description
    self.description = description
    self.name = name
    self.color = color
    self.value = value
    self.locales = locales
    self.module_name = module_name

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

clone()

Return a shallow clone preserving selector configuration.

Source code in settings/DefaultTypes/role.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def clone(self) -> "RoleSetting":
    """Return a shallow clone preserving selector configuration."""
    return RoleSetting(
        name=self.name,
        description=self.description,
        id=self.id,
        max_values=self.max_values,
        min_values=self.min_values,
        placeholder=self.placeholder,
        embed_description=self.embed_description,
        color=self.color,
        value=self.value,
        locales=self.locales,
        module_name=self.module_name
    )

parse(config, client, guild_data, guild) async

Parse the role from the database configuration.

Source code in settings/DefaultTypes/role.py
140
141
142
143
144
async def parse(self, config: Any, client: ExtendedClient, guild_data: Any, guild: Guild) -> Role:
    """
    Parse the role from the database configuration.
    """
    return f"<@&{config}>"

parse_from_database(config)

Reconstruct the runtime value from the DB representation.

This is the inverse of parse_to_database.

Parameters:

Name Type Description Default
config Any

Raw stored value from the database.

required

Returns:

Type Description
T

The deserialized runtime value of type T.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def parse_from_database(self, config: Any) -> T:
    """Reconstruct the runtime value from the DB representation.

    This is the inverse of `parse_to_database`.

    Args:
        config: Raw stored value from the database.

    Returns:
        The deserialized runtime value of type `T`.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

parse_to_database(value)

Parse the role to a database-friendly format.

Source code in settings/DefaultTypes/role.py
134
135
136
137
138
def parse_to_database(self, value: Role) -> str:
    """
    Parse the role to a database-friendly format.
    """
    return value.id

parse_to_field(value, translator=None)

Parse the role to a displayable string format.

Source code in settings/DefaultTypes/role.py
146
147
148
149
150
151
152
153
def parse_to_field(self, value: Role, translator: Optional[callable] = None) -> str:
    """
    Parse the role to a displayable string format.
    """
    if translator:
        return f'{translator("role_setting.display_value")}: <@{value.id}>'
    else:
        return f'Role: <@{value.id}>'

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Render the dropdown and return the chosen role.

Parameters:

Name Type Description Default
view InteractionView

Active interaction view.

required

Returns:

Type Description
Role

The selected Role.

Raises:

Type Description
TimeoutError

If the selection is not completed in time.

Source code in settings/DefaultTypes/role.py
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
async def run(self, view: InteractionView) -> Role:
    """Render the dropdown and return the chosen role.

    Args:
        view: Active interaction view.

    Returns:
        The selected `Role`.

    Raises:
        TimeoutError: If the selection is not completed in time.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        name, description, kwargs = self.apply_locale(translate_module=translate_module)

    placeholder = self.placeholder or translate("role_setting.placeholder")
    embed = Embed(
        title=translate("role_setting.title", setting_name=name),
        description=description or translate("role_setting.description"),
        color=int(self.color.lstrip("#"), 16),
    )

    roles = view.interaction.guild.roles
    options = [
        SelectOption(label=role.name, value=str(role.id)) for role in roles if not role.managed
    ]

    select = Select(
        placeholder=placeholder,
        options=options,
        custom_id=f"select-{view.view_id}",  # Adiciona o ID único
        max_values=self.max_values,
        min_values=self.min_values,
    )

    view.clear_items()
    view.add_item(select)

    await view.update(embed=embed, components=view.children)

    async def handle_select(inter: Interaction):
        await inter.response.defer()
        selected_role_id = inter.data["values"][0]
        selected_role = view.interaction.guild.get_role(int(selected_role_id))
        if not selected_role:
            return

        self.value = selected_role
        embed.description = translate("role_setting.selected", role_name=selected_role.name)

        await view.update(embed=embed, components=[])
        view.stop()

        return self.value

    select.callback = handle_select

    await view.wait()

    if not self.value:
        raise TimeoutError(translate("role_setting.timeout_error"))

    return self.value

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

vip_role = RoleSetting(
    name="VIP Role",
    description="Role given to VIP members",
    id="vip_role"
)

settings.DefaultTypes.select

SelectSetting

Bases: Setting[str]

Single-select setting based on a predefined list of (label, value) options.

Source code in settings/DefaultTypes/select.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
class SelectSetting(Setting[str]):
    """Single-select setting based on a predefined list of (label, value) options."""


    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        options: List[Dict[str, str]],  # Cada opção será um dicionário com label e value
        value: Optional[str] = None,
        max_values: Optional[int] = 1,
        min_values: Optional[int] = 1,
        color: str = "#ffffff",
        permission: Optional[int] = None,
        locales: Optional[bool] = False,
        module_name: Optional[str] = None
    ):
        """Initialize a SelectSetting.

        Args:
            name: Display name.
            description: UX description.
            id: Persistence key.
            options: List of dicts with `{label, value}`.
            value: Initial selected value.
            max_values: Max selectable (kept for API parity, UI uses single select).
            min_values: Min selectable.
            color: Hex color for the embed.
            permission: Optional permission flag/bit.
            locales: Enable i18n of display strings.
            module_name: Module context for translations.
        """
        super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="select")
        self.options = options 
        self.value = value
        self.max_values = max_values
        self.min_values = min_values
        self.color = color
        self.permission = permission
        self.locales = locales
        self.module_name = module_name

    async def run(self, view: InteractionView) -> str:
        """Render a dropdown list and return the selected string value.

        Args:
            view: Active interaction view.

        Returns:
            The selected option `value`.

        Raises:
            TimeoutError: When selection does not complete in time.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)

        # Criar embed para exibição
        embed = Embed(
            title=translate("select_setting.title", setting_name=name),
            description=translate("select_setting.description", description=description),
            color=int(self.color.lstrip("#"), 16),
        )

        # Criar opções para o menu de seleção
        options = [
            SelectOption(label=option["label"], value=option["value"])
            for option in self.options
        ]

        # Criar Select
        select = Select(
            placeholder=translate("select_setting.placeholder"),
            options=options,
            max_values=1,  # Seleção única
            min_values=1,
            custom_id="select_option",
        )

        # Criar View
        select_view = View()
        select_view.add_item(select)

        await view.update(embeds=[embed], components=select_view.children)

        async def handle_select(inter: Interaction):
            selected_values = inter.data["values"]
            self.value = selected_values[0]  # Garantir que seja uma string
            embed.description = translate("select_setting.selected", selected=", ".join(selected_values))
            await inter.response.edit_message(embeds=[embed], view=None)
            view.stop()

        # Adicionar o callback ao Select
        select.callback = handle_select

        # Esperar a interação
        await view.wait()

        if not self.value:
            raise TimeoutError(translate("select_setting.timeout_error"))

        return self.value

    def parse_to_database(self, value: str) -> str:
        """
        Parse the value to a database-friendly format.
        """
        if not isinstance(value, str):
            raise ValueError(f"Expected a string for database storage, got {type(value).__name__}")
        return value

    def parse_from_database(self, config: Any) -> str:
        """
        Parse the value from a database configuration.
        """
        return config

    def parse_to_field(self, value: str) -> str:
        """
        Parse the value to a displayable string format.
        """
        label = next((option["label"] for option in self.options if option["value"] == value), "N/A")
        return label

    def clone(self) -> "SelectSetting":
        """Return a shallow clone preserving available options and i18n context."""
        return SelectSetting(
            name=self.name,
            description=self.description,
            id=self.id,
            options=self.options[:],
            value=self.value,
            max_values=self.max_values,
            min_values=self.min_values,
            color=self.color,
            permission=self.permission,
            locales=self.locales,
            module_name=self.module_name,
        )

__init__(name, description, id, options, value=None, max_values=1, min_values=1, color='#ffffff', permission=None, locales=False, module_name=None)

Initialize a SelectSetting.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

UX description.

required
id str

Persistence key.

required
options List[Dict[str, str]]

List of dicts with {label, value}.

required
value Optional[str]

Initial selected value.

None
max_values Optional[int]

Max selectable (kept for API parity, UI uses single select).

1
min_values Optional[int]

Min selectable.

1
color str

Hex color for the embed.

'#ffffff'
permission Optional[int]

Optional permission flag/bit.

None
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module context for translations.

None
Source code in settings/DefaultTypes/select.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    options: List[Dict[str, str]],  # Cada opção será um dicionário com label e value
    value: Optional[str] = None,
    max_values: Optional[int] = 1,
    min_values: Optional[int] = 1,
    color: str = "#ffffff",
    permission: Optional[int] = None,
    locales: Optional[bool] = False,
    module_name: Optional[str] = None
):
    """Initialize a SelectSetting.

    Args:
        name: Display name.
        description: UX description.
        id: Persistence key.
        options: List of dicts with `{label, value}`.
        value: Initial selected value.
        max_values: Max selectable (kept for API parity, UI uses single select).
        min_values: Min selectable.
        color: Hex color for the embed.
        permission: Optional permission flag/bit.
        locales: Enable i18n of display strings.
        module_name: Module context for translations.
    """
    super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="select")
    self.options = options 
    self.value = value
    self.max_values = max_values
    self.min_values = min_values
    self.color = color
    self.permission = permission
    self.locales = locales
    self.module_name = module_name

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

clone()

Return a shallow clone preserving available options and i18n context.

Source code in settings/DefaultTypes/select.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def clone(self) -> "SelectSetting":
    """Return a shallow clone preserving available options and i18n context."""
    return SelectSetting(
        name=self.name,
        description=self.description,
        id=self.id,
        options=self.options[:],
        value=self.value,
        max_values=self.max_values,
        min_values=self.min_values,
        color=self.color,
        permission=self.permission,
        locales=self.locales,
        module_name=self.module_name,
    )

parse(config, client, guild_data, guild) async

Optional async hook to parse a value with context.

Subclasses may override this to perform context‑aware parsing (e.g. resolve IDs to Discord objects). Default behavior returns config as‑is.

Parameters:

Name Type Description Default
config Any

Raw config value from DB or input.

required
client ExtendedClient

Extended bot client.

required
guild_data Any

Raw guild document (for extra context if needed).

required
guild Guild

Hydrated guild wrapper.

required

Returns:

Type Description
Awaitable[T]

The parsed value (usually of type T).

Source code in settings/Setting.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
async def parse(self, config: Any, client: ExtendedClient, guild_data: Any, guild: Guild) -> Awaitable[T]:
    """Optional async hook to parse a value with context.

    Subclasses may override this to perform context‑aware parsing (e.g.
    resolve IDs to Discord objects). Default behavior returns `config` as‑is.

    Args:
        config: Raw config value from DB or input.
        client: Extended bot client.
        guild_data: Raw guild document (for extra context if needed).
        guild: Hydrated guild wrapper.

    Returns:
        The parsed value (usually of type `T`).
    """
    return config

parse_from_database(config)

Parse the value from a database configuration.

Source code in settings/DefaultTypes/select.py
126
127
128
129
130
def parse_from_database(self, config: Any) -> str:
    """
    Parse the value from a database configuration.
    """
    return config

parse_to_database(value)

Parse the value to a database-friendly format.

Source code in settings/DefaultTypes/select.py
118
119
120
121
122
123
124
def parse_to_database(self, value: str) -> str:
    """
    Parse the value to a database-friendly format.
    """
    if not isinstance(value, str):
        raise ValueError(f"Expected a string for database storage, got {type(value).__name__}")
    return value

parse_to_field(value)

Parse the value to a displayable string format.

Source code in settings/DefaultTypes/select.py
132
133
134
135
136
137
def parse_to_field(self, value: str) -> str:
    """
    Parse the value to a displayable string format.
    """
    label = next((option["label"] for option in self.options if option["value"] == value), "N/A")
    return label

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Render a dropdown list and return the selected string value.

Parameters:

Name Type Description Default
view InteractionView

Active interaction view.

required

Returns:

Type Description
str

The selected option value.

Raises:

Type Description
TimeoutError

When selection does not complete in time.

Source code in settings/DefaultTypes/select.py
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
async def run(self, view: InteractionView) -> str:
    """Render a dropdown list and return the selected string value.

    Args:
        view: Active interaction view.

    Returns:
        The selected option `value`.

    Raises:
        TimeoutError: When selection does not complete in time.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        name, description, kwargs = self.apply_locale(translate_module=translate_module)

    # Criar embed para exibição
    embed = Embed(
        title=translate("select_setting.title", setting_name=name),
        description=translate("select_setting.description", description=description),
        color=int(self.color.lstrip("#"), 16),
    )

    # Criar opções para o menu de seleção
    options = [
        SelectOption(label=option["label"], value=option["value"])
        for option in self.options
    ]

    # Criar Select
    select = Select(
        placeholder=translate("select_setting.placeholder"),
        options=options,
        max_values=1,  # Seleção única
        min_values=1,
        custom_id="select_option",
    )

    # Criar View
    select_view = View()
    select_view.add_item(select)

    await view.update(embeds=[embed], components=select_view.children)

    async def handle_select(inter: Interaction):
        selected_values = inter.data["values"]
        self.value = selected_values[0]  # Garantir que seja uma string
        embed.description = translate("select_setting.selected", selected=", ".join(selected_values))
        await inter.response.edit_message(embeds=[embed], view=None)
        view.stop()

    # Adicionar o callback ao Select
    select.callback = handle_select

    # Esperar a interação
    await view.wait()

    if not self.value:
        raise TimeoutError(translate("select_setting.timeout_error"))

    return self.value

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

region_select = SelectSetting(
    name="Server Region",
    description="Choose the server region",
    id="server_region",
    options=["US-East", "US-West", "Europe", "Asia"],
    value="US-East"
)

settings.DefaultTypes.string

StringSettingFile

Bases: Setting[str]

Interactive free‑text string setting with optional validation.

Source code in settings/DefaultTypes/string.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
class StringSettingFile(Setting[str]):
    """Interactive free‑text string setting with optional validation."""

    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        filter: Optional[Dict[str, Any]] = None,
        color: str = "#ffffff",
        value: Optional[str] = None,
        locales: Optional[bool] = False,
        module_name: Optional[str] = None

    ):
        """Initialize a StringSettingFile.

        Args:
            name: Display name.
            description: UX description.
            id: Persistence key.
            filter: Optional dict with keys: `fn` (Callable[[str], bool]), `error` (str), `footer` (str).
            color: Hex color for the embed.
            value: Initial value.
            locales: Enable i18n of display strings.
            module_name: Module context for translations.
        """
        super().__init__(name=name, description=description, locales=locales, module_name= module_name, id=id, type_="string")
        self.filter = filter
        self.color = color
        self.value = value
        self.locales = locales
        self.module_name = module_name


    async def run(self, view: InteractionView) -> str:
        """Render the editor, collect free text, validate, and return it.

        Args:
            view: Active interaction view.

        Returns:
            The updated string value.

        Raises:
            TimeoutError: When the user does not provide input in time.
        """
        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs = self.name, self.description, self.kwargs

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)

        value_text = (
            translate("string_setting.too_long")
            if (len(self.value or "") > 1000)
            else (self.value or translate("string_setting.not_defined"))
        )

        embed = Embed(
            title=translate("string_setting.title", setting_name=name),
            description=description,
            color=int(self.color.lstrip("#"), 16),
        )
        embed.add_field(name=translate("string_setting.current_value"), value=value_text)
        if self.filter and "footer" in self.filter:
            embed.set_footer(text=self.filter["footer"])

        async def handle_set(inter: InteractionView):
            """
            Handles the set action to update the string value.
            """
            await inter.response.defer()
            input_embed = Embed(
                title=translate("string_setting.title", setting_name=name),
                description=description,
                color=int(self.color.lstrip("#"), 16),
            )
            input_embed.add_field(name=translate("string_setting.current_value"), value=value_text)
            input_embed.set_footer(text=translate("string_setting.enter_value"))
            await view.update(embeds=[input_embed], components=[])

            def message_filter(message):
                return message.author.id == view.interaction.user.id

            try:
                message = await view.client.wait_for(
                    "message", check=message_filter, timeout=30
                )
            except TimeoutError:
                embed.set_footer(text=translate("string_setting.timeout"))
                await view.update(embeds=[embed])
                return

            value = message.content
            await message.delete()

            if self.filter and not self.filter["fn"](value):
                error_embed = Embed(
                    title=translate("string_setting.title", setting_name=name),
                    description=description,
                    color=int(self.color.lstrip("#"), 16),
                )
                error_embed.add_field(name=translate("string_setting.current_value"), value=value_text)
                error_embed.set_footer(text=self.filter["error"])
                await view.update(embeds=[error_embed], components=[setButton])
                return

            self.value = value
            new_value_text = (
                translate("string_setting.too_long")
                if len(value) > 1000
                else value
            )
            embed = Embed(
                title=translate("string_setting.title", setting_name=self.name),
                color=int(self.color.lstrip("#"), 16),
            )
            embed.add_field(
                name=translate("string_setting.previous_value"), value=value_text
            )
            embed.add_field(
                name=translate("string_setting.new_value"), value=new_value_text
            )
            await view.update(embeds=[embed], components=[])
            view.stop()

        setButton = Button(
            label=translate("string_setting.set"),
            style=ButtonStyle.primary,
            custom_id="set",
        )
        setButton.callback = handle_set

        await view.update(embeds=[embed], components=[setButton])
        await view.wait()

        return self.value

    def parse_to_database(self, value: str) -> str:
        """
        Parse the value to a database-friendly format.
        """
        return value

    def parse_to_field(self, value: str) -> str:
        """
        Parse the value to a displayable string format.
        """
        return value

    def clone(self) -> "StringSettingFile":
        """
        Clone the current instance.
        """
        return StringSettingFile(
            name=self.name,
            description=self.description,
            id=self.id,
            filter=self.filter,
            color=self.color,
            value=self.value,
            locales = self.locales,
            module_name = self.module_name 
        )

__init__(name, description, id, filter=None, color='#ffffff', value=None, locales=False, module_name=None)

Initialize a StringSettingFile.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

UX description.

required
id str

Persistence key.

required
filter Optional[Dict[str, Any]]

Optional dict with keys: fn (Callable[[str], bool]), error (str), footer (str).

None
color str

Hex color for the embed.

'#ffffff'
value Optional[str]

Initial value.

None
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module context for translations.

None
Source code in settings/DefaultTypes/string.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    filter: Optional[Dict[str, Any]] = None,
    color: str = "#ffffff",
    value: Optional[str] = None,
    locales: Optional[bool] = False,
    module_name: Optional[str] = None

):
    """Initialize a StringSettingFile.

    Args:
        name: Display name.
        description: UX description.
        id: Persistence key.
        filter: Optional dict with keys: `fn` (Callable[[str], bool]), `error` (str), `footer` (str).
        color: Hex color for the embed.
        value: Initial value.
        locales: Enable i18n of display strings.
        module_name: Module context for translations.
    """
    super().__init__(name=name, description=description, locales=locales, module_name= module_name, id=id, type_="string")
    self.filter = filter
    self.color = color
    self.value = value
    self.locales = locales
    self.module_name = module_name

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

clone()

Clone the current instance.

Source code in settings/DefaultTypes/string.py
166
167
168
169
170
171
172
173
174
175
176
177
178
179
def clone(self) -> "StringSettingFile":
    """
    Clone the current instance.
    """
    return StringSettingFile(
        name=self.name,
        description=self.description,
        id=self.id,
        filter=self.filter,
        color=self.color,
        value=self.value,
        locales = self.locales,
        module_name = self.module_name 
    )

parse(config, client, guild_data, guild) async

Optional async hook to parse a value with context.

Subclasses may override this to perform context‑aware parsing (e.g. resolve IDs to Discord objects). Default behavior returns config as‑is.

Parameters:

Name Type Description Default
config Any

Raw config value from DB or input.

required
client ExtendedClient

Extended bot client.

required
guild_data Any

Raw guild document (for extra context if needed).

required
guild Guild

Hydrated guild wrapper.

required

Returns:

Type Description
Awaitable[T]

The parsed value (usually of type T).

Source code in settings/Setting.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
async def parse(self, config: Any, client: ExtendedClient, guild_data: Any, guild: Guild) -> Awaitable[T]:
    """Optional async hook to parse a value with context.

    Subclasses may override this to perform context‑aware parsing (e.g.
    resolve IDs to Discord objects). Default behavior returns `config` as‑is.

    Args:
        config: Raw config value from DB or input.
        client: Extended bot client.
        guild_data: Raw guild document (for extra context if needed).
        guild: Hydrated guild wrapper.

    Returns:
        The parsed value (usually of type `T`).
    """
    return config

parse_from_database(config)

Reconstruct the runtime value from the DB representation.

This is the inverse of parse_to_database.

Parameters:

Name Type Description Default
config Any

Raw stored value from the database.

required

Returns:

Type Description
T

The deserialized runtime value of type T.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def parse_from_database(self, config: Any) -> T:
    """Reconstruct the runtime value from the DB representation.

    This is the inverse of `parse_to_database`.

    Args:
        config: Raw stored value from the database.

    Returns:
        The deserialized runtime value of type `T`.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

parse_to_database(value)

Parse the value to a database-friendly format.

Source code in settings/DefaultTypes/string.py
154
155
156
157
158
def parse_to_database(self, value: str) -> str:
    """
    Parse the value to a database-friendly format.
    """
    return value

parse_to_field(value)

Parse the value to a displayable string format.

Source code in settings/DefaultTypes/string.py
160
161
162
163
164
def parse_to_field(self, value: str) -> str:
    """
    Parse the value to a displayable string format.
    """
    return value

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Render the editor, collect free text, validate, and return it.

Parameters:

Name Type Description Default
view InteractionView

Active interaction view.

required

Returns:

Type Description
str

The updated string value.

Raises:

Type Description
TimeoutError

When the user does not provide input in time.

Source code in settings/DefaultTypes/string.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
async def run(self, view: InteractionView) -> str:
    """Render the editor, collect free text, validate, and return it.

    Args:
        view: Active interaction view.

    Returns:
        The updated string value.

    Raises:
        TimeoutError: When the user does not provide input in time.
    """
    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs = self.name, self.description, self.kwargs

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        name, description, kwargs = self.apply_locale(translate_module=translate_module)

    value_text = (
        translate("string_setting.too_long")
        if (len(self.value or "") > 1000)
        else (self.value or translate("string_setting.not_defined"))
    )

    embed = Embed(
        title=translate("string_setting.title", setting_name=name),
        description=description,
        color=int(self.color.lstrip("#"), 16),
    )
    embed.add_field(name=translate("string_setting.current_value"), value=value_text)
    if self.filter and "footer" in self.filter:
        embed.set_footer(text=self.filter["footer"])

    async def handle_set(inter: InteractionView):
        """
        Handles the set action to update the string value.
        """
        await inter.response.defer()
        input_embed = Embed(
            title=translate("string_setting.title", setting_name=name),
            description=description,
            color=int(self.color.lstrip("#"), 16),
        )
        input_embed.add_field(name=translate("string_setting.current_value"), value=value_text)
        input_embed.set_footer(text=translate("string_setting.enter_value"))
        await view.update(embeds=[input_embed], components=[])

        def message_filter(message):
            return message.author.id == view.interaction.user.id

        try:
            message = await view.client.wait_for(
                "message", check=message_filter, timeout=30
            )
        except TimeoutError:
            embed.set_footer(text=translate("string_setting.timeout"))
            await view.update(embeds=[embed])
            return

        value = message.content
        await message.delete()

        if self.filter and not self.filter["fn"](value):
            error_embed = Embed(
                title=translate("string_setting.title", setting_name=name),
                description=description,
                color=int(self.color.lstrip("#"), 16),
            )
            error_embed.add_field(name=translate("string_setting.current_value"), value=value_text)
            error_embed.set_footer(text=self.filter["error"])
            await view.update(embeds=[error_embed], components=[setButton])
            return

        self.value = value
        new_value_text = (
            translate("string_setting.too_long")
            if len(value) > 1000
            else value
        )
        embed = Embed(
            title=translate("string_setting.title", setting_name=self.name),
            color=int(self.color.lstrip("#"), 16),
        )
        embed.add_field(
            name=translate("string_setting.previous_value"), value=value_text
        )
        embed.add_field(
            name=translate("string_setting.new_value"), value=new_value_text
        )
        await view.update(embeds=[embed], components=[])
        view.stop()

    setButton = Button(
        label=translate("string_setting.set"),
        style=ButtonStyle.primary,
        custom_id="set",
    )
    setButton.callback = handle_set

    await view.update(embeds=[embed], components=[setButton])
    await view.wait()

    return self.value

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

Example

bank_name_setting = StringSettingFile(
    name="Bank name",
    description="The display name of your guild bank",
    id="bank_name",
    value="GigaBank"
)

settings.DefaultTypes.complex

ComplexSetting

Bases: Setting[Dict[str, Any]]

A composite setting that edits a dict of child settings interactively.

Renders one button per child field; clicking a button opens the child setting UI. A Confirm button validates required fields and finalizes.

Attributes:

Name Type Description
schema

Ordered map of field key -> child Setting.

update_fn

Callable (sync or async) that builds the current summary embed.

optionals

Optional list of keys that are not required.

Source code in settings/DefaultTypes/complex.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
class ComplexSetting(Setting[Dict[str, Any]]):
    """A composite setting that edits a dict of child settings interactively.

    Renders one button per child field; clicking a button opens the child
    setting UI. A **Confirm** button validates required fields and finalizes.

    Attributes:
        schema: Ordered map of field key -> child `Setting`.
        update_fn: Callable (sync or async) that builds the current summary embed.
        optionals: Optional list of keys that are not required.
    """

    def __init__(
        self,
        name: str,
        description: str,
        id: str,
        schema: Dict[str, Setting[Any]],
        update_fn: Callable[[Dict[str, Any], InteractionView], Embed],
        optionals: Optional[List[str]] = None,
        value: Optional[Dict[str, Any]] = None,
        locales: Optional[bool] = False,
        module_name: Optional[str] = None,
    ):
        """Initialize a `ComplexSetting`.

        Args:
            name: Display name.
            description: Description for UX.
            id: Persistence key.
            schema: Child settings map (one button per key).
            update_fn: Function that summarizes current state into an embed.
            optionals: Keys that may be omitted.
            value: Initial value (defaults to `{}`).
            locales: Enable i18n of display strings.
            module_name: Module context for translations/emojis.
        """
        super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="complex")
        self.schema = map_schema(schema)
        self.update_fn = update_fn
        self.optionals = optionals
        self.value = value or {}
        self.locales = locales
        self.module_name = module_name

    async def run(self, view: InteractionView) -> Dict[str, Any]:
        """Render the complex editor and handle child edits/confirmation.

        Flow:
            1. Build a summary embed via `update_fn`.
            2. Render a button for each `schema` key; clicking opens the child.
            3. Confirm validates required fields via `check_filled_schema`.
            4. On success, closes the view and returns the dict.

        Args:
            view: Active `InteractionView`.

        Returns:
            Final dictionary value.
        """

        guild_id = str(view.interaction.guild.id)
        translate = await view.client.translator.get_translator(guild_id=guild_id)

        name, description, kwargs, schema = self.name, self.description, self.kwargs, self.schema

        if self.module_name and self.locales:
            translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
            name, description, kwargs = self.apply_locale(translate_module=translate_module)
            schema = {}
            if hasattr(self, "schema"):
                for key, child in self.schema.items():
                    self.propagate_locales(child=child)
                    if child.module_name and child.locales:
                        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=child.module_name)
                        schema[key] = child.apply_locale(translate_module=translate_module, clone=True)
                        view.client.logger.info(f"Key: {key}, name: {child.name}, description: {child.description}")            


        async def update_embed():
            """
            Updates the embed with the current state of the settings.
            """
            if inspect.iscoroutinefunction(self.update_fn):
                return await self.update_fn(self.value, view)
            else:
                return self.update_fn(self.value, view)

        async def handle_child(interaction: Interaction, key: str):
            """
            Handles interactions for child settings.
            """
            await interaction.response.defer()
            setting = schema.get(key)
            if not setting:
                view.client.logger.warning(f"No setting found for key: {key}")
                return

            current_value = self.value or {}
            clone = setting.clone()

            if current_value.get(key):
                clone.value = current_value[key]

            try:
                # clone.value = self.value.get(key)
                new_value = await clone.run(view.clone())
                if new_value is not None:
                    self.value[key] = new_value
                    view.client.logger.debug(f"Updated value for key {key}: {new_value}")
            finally:
                updated_embed = await update_embed()
                await view.update(embeds=[updated_embed], components=buttons)


        async def handle_confirm(interaction: Interaction):
            """
            Handles the confirmation button interaction.
            """
            await interaction.response.defer()

            if not check_filled_schema(self):
                error_embed = Embed(
                    title=translate("error.title"),
                    description=translate("error.incomplete"),
                    color=0xFF6767,
                )
                await interaction.message.edit(embed=error_embed, view=view)
                return

            success_embed = Embed(
                title=translate("settings.completed", setting_name=name or self.name),
                description=translate("settings.success"),
                color=0xFFFFFF,
            )
            await interaction.message.edit(embed=success_embed, view=None)
            view.stop()
            view.client.logger.info("Configuration confirmed and view stopped.")

        def rebuild_buttons():
            """
            Rebuilds the buttons for the parent view.
            """
            buttons = []
            for key, setting in schema.items():
                button = Button(
                    label=setting.name,
                    style=ButtonStyle.primary,
                    custom_id=key,
                )

                async def child_callback(inter: Interaction, key=key):
                    await handle_child(inter, key)

                button.callback = child_callback
                buttons.append(button)

            confirm_button = Button(
                label=translate("settings.confirm"),
                style=ButtonStyle.success,
                custom_id="confirm",
            )
            confirm_button.callback = handle_confirm
            buttons.append(confirm_button)
            return buttons

        embed = await update_embed()
        buttons = rebuild_buttons()
        await view.update(embed=embed, components=buttons)

        await view.wait()
        return self.value

    def parse_to_database(self, value: Dict[str, Any]) -> Dict[str, Any]:
        """
        Prepares the complex value for database storage.
        """
        self.logger = getattr(self, 'logger', None) or print  # Use logger if available, else fallback to print
        serialized_data = {}
        self.logger(f"Schema Items: {self.schema.items()}")
        for key, setting in self.schema.items():
            try:
                if setting.parse_to_database:
                    self.logger(f"Parsing key '{key}' with value: {value.get(key)}")
                    serialized_value = setting.parse_to_database(value.get(key))
                    self.logger(f"Serialized value for key '{key}': {serialized_value}")
                else:
                    self.logger(f"No parse_to_database method for key '{key}'. Using raw value.")
                    serialized_value = value.get(key)
                serialized_data[key] = serialized_value
            except Exception as e:
                self.logger(f"Error while parsing key '{key}': {e}")
                raise

        return serialized_data


    async def parse(self, config: Any, client, guild_data: Any, guild: DiscordGuild) -> Dict[str, Any]:
        """
        Parses the configuration data from the database or input.
        """
        if isinstance(config, dict):
            parsed_values = {}
            for key, setting in self.schema.items():
                if setting.parse:
                    parsed_values[key] = await setting.parse(config.get(key), client, guild_data, guild)
                else:
                    parsed_values[key] = config.get(key)
            return parsed_values
        return {}

    def clone(self) -> "ComplexSetting":
        return ComplexSetting(
            name=self.name,
            description=self.description,
            id=self.id,
            schema=clone_schema(self.schema),
            update_fn=self.update_fn,
            optionals=self.optionals,
            value=self.value.copy(),
            locales=self.locales,
            module_name=self.module_name,
        )

__init__(name, description, id, schema, update_fn, optionals=None, value=None, locales=False, module_name=None)

Initialize a ComplexSetting.

Parameters:

Name Type Description Default
name str

Display name.

required
description str

Description for UX.

required
id str

Persistence key.

required
schema Dict[str, Setting[Any]]

Child settings map (one button per key).

required
update_fn Callable[[Dict[str, Any], InteractionView], Embed]

Function that summarizes current state into an embed.

required
optionals Optional[List[str]]

Keys that may be omitted.

None
value Optional[Dict[str, Any]]

Initial value (defaults to {}).

None
locales Optional[bool]

Enable i18n of display strings.

False
module_name Optional[str]

Module context for translations/emojis.

None
Source code in settings/DefaultTypes/complex.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def __init__(
    self,
    name: str,
    description: str,
    id: str,
    schema: Dict[str, Setting[Any]],
    update_fn: Callable[[Dict[str, Any], InteractionView], Embed],
    optionals: Optional[List[str]] = None,
    value: Optional[Dict[str, Any]] = None,
    locales: Optional[bool] = False,
    module_name: Optional[str] = None,
):
    """Initialize a `ComplexSetting`.

    Args:
        name: Display name.
        description: Description for UX.
        id: Persistence key.
        schema: Child settings map (one button per key).
        update_fn: Function that summarizes current state into an embed.
        optionals: Keys that may be omitted.
        value: Initial value (defaults to `{}`).
        locales: Enable i18n of display strings.
        module_name: Module context for translations/emojis.
    """
    super().__init__(name=name, description=description, locales=locales, module_name=module_name, id=id, type_="complex")
    self.schema = map_schema(schema)
    self.update_fn = update_fn
    self.optionals = optionals
    self.value = value or {}
    self.locales = locales
    self.module_name = module_name

apply_locale(translate_module, clone=False)

Apply module‑scoped translation to all display fields.

If self.locales is truthy, this will translate name, description, and any string values inside kwargs using the provided function.

Parameters:

Name Type Description Default
translate_module Callable[[str], str]

Function that maps a translation key to its value.

required
clone Optional[bool]

If True, returns a new translated copy of this setting; otherwise returns a tuple (name, description, kwargs) with translated values without mutating the instance.

False

Returns:

Type Description
Union[Setting[T], tuple[str, str, str]]
  • If clone=True: a new Setting instance (same type) with fields translated;
Union[Setting[T], tuple[str, str, str]]
  • If clone=False: a tuple (name, description, kwargs); or
Union[Setting[T], tuple[str, str, str]]
  • If self.locales is falsy: None.
Source code in settings/Setting.py
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
def apply_locale(self, translate_module: Callable[[str], str], clone: Optional[bool] = False) -> Union["Setting[T]", tuple[str, str, str]]:
    """Apply module‑scoped translation to all display fields.

    If `self.locales` is truthy, this will translate `name`, `description`,
    and any string values inside `kwargs` using the provided function.

    Args:
        translate_module: Function that maps a translation key to its value.
        clone: If True, returns a **new** translated copy of this setting;
            otherwise returns a tuple `(name, description, kwargs)` with
            translated values without mutating the instance.

    Returns:
        - If `clone=True`: a new `Setting` instance (same type) with fields
          translated;
        - If `clone=False`: a tuple `(name, description, kwargs)`; or
        - If `self.locales` is falsy: `None`.
    """
    if self.locales:
        if clone:
            setting_clone = self.clone()
            setting_clone.name = translate_module(self.name)
            setting_clone.description = translate_module(self.description)
            setting_clone.kwargs = {}
            for key, value in self.kwargs.items():
                if isinstance(value, str):
                    setting_clone.kwargs[key] = translate_module(value)
            return setting_clone
        else:
            name = translate_module(self.name)
            description = translate_module(self.description)
            kwargs = {}
            for key, value in self.kwargs.items():
                kwargs[key] = translate_module(value)
            return name, description, kwargs

parse(config, client, guild_data, guild) async

Parses the configuration data from the database or input.

Source code in settings/DefaultTypes/complex.py
266
267
268
269
270
271
272
273
274
275
276
277
278
async def parse(self, config: Any, client, guild_data: Any, guild: DiscordGuild) -> Dict[str, Any]:
    """
    Parses the configuration data from the database or input.
    """
    if isinstance(config, dict):
        parsed_values = {}
        for key, setting in self.schema.items():
            if setting.parse:
                parsed_values[key] = await setting.parse(config.get(key), client, guild_data, guild)
            else:
                parsed_values[key] = config.get(key)
        return parsed_values
    return {}

parse_from_database(config)

Reconstruct the runtime value from the DB representation.

This is the inverse of parse_to_database.

Parameters:

Name Type Description Default
config Any

Raw stored value from the database.

required

Returns:

Type Description
T

The deserialized runtime value of type T.

Raises:

Type Description
NotImplementedError

If the subclass does not override this method.

Source code in settings/Setting.py
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def parse_from_database(self, config: Any) -> T:
    """Reconstruct the runtime value from the DB representation.

    This is the inverse of `parse_to_database`.

    Args:
        config: Raw stored value from the database.

    Returns:
        The deserialized runtime value of type `T`.

    Raises:
        NotImplementedError: If the subclass does not override this method.
    """
    raise NotImplementedError("Must be implemented in derived classes.")

parse_to_database(value)

Prepares the complex value for database storage.

Source code in settings/DefaultTypes/complex.py
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def parse_to_database(self, value: Dict[str, Any]) -> Dict[str, Any]:
    """
    Prepares the complex value for database storage.
    """
    self.logger = getattr(self, 'logger', None) or print  # Use logger if available, else fallback to print
    serialized_data = {}
    self.logger(f"Schema Items: {self.schema.items()}")
    for key, setting in self.schema.items():
        try:
            if setting.parse_to_database:
                self.logger(f"Parsing key '{key}' with value: {value.get(key)}")
                serialized_value = setting.parse_to_database(value.get(key))
                self.logger(f"Serialized value for key '{key}': {serialized_value}")
            else:
                self.logger(f"No parse_to_database method for key '{key}'. Using raw value.")
                serialized_value = value.get(key)
            serialized_data[key] = serialized_value
        except Exception as e:
            self.logger(f"Error while parsing key '{key}': {e}")
            raise

    return serialized_data

propagate_locales(child)

Propagate locales and module_name flags to a child setting.

Useful when composite settings are composed of other settings and you want consistent translation behavior across the hierarchy.

Parameters:

Name Type Description Default
child Setting[Any]

The setting that should inherit locale info.

required
Source code in settings/Setting.py
217
218
219
220
221
222
223
224
225
226
227
228
def propagate_locales(self, child: "Setting[Any]"):
    """Propagate `locales` and `module_name` flags to a child setting.

    Useful when composite settings are composed of other settings and you
    want consistent translation behavior across the hierarchy.

    Args:
        child: The setting that should inherit locale info.
    """
    if self.locales and self.module_name:
        child.locales = self.locales
        child.module_name = self.module_name

run(view) async

Render the complex editor and handle child edits/confirmation.

Flow
  1. Build a summary embed via update_fn.
  2. Render a button for each schema key; clicking opens the child.
  3. Confirm validates required fields via check_filled_schema.
  4. On success, closes the view and returns the dict.

Parameters:

Name Type Description Default
view InteractionView

Active InteractionView.

required

Returns:

Type Description
Dict[str, Any]

Final dictionary value.

Source code in settings/DefaultTypes/complex.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
async def run(self, view: InteractionView) -> Dict[str, Any]:
    """Render the complex editor and handle child edits/confirmation.

    Flow:
        1. Build a summary embed via `update_fn`.
        2. Render a button for each `schema` key; clicking opens the child.
        3. Confirm validates required fields via `check_filled_schema`.
        4. On success, closes the view and returns the dict.

    Args:
        view: Active `InteractionView`.

    Returns:
        Final dictionary value.
    """

    guild_id = str(view.interaction.guild.id)
    translate = await view.client.translator.get_translator(guild_id=guild_id)

    name, description, kwargs, schema = self.name, self.description, self.kwargs, self.schema

    if self.module_name and self.locales:
        translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=self.module_name)
        name, description, kwargs = self.apply_locale(translate_module=translate_module)
        schema = {}
        if hasattr(self, "schema"):
            for key, child in self.schema.items():
                self.propagate_locales(child=child)
                if child.module_name and child.locales:
                    translate_module = await view.client.translator.get_translator(guild_id=guild_id, module_name=child.module_name)
                    schema[key] = child.apply_locale(translate_module=translate_module, clone=True)
                    view.client.logger.info(f"Key: {key}, name: {child.name}, description: {child.description}")            


    async def update_embed():
        """
        Updates the embed with the current state of the settings.
        """
        if inspect.iscoroutinefunction(self.update_fn):
            return await self.update_fn(self.value, view)
        else:
            return self.update_fn(self.value, view)

    async def handle_child(interaction: Interaction, key: str):
        """
        Handles interactions for child settings.
        """
        await interaction.response.defer()
        setting = schema.get(key)
        if not setting:
            view.client.logger.warning(f"No setting found for key: {key}")
            return

        current_value = self.value or {}
        clone = setting.clone()

        if current_value.get(key):
            clone.value = current_value[key]

        try:
            # clone.value = self.value.get(key)
            new_value = await clone.run(view.clone())
            if new_value is not None:
                self.value[key] = new_value
                view.client.logger.debug(f"Updated value for key {key}: {new_value}")
        finally:
            updated_embed = await update_embed()
            await view.update(embeds=[updated_embed], components=buttons)


    async def handle_confirm(interaction: Interaction):
        """
        Handles the confirmation button interaction.
        """
        await interaction.response.defer()

        if not check_filled_schema(self):
            error_embed = Embed(
                title=translate("error.title"),
                description=translate("error.incomplete"),
                color=0xFF6767,
            )
            await interaction.message.edit(embed=error_embed, view=view)
            return

        success_embed = Embed(
            title=translate("settings.completed", setting_name=name or self.name),
            description=translate("settings.success"),
            color=0xFFFFFF,
        )
        await interaction.message.edit(embed=success_embed, view=None)
        view.stop()
        view.client.logger.info("Configuration confirmed and view stopped.")

    def rebuild_buttons():
        """
        Rebuilds the buttons for the parent view.
        """
        buttons = []
        for key, setting in schema.items():
            button = Button(
                label=setting.name,
                style=ButtonStyle.primary,
                custom_id=key,
            )

            async def child_callback(inter: Interaction, key=key):
                await handle_child(inter, key)

            button.callback = child_callback
            buttons.append(button)

        confirm_button = Button(
            label=translate("settings.confirm"),
            style=ButtonStyle.success,
            custom_id="confirm",
        )
        confirm_button.callback = handle_confirm
        buttons.append(confirm_button)
        return buttons

    embed = await update_embed()
    buttons = rebuild_buttons()
    await view.update(embed=embed, components=buttons)

    await view.wait()
    return self.value

save(client, entity, setting) async

Persist the setting value using the client DB API.

Default behavior
  1. Serializes setting.value via parse_to_database if available.
  2. Writes to:
  3. guilds collection with filter {"_id": str(guild.id)} when entity is a Guild;
  4. members collection with filter {"_id": str(member.id)} when entity is a Member.
Note

If your schema uses different keys (e.g., composite keys {"id": ..., "guildId": ...} for members), override this method in your concrete setting type or adapt it at call‑site.

Parameters:

Name Type Description Default
client ExtendedClient

Extended bot client (exposes client.db.update_one).

required
entity Union[Guild, Member]

Guild or Member instance that owns the setting.

required
setting Setting[T]

The concrete Setting instance being saved.

required

Returns:

Type Description
bool

An awaitable that resolves truthy on success.

Source code in settings/Setting.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
async def save(self, client: ExtendedClient, entity: Union["Guild", "Member"], setting: "Setting[T]") -> bool:
    """Persist the setting value using the client DB API.

    Default behavior:
      1. Serializes `setting.value` via `parse_to_database` if available.
      2. Writes to:
         - `guilds` collection with filter `{"_id": str(guild.id)}` when
           `entity` is a `Guild`;
         - `members` collection with filter `{"_id": str(member.id)}` when
           `entity` is a `Member`.

    Note:
        If your schema uses different keys (e.g., composite keys
        `{"id": ..., "guildId": ...}` for members), override this method in
        your concrete setting type or adapt it at call‑site.

    Args:
        client: Extended bot client (exposes `client.db.update_one`).
        entity: Guild or Member instance that owns the setting.
        setting: The concrete `Setting` instance being saved.

    Returns:
        An awaitable that resolves truthy on success.
    """
    client.logger.debug(f"Using default save method for setting: {self.id}")

    if hasattr(setting, "parse_to_database") and callable(setting.parse_to_database):
        if setting.value is not None:
            value = setting.parse_to_database(setting.value)
        else:
            client.logger.warning(f"Setting value is None; storing as None in database.")
            value = None
    else:
        client.logger.warning(f"Setting does not have a parse_to_database method. Using raw value.")
        value = setting.value

    query = {f"settings.{setting.id}": value}

    if isinstance(entity, Guild):
        result = await client.db.update_one(
            "guilds",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    elif isinstance(entity, Member):
        result = await client.db.update_one(
            "members",
            {"_id": str(entity.id)},
            {"$set": query},
        )
        return bool(result)
    else:
        raise TypeError("Entity must be a Guild or Member.")

check_filled_schema(current_config)

Check if all required schema fields are present in .value.

Parameters:

Name Type Description Default
current_config ComplexSetting

The complex setting instance.

required

Returns:

Type Description
bool

True if all required fields are filled; otherwise False.

Source code in settings/DefaultTypes/complex.py
42
43
44
45
46
47
48
49
50
51
52
53
54
def check_filled_schema(current_config: "ComplexSetting") -> bool:
    """Check if all required schema fields are present in `.value`.

    Args:
        current_config: The complex setting instance.

    Returns:
        True if all required fields are filled; otherwise False.
    """
    for key, value in current_config.schema.items():
        if not current_config.value.get(key) and key not in (current_config.optionals or []):
            return False
    return True

chunk_arr(arr, size)

Split a list into chunks of size size.

Parameters:

Name Type Description Default
arr List[Any]

Input list.

required
size int

Chunk size.

required

Returns:

Type Description
List[List[Any]]

List of chunks.

Source code in settings/DefaultTypes/complex.py
29
30
31
32
33
34
35
36
37
38
39
def chunk_arr(arr: List[Any], size: int) -> List[List[Any]]:
    """Split a list into chunks of size `size`.

    Args:
        arr: Input list.
        size: Chunk size.

    Returns:
        List of chunks.
    """
    return [arr[i:i + size] for i in range(0, len(arr), size)]

clone_schema(schema)

Deep‑clone a schema by calling .clone() on each child setting.

Parameters:

Name Type Description Default
schema Dict[str, T]

Original mapping.

required

Returns:

Type Description
Dict[str, T]

New ordered mapping with cloned children.

Source code in settings/DefaultTypes/complex.py
57
58
59
60
61
62
63
64
65
66
def clone_schema(schema: Dict[str, T]) -> Dict[str, T]:
    """Deep‑clone a schema by calling `.clone()` on each child setting.

    Args:
        schema: Original mapping.

    Returns:
        New ordered mapping with cloned children.
    """
    return OrderedDict({key: value.clone() for key, value in schema.items()})

map_schema(schema)

Normalize a schema to an ordered dict for deterministic iteration.

Parameters:

Name Type Description Default
schema Dict[str, Setting[Any]]

Mapping of field key -> Setting.

required

Returns:

Type Description
Dict[str, Setting[Any]]

Ordered mapping suitable for stable rendering / iteration.

Source code in settings/DefaultTypes/complex.py
17
18
19
20
21
22
23
24
25
26
def map_schema(schema: Dict[str, Setting[Any]]) -> Dict[str, Setting[Any]]:
    """Normalize a schema to an ordered dict for deterministic iteration.

    Args:
        schema: Mapping of field key -> `Setting`.

    Returns:
        Ordered mapping suitable for stable rendering / iteration.
    """
    return OrderedDict(schema)

Example

xp_settings = ComplexSetting(
    name="XP system",
    description="Configure XP roles and multipliers",
    id="xp_system",
    schema={
        "role": RoleSetting(
            name="XP role",
            description="Role assigned at a certain XP",
            id="xp_role"
        ),
        "xp_required": NumberSetting(
            name="XP required",
            description="XP required to unlock the role",
            id="xp_required",
            value=1000,
            minValue=1,
            maxValue=999999
        ),
        "remove_previous_role": BooleanSetting(
            name="Remove previous role",
            description="Remove older XP roles when a new one is assigned",
            id="remove_previous_role",
            value=True
        )
    }
)

ComplexSetting allows you to compose multiple child settings together into a single structured object, making it the foundation for advanced configurations like the XP system or the economy module.