Developer Portal
Eksperimental Aktibo pang dine-develop ang bot at app platform. Na-ship ang suporta para sa self-hosted servers noong 2026-08-13, pero mas kaunti ang features nito kumpara sa cloud. Tingnan kung ano ang supported.
← Docs

In-App Settings

Kayang i-render ng GameVox ang configuration form ng app mo sa loob ng client. Idine-describe ng app mo ang form sa gateway, ine-edit ito ng operator sa Mga Setting ng Server ▸ Mga Integration ▸ Mga Setting, at diretsong bumabalik sa bot mo ang na-edit na values. Walang ini-store ang GameVox: ang app mo pa rin ang nag-iisang may-ari ng configuration nito.

Walang katapat ito sa Discord. Ito lang ang bahagi ng GameVox app platform na sinadyang hindi compatible, dahil wala namang nasa kabilang side na puwedeng pag-compatible-an.

Ang Server Settings window ng GameVox client, na nagpapakita ng Settings tab ng isang naka-install na app na may language dropdown, toggle, at channel checklist.
Isang live na panel, buong nire-render mula sa reply ng iisang bot: isang select, isang boolean at isang channels picker, naka-group sa isang section na may description. Walang ipinadalang channel ids ang bot: ang GameVox ang nag-supply ng listahang iyon. Tinakpan ang mga pangalan ng server at channel.

Bakit ito meron

Sa Discord, ang bot na may kahit anong configuration ay may kasamang web dashboard: domain, OAuth2 login, session handling, permission check na binabasa ulit ang guild list ng user, CSRF protection, at hosting para sa lahat ng iyon. Kadalasan, para lang makapili ang operator ng log channel at makapag-toggle ng tatlong features. Alam na ng GameVox kung sino ang operator, aling server ang kino-configure nila, at kung pinapayagan silang i-configure ito, kaya direkta na nitong ibinibigay ang surface na iyon sa app mo.

DiscordGameVox
Saan kino-configure ng operators ang app mo Sa web dashboard na ikaw ang nagho-host Mga Setting ng Server ▸ Mga Integration ▸ Mga Setting
Ano ang bubuuin mo Web app, OAuth2 login, sessions, permission checks, hosting Isang gateway handler at isang REST call
Sino ang nag-a-authenticate sa operator Ikaw, gamit ang OAuth2 Ang GameVox, bago pa man tanungin ang app mo
Sino ang nag-i-store ng configuration Ikaw Ikaw. Walang itinatagong kahit ano ang GameVox.
Channel at role pickers Ikaw ang kukuha ng channels at roles ng guild at ikaw ang magre-render Magbibigay ka ng type; ang GameVox ang magsu-supply ng choices
Gastos para mag-expose ng isang toggle Isang dashboard Mga dalawampung linya

Wala itong pinapalitan. Gumagana pa rin ang slash commands, at kung may dashboard ka na, puwede mo itong panatilihin. Hindi exclusive ang dalawa, at maraming apps ang gugustuhin ang panel para sa limang settings na talagang binabago ng operators at ang dashboard para sa lahat ng iba pa.

Paano ito gumagana

Binuksan ng operator ang Settings tab
      │
      ▼
GameVox ──── APP_SETTINGS_REQUEST (action: "describe") ────▶ ang bot mo
                                                                │
ang bot  ─── POST /applications/@me/settings-response ──────────┘
             { nonce, version: 2, sections: [ ... ] }
      │
      ▼
Nire-render ng GameVox ang form, ine-edit ng operator, kini-click ang Save
      │
      ▼
GameVox ──── APP_SETTINGS_REQUEST (action: "save", values) ─▶ ang bot mo
                                                                │
ang bot  ─── POST /applications/@me/settings-response ──────────┘
             { nonce, message: "Na-save." }

Dalawang action, isang request shape, isang reply endpoint. Tinatanong ng describe kung ano ang ine-expose ng app mo para sa server na ito; ibinabalik naman ng save ang binago ng operator. Parehong paraan ang pagsagot sa dalawa.

1. Pagtanggap ng request

Dini-dispatch ng GameVox ang APP_SETTINGS_REQUEST sa gateway papunta sa kung aling session ang may hawak ng connection ng app mo. Walang intent na nagga-gate dito: naka-address ang dispatch sa app mo mismo, hindi fina-filter laban sa intent mask, kaya darating ito kahit ano pa ang in-identify mo.

{
  "op": 0,
  "t": "APP_SETTINGS_REQUEST",
  "d": {
    "nonce": "5f3c1b0e-8a4d-4b2f-9c11-2f7a6d0b4e93",
    "server_id": "1387452901234567890",
    "action": "describe",
    "schema_version": 2
  }
}
FieldTypeMga tala
nonce string Opaque. Ibalik ito nang verbatim; ito lang ang nag-uugnay sa reply mo at sa naghihintay na operator. Puwedeng sagutin sa loob ng 15 segundo.
server_id snowflake Ang guild, sa parehong id space na ginagamit ng bawat ibang dispatch. Ito ang id na dala ng GUILD_CREATE mo, kaya mare-resolve ito ng existing mong guild cache.
action string "describe" o "save".
schema_version integer Ang pinaka-rich na schema na kayang i-render ng server na ito. Sa ngayon, 2. Tingnan ang Pagsisilbi sa mas lumang server.
values object Nasa save lang. Ang keys ay ang field keys na ipinadala mo.

Ang server_id ay sine-stamp ng GameVox mula sa sariling authenticated na request ng operator. Hindi ito binabasa pabalik mula sa reply mo, kaya hindi puwedeng sagutin ng app ang tanong tungkol sa isang server gamit ang settings para sa ibang server.

Pagbasa ng dispatch sa library mo

Walang bot library na may handler para sa event na wala sa Discord, kaya nadodrop ang packet bago pa ito umabot sa event handler. Bawat library ay nag-e-expose ng raw dispatch stream para mismo sa kasong ito:

LibraryHookI-enable
discord.js (v14) client.on('raw', packet) Naka-on by default. Ine-emit para sa bawat dispatch bago i-handle ang packet.
discord.py (v2) on_socket_raw_receive(msg) Ipasa ang enable_debug_events=True sa client.
Eris client.on('rawWS', packet) Naka-on by default.
JDA RawGatewayEvent JDABuilder.setRawEventsEnabled(true)
DSharpPlus DiscordClient.UnknownEvent Naka-on by default. EventName kasama ang raw na Json.
serenity RawEventHandler::raw_event ClientBuilder::raw_event_handler

Sa totoong Discord, hindi kailanman magfa-fire ang handler na ito, kaya tatakbo ang parehong build sa dalawang platform nang walang branch.

2. Pag-reply

Sumagot sa REST, hindi sa gateway. Bawat library ay may HTTP client; karamihan ay hindi pinapayagan ang application code na magsulat ng kahit anong opcode sa socket, kaya ganito ang shape ng reply.

POST https://app-api.gamevox.com/api/v10/applications/@me/settings-response
Authorization: Bot YOUR_BOT_TOKEN
Content-Type: application/json
{
  "nonce": "5f3c1b0e-8a4d-4b2f-9c11-2f7a6d0b4e93",
  "version": 2,
  "message": "Opsyonal na note na ipinapakita sa itaas ng form.",
  "sections": [
    {
      "key": "welcome",
      "label": "Mga welcome message",
      "description": "Pino-post kapag may sumali.",
      "fields": [
        { "key": "welcome.enabled", "label": "Magpadala ng welcome message",
          "type": "boolean", "value": true }
      ]
    }
  ]
}

Nagbabalik ng 204 No Content ang successful na reply. Puwede mong idugtong ang ?server_id= kasama ang guild id; ginagamit lang ito para ma-attribute ang call sa activity log ng app mo.

KeyTypeMga tala
nonce string Kailangan. Galing sa request.
version integer Ang schema na ginagamit mo sa pagsagot. Ipadala ang 2 kapag nagpapadala ka ng sections.
sections array Schema v2 na form. Hindi puwedeng kasabay ng fields.
fields array Schema v1 na flat form. Nire-render bilang isang section na walang label.
message string Sa describe, isang note sa itaas ng form. Sa save, ang confirmation toast. Pinuputol sa 200 characters.
error string Ipinapakita sa operator sa halip na form. Pinuputol sa 300 characters.

Laging sumagot, kahit may failure. Kapag tahimik na dinrop ang request, maiiwan ang operator sa spinner hanggang sa 12-second timeout ng GameVox, na mababasa bilang “sira ang app na ito” sa halip na “nagka-problema lang minsan”.

3. Mga field type

Labindalawang type, at sarado ang listahan: ang hindi kilalang type ay nagre-reject sa buong reply sa halip na mag-render bilang wala.

Type Control value na ipinapadala moAno ang babalik sa iyo pag-save
string Isang linyang text box string string ("" kapag blangko)
text Textarea string string
boolean Toggle switch boolean true / false
number Numeric input number number, o null kapag kinlear
select Dropdown ng options mo option value string, o null kapag unset
multiselect Checklist ng options mo array ng option values array ng strings (puwedeng empty)
channel Dropdown ng channels ng server na ito channel snowflake snowflake string, o null
channels Checklist ng channels ng server na ito array ng snowflakes array ng snowflake strings
role Dropdown ng mga grupo ng server na ito role snowflake snowflake string, o null
roles Checklist ng mga grupo ng server na ito array ng snowflakes array ng snowflake strings
color Colour swatch #rrggbb #rrggbb, laging meron
static Read-only na linya ng text string Hindi kailanman sina-submit: wala ang key sa values

Laging may value ang colour input, kaya ang app na walang in-store ay makakatanggap ng #000000 sa unang save. Kung kailangan mo ng “walang kulay”, ipares ang swatch sa isang boolean.

4. Mga field attribute

AttributePara saEpekto
key lahat Kailangan. [A-Za-z0-9][A-Za-z0-9._:-]{0,63}. Unique sa buong reply, hindi kada section.
label lahat Key ang fallback. Pinuputol sa 100 characters.
help lahat Hint line sa ilalim ng control. Pinuputol sa 200.
placeholder text inputs, number Pinuputol sa 100.
options select, multiselect Kailangan para sa dalawang iyon. { value, label, description }, max 25. Dinodrop sa kahit anong ibang type.
min , max , step number Hinuhubog ang input. Advisory lang: i-check ulit ang value na matatanggap mo.
max_length string, text Nilalagyan ng cap ang input, hanggang sa platform ceiling para sa type na iyon.
channel_kinds channel, channels Pinakikitid ang picker. text, voice, forum, news, fileshare, header. Dinodrop ang hindi kilalang kinds; kapag empty ang resulta, lahat ng kind ang ibig sabihin.
required lahat Minamarkahan ang label at pinapalitan ang “Wala” slot ng picker ng “Pumili…”. Presentational lang: i-enforce ito pag-save.
disabled lahat Ginagawang gray ang control. Sina-submit pa rin, kasama ang kasalukuyang value nito.
secret string, text Mina-mask ang input at binablangko ang kasalukuyang value bago ipadala. Nire-reject ang reply kapag ibang type.
show_if lahat { key, equals }. Nakatago ang field hanggang may ibang field sa parehong reply na may hawak ng value na iyon.

Equality test lang ang show_if at wala nang iba: walang expressions, walang operators. Ang nire-reference na key ay dapat galing sa isang single-value field sa parehong reply at hindi puwedeng sariling key ng field; ang condition na pumalya sa alinman dito ay dinodrop at palaging lalabas ang field. Puwedeng string, boolean, number o null ang equals.

Ang nakatagong field ay sina-submit pa rin. May value ito na ipinadala ng app mo, at kapag dinrop ito, mababasa ng app mo na parang kinlear ito ng operator.

5. Channel at role pickers

Ito ang bahaging sulit intindihin, dahil binabaligtad nito ang karaniwang ayos. Magbibigay ang app mo ng kind; ang GameVox ang magsu-supply ng choices.

Walang dalang ids ang channel field. Magpapadala ka ng { "type": "channel" } at, kung gusto mo, isang channel_kinds filter. Ikakabit ng GameVox ang sariling channel list ng server na ito, gagawa ang client ng picker mula dito, at ang option lang na ibinigay ng GameVox ang puwedeng i-submit ng operator.

Bawat catalog entry ay naka-key sa parehong snowflake na nakikita na ng app mo para sa channel o grupong iyon. Walang id translation step na puwedeng magkamali: pipili ang operator ng label, makakatanggap ang app mo ng id sa sarili nitong namespace, at ang value na tumutukoy sa bagay na wala sa server na ito ay hindi lang magma-match sa kahit anong option.

Isang channels field na naka-render bilang checklist ng sariling channels ng server, bawat isa may kind glyph sa unahan.
Ito lang ang buong ipinadala ng app para sa field na ito: { "key": "ignored", "type": "channels", "channel_kinds": ["text", "news", "forum", "fileshare"] }. Sa GameVox galing ang listahan, ang kind glyphs at ang ids.
SitwasyonAno ang nakikita ng operator
Wala pang naka-store Isang “Wala” slot, naka-select. Ang required na field ay nagpapakita ng “Pumili…” sa halip, kaya hindi makakapag-commit ang save ng value na walang pumili.
Wala na ang naka-store na id (na-delete ang channel) Pinapanatili bilang “Kasalukuyang napili (wala na sa listahan)”, naka-select pa rin. Hindi ito tahimik na kinli-clear ng pag-save.
Mga naka-store na id sa multi-picker na hindi na nakalista Nire-render at naka-check kasama ng mga live, sa parehong dahilan.
Hindi available ang catalog (offline ang self-hosted box) Naka-disable na picker na may nakasulat na “Hindi available sa ngayon”. Ibinabalik ang naka-store na value pag-save, kaya hindi mabubura ang setting kapag binuksan ang panel habang may outage.
Mahigit 500 channels o grupo ang server Pinuputol ang listahan sa 500 sa halip na magpadala ng isang megabyte ng options.

Dalawang bagay ang hindi kasama sa role catalog: ang server owner group, na hindi dapat ialok sa kahit anong app bilang assignable role, at ang per-app groups na may dalang sariling permissions ng mga naka-install na app.

6. Mga scenario

Scenario 1: Ang pinakamaliit na useful na panel

Isang toggle, walang sections. Valid ang flat na fields array sa kahit anong schema version at nire-render bilang iisang grupo na walang label.

{
  "nonce": nonce,
  "fields": [
    { "key": "greetings", "label": "Batiin ang mga bagong miyembro", "type": "boolean", "value": true }
  ]
}

Scenario 2: Pag-group sa sections

{
  "nonce": nonce,
  "version": 2,
  "sections": [
    {
      "key": "general",
      "label": "Pangkalahatan",
      "description": "Paano kumikilos ang bot sa buong server na ito.",
      "fields": [
        { "key": "prefix", "label": "Command prefix", "type": "string",
          "value": "!", "max_length": 4, "help": "Ginagamit para sa legacy na text commands." }
      ]
    },
    {
      "key": "logging",
      "label": "Audit logging",
      "fields": [
        { "key": "log.enabled", "label": "I-log ang moderation actions", "type": "boolean", "value": false }
      ]
    }
  ]
}

Opsyonal ang section key pero dapat unique kapag meron. Parehong opsyonal ang label at description; hindi nire-render ang section na walang fields.

Scenario 3: Humingi ng channel

Walang ids, walang pagkuha ng channel list ng guild. Limitahan ang picker sa mga kind na puwedeng paglagyan ng message.

{
  "key": "log.channel",
  "label": "Log channel",
  "type": "channel",
  "value": stored.logChannelId ?? null,
  "channel_kinds": ["text", "news"],
  "help": "Kung saan nire-record ang moderation actions."
}

Gamitin ang "channel_kinds": ["header"] kapag category ang gusto mo at hindi lugar na pagpo-post-an: headers ang categories ng GameVox.

Scenario 4: Magpakita ng fields depende sa condition

Ang karaniwang shape: isang boolean na nagga-gate sa natitirang bahagi ng feature nito.

"fields": [
  { "key": "welcome.enabled", "label": "Magpadala ng welcome message",
    "type": "boolean", "value": true },

  { "key": "welcome.channel", "label": "Channel", "type": "channel",
    "value": stored.welcomeChannel ?? null,
    "channel_kinds": ["text", "news"],
    "show_if": { "key": "welcome.enabled", "equals": true } },

  { "key": "welcome.message", "label": "Mensahe", "type": "text",
    "value": stored.welcomeMessage ?? "",
    "max_length": 1800,
    "placeholder": "Welcome {user} sa {server}!",
    "help": "Pinapalitan ang {user}, {server} at {membercount} kapag ipinadala.",
    "show_if": { "key": "welcome.enabled", "equals": true } }
]

Puwede ring mag-key ang conditions sa isang select, at ganyan ka gagawa ng mode switch: "show_if": { "key": "mode", "equals": "advanced" }.

Scenario 5: Sarili mong choices

Ang select at multiselect ang dalawang type na may sariling options. Parehong kailangan ng kahit isang option at hanggang 25 lang.

{
  "key": "automod.action",
  "label": "Kapag nag-match ang isang filter",
  "type": "select",
  "value": stored.action,
  "required": true,
  "options": [
    { "value": "delete",  "label": "I-delete ang message" },
    { "value": "warn",    "label": "Bigyan ng warning ang author",  "description": "Dine-delete at dine-DM ang member." },
    { "value": "timeout", "label": "I-timeout ang author", "description": "10 minuto." },
    { "value": "none",    "label": "Walang gagawin" }
  ]
}
{
  "key": "automod.filters",
  "label": "Mga aktibong filter",
  "type": "multiselect",
  "value": stored.filters,
  "options": [
    { "value": "invites",   "label": "Discord/GameVox invites" },
    { "value": "links",     "label": "Mga link" },
    { "value": "mentions",  "label": "Mass mentions" },
    { "value": "caps",      "label": "Sobrang caps" }
  ]
}

Lumalabas ang description ng isang option pagkatapos ng label sa dropdown at bilang tooltip sa checklist.

Scenario 6: Roles

Parehong catalog rule gaya ng channels. Inililista ng GameVox ang mga grupo ng server na ito ayon sa rank, kasama ang mga kulay nila.

"fields": [
  { "key": "autorole.role", "label": "Role na ibinibigay pagsali",
    "type": "role", "value": stored.autoRole ?? null },

  { "key": "moderator.roles", "label": "Mga role na puwedeng gumamit ng moderation commands",
    "type": "roles", "value": stored.modRoles,
    "help": "Ang members na may alinman dito ay puwedeng magpatakbo ng /ban at /timeout." }
]

Scenario 7: Numbers na may hangganan

{
  "key": "automod.threshold",
  "label": "Bilang ng messages bago ideklarang raid",
  "type": "number",
  "value": stored.threshold ?? 10,
  "min": 3,
  "max": 100,
  "step": 1,
  "help": "Binibilang sa rolling na 60-second window."
}

Hinuhubog lang ng min, max at step ang control. Hindi validator na kontrolado mo ang browser ng operator, kaya i-clamp ulit ang value pagbalik nito. Tandaan din na kapag kinlear ang box, null ang sina-submit, hindi 0.

Scenario 8: Secrets

Markahan ang isang credential bilang secret at bablangkuhin ng GameVox ang kasalukuyang value nito bago ipadala. Wala ito sa WebSocket frame, wala sa DOM, at wala sa screenshot ng panel: empty na masked box na may placeholder na “Walang pagbabago” ang nakikita ng operator.

{
  "key": "integrations.apiKey",
  "label": "Weather API key",
  "type": "string",
  "secret": true,
  "help": "Iwanang blangko para panatilihin ang kasalukuyang key."
}

Ang kontratang sumusunod dito: ang empty na sina-submit na secret ay nangangahulugang “huwag galawin”, hindi kailanman “i-clear ito”. Kung kailangang mag-alis ng operator ng credential, bigyan sila ng explicit na boolean para gawin iyon. Nire-reject ang reply kapag may secret sa kahit anong type maliban sa string o text.

Scenario 9: Read-only na status

Nagre-render ang static ng isang linya ng text at hindi kailanman sina-submit. Gamitin ito para sa state na kailangan ng operator habang nagko-configure pero hindi puwedeng i-edit dito.

"fields": [
  { "key": "plan", "label": "Plan", "type": "static", "value": "Pro: 4,000 lookups/araw" },
  { "key": "usage", "label": "Nagamit ngayong araw", "type": "static", "value": "1,284 lookups" },
  { "key": "lastSync", "label": "Huling sync", "type": "static", "value": "2026-09-02 14:31 UTC" }
]

Scenario 10: Pag-handle ng save

May dalang values ang save request, naka-key sa field keys na ipinadala mo. I-persist, tapos sumagot ng confirmation.

// action === "save"
const v = req.values ?? {};

const guild = client.guilds.cache.get(req.server_id);
if (!guild) return reply(nonce, { error: "Wala na ang bot na ito sa server na iyon." });

const patch = {};

// boolean: laging totoong boolean
patch.welcomeEnabled = v["welcome.enabled"] === true;

// channel: snowflake string o null; i-check ulit laban sa guild na ITO
const chan = v["welcome.channel"];
patch.welcomeChannel =
  (typeof chan === "string" && guild.channels.cache.has(chan)) ? chan : null;

// number: number o null kapag kinlear ng operator ang box
const n = v["automod.threshold"];
patch.threshold = typeof n === "number" ? Math.min(100, Math.max(3, Math.round(n))) : 10;

// secret: kapag empty, walang binago
const key = v["integrations.apiKey"];
if (typeof key === "string" && key.trim() !== "") patch.apiKey = key.trim();

await store.update(req.server_id, patch);
await reply(nonce, { message: "Na-save ang settings." });

Ang message mo ang magiging confirmation toast ng operator. Iniiwan ang form nang eksaktong gaya ng pagkakaiwan nila, kaya puwede pa silang magpatuloy sa pag-edit.

Scenario 11: Pag-reject ng save

Magbalik ng error at ang text mo ang makikita ng operator sa halip na confirmation. Gamitin ito para sa kahit anong hindi mo matatanggap: value na pumalya sa sarili mong validation, plan limit, upstream credential na hindi na gumagana.

// presentational lang ang `required`: dito ito i-enforce.
const action = v["automod.action"];
if (!["delete", "warn", "timeout", "none"].includes(action)) {
  return reply(nonce, guildId, {
    error: "Piliin kung ano ang gagawin ng AutoMod kapag nag-match ang isang filter.",
  });
}

// Anumang bagay na sa side mo lang malalaman.
if (patch.apiKey && !(await upstream.verifyKey(patch.apiKey))) {
  return reply(nonce, guildId, {
    error: "Tinanggihan ng weather provider ang API key na iyan.",
  });
}

Ganoon din sa describe: kung down ang database mo, sumagot ng error sa halip na hindi sumagot. Mas maganda para sa operator ang “Hindi mabasa ng app na ito ang settings nito sa ngayon” kaysa sa labindalawang segundong spinner.

Scenario 12: Pagsisilbi sa mas lumang server

May dalang schema_version ang request, kaya hindi mo na kailangang mag-probe. Ibigay ang pinaka-rich na form na talagang kayang i-render ng server.

const version = typeof req.schema_version === "number" ? req.schema_version : 1;

if (version >= 2) {
  await reply(nonce, { version: 2, sections: describeV2(guildId) });
} else {
  // v1: flat na listahan, apat na type: string, boolean, number, select
  await reply(nonce, { fields: describeV1(guildId) });
}

Totoo rin ang kabaligtaran. Nare-render pa rin ang v2 reply na umabot sa mas lumang client: nagpapadala ang GameVox ng flattened na fields array kasabay ng sections, at ang client na mas nauna sa mga bagong type ay dino-drawing ang mga ito bilang text boxes. Degraded, pero walang nakatago.

Scenario 13: Self-hosted servers

Gumagana nang walang binabago. Sa isang self-hosted server, kinukuha ang channel at role catalogs mula sa sariling box ng customer sa halip na sa cloud tables, at hindi ito nakikita ng app mo: parehong field types, parehong snowflakes, parehong reply.

Ang tanging pagkakaibang mapapansin mo: kung hindi ma-reach ang box, empty na darating ang pickers at magre-render bilang “Hindi available sa ngayon”. Ibinabalik pag-save ang naka-store na values ng app mo, kaya walang nawawala. Huwag ituring na “kinlear ng operator” ang empty na picker.

7. Ano ang vina-validate ng GameVox, at ano ang hindi

Chine-check ng GameVox ang shape ng isang save, at sadyang hindi ang kahulugan nito. Hindi nito itinatabi ang form na ni-render nito (ang hindi pag-store ng kahit anong configuration mo ang buong punto), kaya pagdating ng save, walang nakakaalam sa side namin kung aling key ang channel picker at alin ang free-text box.

Ginagarantiya ng GameVoxKailangan mo pa ring i-check
Tugma ang keys sa key charset at hindi __proto__, constructor o prototype Na ang key ay talagang ipinadala mo
Ang values ay null, boolean, number, string, o array ng strings: hindi kailanman nested object Na tugma ang type sa field na dineklara mo
May hangganan ang strings (2,000 characters) at hanggang 25 entries lang ang arrays Ang sarili mong mas mahigpit na limits
Bawat picker id ay ibinigay ng GameVox mula sa catalog ng server na ito Na nare-resolve pa rin ang id sa guild na ito: snapshot lang ang catalogs, at puwedeng ma-delete ang channel sa pagitan ng render at save
Ang operator ay server owner o may Pamahalaan ang Server, at naka-install ang app dito Anumang sarili mong authorisation (plan tier, naka-link na account)

Hindi ito butas na dala ng panel: kaya na ng operator na mag-POST ng kahit ano sa sariling dashboard ng isang app. Ito ang kontrata, at ito mismo ang dahilan kung bakit ang picker types ay nagbibigay sa iyo ng ids na mini-mint ng GameVox sa halip na hayaan ang app na magdesisyon kung ano ang ibig sabihin ng isang id.

8. Mga limit

Dalawang behavior dito, at mahalaga ang pagkakaiba: nire-reject ng counts at structure ang buong reply, kaya malalaman mo; tahimik namang pinuputol ang text ceilings, kaya hindi kailanman makakakita ang operator ng sirang layout.

LimitValueKapag lumampas
Sections kada reply 12 Nire-reject
Fields kada reply (kabuuan, hindi kada section) 60 Nire-reject
Options kada select / multiselect 1–25 Nire-reject
Mga naka-select na entry sa multi-value 25 Nire-reject
Field / section key 64 chars Nire-reject
Option value 64 chars Nire-reject
Reply body 256 KB 413
Label, placeholder 100 chars Pinuputol
Help text 200 chars Pinuputol
Section description 300 chars Pinuputol
message 200 chars Pinuputol
error 300 chars Pinuputol
Value ng field na string-family 1,000 chars Pinuputol
Value ng text / static field 2,000 chars Pinuputol
Channels o roles sa isang catalog 500 Tina-truncate

Ire-report sa operator ang rejection bilang “Nagpadala ang app na ito ng settings na hindi mabasa ng GameVox: <dahilan>”, kasama ang ceiling na naabot mo at ang field na umabot dito. Ganoon din nire-reject ang duplicate keys, hindi kilalang type, select na walang options, at pagpapadala ng parehong sections at fields.

Tahimik na inaayos sa halip na i-reject

  • Nagiging empty ang kasalukuyang value na hindi tugma sa type nito. Dapat isang field lang ang mablangko ng stale na value, hindi ang buong panel ang bumagsak.
  • Dinodrop ang options sa type na hindi gumagamit nito.
  • Dinodrop ang min / max / step sa non-numeric na field.
  • Dinodrop ang hindi kilalang channel_kinds entries.
  • Dinodrop ang show_if na tumutukoy sa hindi kilalang key, sa sarili nito, o sa multi-value field, at palaging lalabas ang field.

9. Timing at failure modes

BehaviorValueAno ang nangyayari
Reply window 12s Sasabihan ang operator ng “Hindi tumugon ang app na ito. Maaaring hindi nito sinusuportahan ang mga in-app na setting.”
Nonce lifetime 15s Dinodrop ang late na reply sa halip na i-deliver sa walang tatanggap. Nagbabalik ng 404 ang pagsagot gamit ang expired na nonce.
Cooldown 2s kada action, kada app, kada server “Bigyan muna ng sandali ang app, tapos subukan ulit.” Magkahiwalay ang windows ng describe at save, kaya hindi nabablock ng pagbukas ng panel ang agarang save.
Offline ang app - Tinatanggihan agad: “Offline ang app na ito, kaya hindi ito ma-configure sa ngayon.” Walang dispatch na susubukan.
Maling app ang sumagot - 403. Naka-bind ang nonce sa application kung saan ito ibinigay.
Duplicate na reply - Ang unang sagot ang panalo; no-op ang pangalawa.

Bawat request ay nagiging gateway dispatch sa app mo, kaya nandiyan ang cooldown para hindi magamit ng operator ang GameVox para i-spam ang bot ng ibang tao. I-budget nang tama ang describe handler mo: tumatakbo ito pagbukas ng page, at inaasahan ang database read, pero labindalawang segundo ang hangganan.

10. Kumpletong halimbawa (discord.js)

Drop-in na ito, maliban sa sarili mong storage layer. Ito rin ang shape na gamit ng reference implementation.

const SCHEMA_V2 = 2;
const POSTABLE = ["text", "news", "forum", "fileshare"];

client.on("raw", (packet) => {
  if (!packet || packet.t !== "APP_SETTINGS_REQUEST") return;
  void handleSettings(packet.d ?? {});
});

async function reply(nonce, guildId, body) {
  await client.rest.post("/applications/@me/settings-response", {
    body: { nonce, ...body },
    query: new URLSearchParams({ server_id: guildId }),
  });
}

async function handleSettings(req) {
  const nonce = typeof req.nonce === "string" ? req.nonce : "";
  const guildId = typeof req.server_id === "string" ? req.server_id : "";
  if (!nonce || !guildId) return;

  try {
    if (req.action === "save") {
      const message = await applySettings(guildId, req.values ?? {});
      return await reply(nonce, guildId, { message });
    }

    const version = typeof req.schema_version === "number" ? req.schema_version : 1;
    if (version >= SCHEMA_V2) {
      return await reply(nonce, guildId, {
        version: SCHEMA_V2,
        sections: await describe(guildId),
      });
    }
    return await reply(nonce, guildId, { fields: await describeLegacy(guildId) });
  } catch (err) {
    // Sumagot kahit may failure: ang tahimik na drop ay mukhang sirang app.
    await reply(nonce, guildId, {
      error: "Hindi mabasa ng bot ang settings nito para sa server na ito.",
    }).catch(() => undefined);
  }
}

async function describe(guildId) {
  const cfg = await store.get(guildId);
  return [
    {
      key: "general",
      label: "Pangkalahatan",
      description: "Paano kumikilos ang bot sa buong server na ito.",
      fields: [
        { key: "prefix", label: "Command prefix", type: "string",
          value: cfg.prefix, max_length: 4 },
        { key: "ignored", label: "Mga ignored na channel", type: "channels",
          value: cfg.ignored, channel_kinds: POSTABLE,
          help: "Hindi sinasagot ang commands sa mga channel na ito." },
      ],
    },
    {
      key: "welcome",
      label: "Mga welcome message",
      description: "Pino-post kapag may sumali.",
      fields: [
        { key: "welcome.enabled", label: "Magpadala ng welcome message",
          type: "boolean", value: cfg.welcome.enabled === true },
        { key: "welcome.channel", label: "Channel", type: "channel",
          value: cfg.welcome.channel ?? null, channel_kinds: POSTABLE,
          show_if: { key: "welcome.enabled", equals: true } },
        { key: "welcome.message", label: "Mensahe", type: "text",
          value: cfg.welcome.message ?? "", max_length: 1800,
          placeholder: "Welcome {user} sa {server}!",
          show_if: { key: "welcome.enabled", equals: true } },
      ],
    },
  ];
}

async function applySettings(guildId, v) {
  const guild = client.guilds.cache.get(guildId);
  if (!guild) throw new Error("not in guild");

  const channel = (id) =>
    typeof id === "string" ? (guild.channels.cache.has(id) ? id : null) : null;

  await store.set(guildId, {
    prefix: String(v.prefix ?? "!").slice(0, 4) || "!",
    ignored: Array.isArray(v.ignored) ? v.ignored.filter(channel).slice(0, 25) : [],
    welcome: {
      enabled: v["welcome.enabled"] === true,
      channel: channel(v["welcome.channel"]),
      message: String(v["welcome.message"] ?? "").slice(0, 1800),
    },
  });

  return "Na-save ang settings.";
}

11. Checklist bago mag-ship

  • Stable ang bawat field key. Kapag ni-rename ang isa, mao-orphan ang anumang na-configure na ng operator.
  • Bawat id mula sa picker ay chine-check ulit laban sa guild bago isulat.
  • Ang empty na secret ay itinuturing na walang binago, hindi kailanman na-clear.
  • Sumasagot ang describe path nang malayong mas mabilis sa 12 segundo, kasama ang database read nito.
  • Sumasagot pa rin ang bawat failure path: gamit ang error, hindi ang katahimikan.
  • Ine-enforce ulit pag-save ang required at min/max.
  • Ang empty na picker ay nangangahulugang hindi available ang catalog, hindi na kinlear ng operator ang field.
  • No-op ang handler sa Discord, kaya iisang build ang tatakbo sa dalawa.

Sino ang puwedeng magbukas ng panel

Ang server owner, o member na may Pamahalaan ang Server: parehong gate na namamahala sa pag-install at pag-uninstall ng apps. Sa self-hosted server, sa sariling box ng customer ginagawa ang check, kaya hindi tatanggihan ang admin doon dahil lang walang cloud permission row.

Tahasang tinatanggihan ng GameVox ang request kung hindi naka-install ang app mo sa server na iyon, kaya ang panel request ay makakarating lang sa app na na-authorize na ng operator.

← Paglipat mula sa Discord  ·  Bumalik sa docs