Add auto widget creation functionality - #2
Conversation
📝 WalkthroughWalkthroughThis PR adds Discord widget creation/publishing support to ChangesDiscord widget lifecycle
Estimated code review effort: 2 (Simple) | ~12 minutes Sequence Diagram(s)sequenceDiagram
participant Setup
participant createWidget
participant isExist
participant DiscordAPI
participant publishWidget
Setup->>createWidget: call createWidget()
createWidget->>isExist: check existing config
isExist->>DiscordAPI: GET widget configs
DiscordAPI-->>isExist: config list or empty
isExist-->>createWidget: configId or null
alt config exists
createWidget->>DiscordAPI: PATCH widget config
else no config
createWidget->>DiscordAPI: POST widget config
end
createWidget->>publishWidget: publish(configId)
publishWidget->>DiscordAPI: POST publish endpoint
DiscordAPI-->>publishWidget: response or error
Related Issues: None found Related PRs: None found Suggested labels: enhancement Suggested reviewers: DEVKaxtusik 🐰 A widget hops through PATCH and POST, 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/providers/discord.ts (1)
3-227: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider extracting
widgetDatainto a config/JSON file.224 lines of static payload embedded in the module hurts readability of the surrounding logic. Not blocking.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/providers/discord.ts` around lines 3 - 227, The `widgetData` object is a large static payload embedded directly in `src/providers/discord.ts`, making the module harder to read and maintain. Extract `widgetData` into a separate config/JSON file and import it back into the provider, keeping only the logic in the `discord` provider module. Use the `widgetData` symbol as the main target when moving the data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/providers/discord.ts`:
- Around line 230-235: The discordAPI axios client is missing a request timeout,
so calls can hang indefinitely; add a timeout to the axios.create configuration
in discordAPI so the client fails fast instead of blocking completion. Update
the existing client setup near discordAPI to include an appropriate timeout
value alongside baseURL and headers, keeping the change localized to the
discordAPI initializer.
- Around line 237-261: The createWidget() flow in src/providers/discord.ts
swallows setup failures by only logging inside the catch block, so the caller
never sees the error and the standalone setup script can still exit
successfully. Update createWidget() to propagate failures after logging by
rethrowing the caught error (or otherwise terminating with a non-zero exit) so
setup.ts can fail the process on bad token, network, or API errors; use
createWidget(), publishWidget(), and the existing discordAPI calls as the key
points to keep behavior intact while making failures visible.
- Around line 263-282: The isExist() helper currently returns null for both “no
widget config exists” and actual fetch failures, so createWidget() can’t tell
whether to create or update. Update isExist() to distinguish these cases by
logging all caught errors (including non-response/network failures) and either
rethrowing or returning a separate failure signal, while preserving null only
for the true “no configs found” path. Then adjust createWidget() to handle that
distinct failure path instead of treating it as an empty result.
- Line 253: The widget config ID handling in the Discord provider is using the
wrong response field, which can leave widgetConfigId unset and break
lookups/publishing. Update the logic in the create/fetch flow around
widgetConfigId, createResponse.data, configs[0], isExist(), and publishWidget()
to read and compare config_id instead of id wherever the widget-config API
response shape is consumed.
---
Nitpick comments:
In `@src/providers/discord.ts`:
- Around line 3-227: The `widgetData` object is a large static payload embedded
directly in `src/providers/discord.ts`, making the module harder to read and
maintain. Extract `widgetData` into a separate config/JSON file and import it
back into the provider, keeping only the logic in the `discord` provider module.
Use the `widgetData` symbol as the main target when moving the data.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 73a00556-67fd-48b3-956b-0bb426d7e2b1
📒 Files selected for processing (3)
src/index.tssrc/providers/discord.tssrc/setup.ts
| const discordAPI = axios.create({ | ||
| baseURL: "https://discord.com/api/v10", | ||
| headers: { | ||
| "Authorization": "Bot " + process.env.DISCORD_TOKEN! | ||
| } | ||
| Authorization: "Bot " + process.env.DISCORD_TOKEN!, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
No timeout configured on discordAPI client.
A stalled network call here will hang indefinitely since no timeout is set, which is risky for a script expected to run to completion (e.g. in CI/deploy pipelines).
🕒 Suggested fix
const discordAPI = axios.create({
baseURL: "https://discord.com/api/v10",
+ timeout: 10000,
headers: {
Authorization: "Bot " + process.env.DISCORD_TOKEN!,
},
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const discordAPI = axios.create({ | |
| baseURL: "https://discord.com/api/v10", | |
| headers: { | |
| "Authorization": "Bot " + process.env.DISCORD_TOKEN! | |
| } | |
| Authorization: "Bot " + process.env.DISCORD_TOKEN!, | |
| }, | |
| }); | |
| const discordAPI = axios.create({ | |
| baseURL: "https://discord.com/api/v10", | |
| timeout: 10000, | |
| headers: { | |
| Authorization: "Bot " + process.env.DISCORD_TOKEN!, | |
| }, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/providers/discord.ts` around lines 230 - 235, The discordAPI axios client
is missing a request timeout, so calls can hang indefinitely; add a timeout to
the axios.create configuration in discordAPI so the client fails fast instead of
blocking completion. Update the existing client setup near discordAPI to include
an appropriate timeout value alongside baseURL and headers, keeping the change
localized to the discordAPI initializer.
| export async function createWidget() { | ||
| let widgetConfigId = await isExist(); | ||
|
|
||
| try { | ||
| if (widgetConfigId) { | ||
| console.log(`Updating existing widget config: ${widgetConfigId}`); | ||
| await discordAPI.patch( | ||
| `/applications/${discordApplicationId}/widget-configs/${widgetConfigId}`, | ||
| widgetData, | ||
| ); | ||
| } else { | ||
| console.log("No existing config found. Creating a new one..."); | ||
| const createResponse = await discordAPI.post( | ||
| `/applications/${discordApplicationId}/widget-configs`, | ||
| widgetData, | ||
| ); | ||
| widgetConfigId = createResponse.data.id; | ||
| } | ||
|
|
||
| await publishWidget(widgetConfigId!); | ||
| console.log(`Successfully published widget configuration: ${widgetConfigId}`); | ||
| } catch (error: any) { | ||
| console.error("Error setting up/publishing widget: ", error?.response?.data || error.message); | ||
| } | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
createWidget() swallows all failures without signaling failure to the caller.
The catch block only logs and never rethrows or exits non-zero. Since this is invoked from setup.ts as a standalone setup script with no additional error handling, a total failure (bad token, network error, API rejection) will still make the process exit successfully (code 0), silently hiding the failure from any CI/deploy pipeline that checks the exit code.
🚨 Suggested fix
} catch (error: any) {
console.error("Error setting up/publishing widget: ", error?.response?.data || error.message);
+ throw error;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function createWidget() { | |
| let widgetConfigId = await isExist(); | |
| try { | |
| if (widgetConfigId) { | |
| console.log(`Updating existing widget config: ${widgetConfigId}`); | |
| await discordAPI.patch( | |
| `/applications/${discordApplicationId}/widget-configs/${widgetConfigId}`, | |
| widgetData, | |
| ); | |
| } else { | |
| console.log("No existing config found. Creating a new one..."); | |
| const createResponse = await discordAPI.post( | |
| `/applications/${discordApplicationId}/widget-configs`, | |
| widgetData, | |
| ); | |
| widgetConfigId = createResponse.data.id; | |
| } | |
| await publishWidget(widgetConfigId!); | |
| console.log(`Successfully published widget configuration: ${widgetConfigId}`); | |
| } catch (error: any) { | |
| console.error("Error setting up/publishing widget: ", error?.response?.data || error.message); | |
| } | |
| } | |
| export async function createWidget() { | |
| let widgetConfigId = await isExist(); | |
| try { | |
| if (widgetConfigId) { | |
| console.log(`Updating existing widget config: ${widgetConfigId}`); | |
| await discordAPI.patch( | |
| `/applications/${discordApplicationId}/widget-configs/${widgetConfigId}`, | |
| widgetData, | |
| ); | |
| } else { | |
| console.log("No existing config found. Creating a new one..."); | |
| const createResponse = await discordAPI.post( | |
| `/applications/${discordApplicationId}/widget-configs`, | |
| widgetData, | |
| ); | |
| widgetConfigId = createResponse.data.id; | |
| } | |
| await publishWidget(widgetConfigId!); | |
| console.log(`Successfully published widget configuration: ${widgetConfigId}`); | |
| } catch (error: any) { | |
| console.error("Error setting up/publishing widget: ", error?.response?.data || error.message); | |
| throw error; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/providers/discord.ts` around lines 237 - 261, The createWidget() flow in
src/providers/discord.ts swallows setup failures by only logging inside the
catch block, so the caller never sees the error and the standalone setup script
can still exit successfully. Update createWidget() to propagate failures after
logging by rethrowing the caught error (or otherwise terminating with a non-zero
exit) so setup.ts can fail the process on bad token, network, or API errors; use
createWidget(), publishWidget(), and the existing discordAPI calls as the key
points to keep behavior intact while making failures visible.
| `/applications/${discordApplicationId}/widget-configs`, | ||
| widgetData, | ||
| ); | ||
| widgetConfigId = createResponse.data.id; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
What is the exact JSON response shape (field names) for Discord's POST /applications/{application.id}/widget-configs and GET /applications/{application.id}/widget-configs endpoints?
💡 Result:
The endpoints POST /applications/{application.id}/widget-configs and GET /applications/{application.id}/widget-configs relate to the management of application-specific widget configurations, a feature distinct from legacy Guild Widgets [1]. According to documentation for this resource, the widget configuration object uses the following primary fields [1]: - display_name (string): The display name of the widget configuration. - surfaces (map): A collection of widget surfaces, mapped by surface type [1]. When creating a new configuration via POST, the request body accepts the display_name and surfaces fields [1]. The response for both the creation (POST) and retrieval (GET) of these configurations returns the widget configuration object, which typically includes the above fields along with a unique identifier (often config_id) [1][2]. Note: These endpoints are distinct from Guild Widget settings, which involve fields like channel_id and enabled [3][4][5][6]. The application widget config feature is specifically designed for application-level integrations [1].
Citations:
- 1: https://docs.discord.food/resources/widgets
- 2: https://gist.github.com/aamiaa/7cdd590e3949cd654758bc90bcb4710b
- 3: https://docs.discord.com/developers/resources/guild
- 4: https://discord.js.org/docs/packages/core/main/APIGuildWidgetSettings:Interface
- 5: https://docs.disnake.dev/en/latest/api/widgets.html
- 6: https://discord.js.org/docs/packages/discord.js/main/GuildWidgetSettings:Interface
🏁 Script executed:
sed -n '220,290p' src/providers/discord.tsRepository: DEVKaxtusik/osu-widget
Length of output: 2109
🏁 Script executed:
rg -n "widget-configs|config_id|createResponse\.data\.id|configs\[0\]\.id" src README.md .github docsRepository: DEVKaxtusik/osu-widget
Length of output: 793
🌐 Web query:
Discord application widget-configs POST GET response schema config_id id widget-configs
💡 Result:
In the context of the Discord API, widget-configs refer to configurations for application widgets displayed on user profiles [1]. These are managed via endpoints associated with an application [1]. Widget Config Object Structure A widget config object typically contains the following fields [1]: - application_id (snowflake): The ID of the application [1]. - config_id (snowflake): The unique ID of the widget configuration [1]. - display_name (string): The user-defined name for the configuration [1]. - surfaces (map): A mapping of surface types (e.g., widget_top, widget_bottom) to their respective layouts and components [2][1]. - status (string): The current status of the configuration [1]. - resolved_assets (array): Optional resolved assets for the widget [1]. - published_at (ISO8601 timestamp): The time the configuration was published [1]. - updated_at (ISO8601 timestamp): The time the configuration was last updated [1]. API Endpoints - Create Application Widget Config: POST /applications/{application.id}/widget-configs [1]. This endpoint requires a payload specifying the display_name and surfaces [1]. - List Application Widget Configs: GET /applications/{application.id}/widget-configs [1]. - Get Developer Application Widget Configs: GET /widget-configs/developer [1]. This allows developers to retrieve configs for applications they have access to [1]. Note: These endpoints are generally considered internal or experimental and are not documented in the official public Discord Developer Portal [3][4][5]. They have been observed through community research and unofficial documentation [1][6]. Developers working with these should be aware that they are subject to change and may require specific experiments or flags to function [6].
Citations:
- 1: https://docs.discord.food/resources/widgets
- 2: https://gist.github.com/aamiaa/7cdd590e3949cd654758bc90bcb4710b
- 3: https://docs.discord.com/developers/resources/guild
- 4: https://docs.discord.com/developers/reference
- 5: https://docs.discord.com/developers/resources/application
- 6: https://chloecinders.com/blog/discord-widgets
🌐 Web query:
"POST /applications/{application.id}/widget-configs" "GET /applications/{application.id}/widget-configs" config_id response
💡 Result:
The API pattern POST /applications/{application.id}/widget-configs and GET /applications/{application.id}/widget-configs is commonly used to manage widget configurations for an application [1]. In this architectural pattern: 1. POST /applications/{application.id}/widget-configs: This endpoint is used to create a new widget configuration [1]. Upon successful creation, the API typically returns the newly created widget configuration object in the response body [1]. This response object contains a unique identifier, often referred to as config_id, which can be used for subsequent operations such as publishing or updating the configuration [1][2]. 2. GET /applications/{application.id}/widget-configs: This endpoint is used to retrieve a list of all widget configurations associated with the specified application [1]. The response typically returns an array of widget configuration objects, each containing its respective config_id [1]. While the specific naming conventions and fields (such as config_id versus id) can vary depending on the implementation and service provider, the pattern of returning the created configuration record—including its unique identifier—within the POST response is a standard RESTful practice for resource management [1][2][3]. Developers should refer to the specific API documentation for their service to confirm whether the field is explicitly named config_id or simply id [1][4][5].
Citations:
- 1: https://docs.discord.food/resources/widgets
- 2: https://gist.github.com/aamiaa/7cdd590e3949cd654758bc90bcb4710b
- 3: https://docs.cloud.google.com/gemini/enterprise/docs/reference/rest/v1alpha/projects.locations.collections.dataStores.widgetConfigs
- 4: https://docs.ecosuite.io/api/ecosuite-project-data-api/widgetconfigs
- 5: https://docs.cloud.google.com/generative-ai-app-builder/docs/reference/rest/v1alpha/projects.locations.collections.engines.widgetConfigs
Use config_id instead of id for widget config IDs.
createResponse.data.id and configs[0].id don’t match the widget-config response shape; if the API returns config_id, widgetConfigId stays empty, isExist() never finds an existing config, and publishWidget() can be called with undefined.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/providers/discord.ts` at line 253, The widget config ID handling in the
Discord provider is using the wrong response field, which can leave
widgetConfigId unset and break lookups/publishing. Update the logic in the
create/fetch flow around widgetConfigId, createResponse.data, configs[0],
isExist(), and publishWidget() to read and compare config_id instead of id
wherever the widget-config API response shape is consumed.
| async function isExist(): Promise<string | null> { | ||
| try { | ||
| const response = await discordAPI.get( | ||
| `/applications/${discordApplicationId}/widget-configs`, | ||
| ); | ||
|
|
||
| const configs = response.data; | ||
|
|
||
| if (configs && configs.length > 0) { | ||
| return configs[0].id; | ||
| } | ||
|
|
||
| return null; | ||
| } catch (error: any) { | ||
| if (error.response) { | ||
| console.error(`ERROR Fetching widget configs:`, JSON.stringify(error.response.data)); | ||
| } | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
isExist() conflates "no config exists" with "fetch failed".
Any error without a .response property (network error, timeout, DNS failure) is silently swallowed with no logging at all, and every error path returns null — the same value used to mean "no widget config exists yet". This makes createWidget() unable to distinguish a real absence of config from a transient failure, risking creation of a duplicate widget config (via POST) on every retry after a network blip, instead of updating the existing one.
🛠️ Suggested fix
} catch (error: any) {
- if (error.response) {
- console.error(`ERROR Fetching widget configs:`, JSON.stringify(error.response.data));
- }
- return null;
+ console.error(`ERROR Fetching widget configs:`, error.response ? JSON.stringify(error.response.data) : error.message);
+ throw error;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async function isExist(): Promise<string | null> { | |
| try { | |
| const response = await discordAPI.get( | |
| `/applications/${discordApplicationId}/widget-configs`, | |
| ); | |
| const configs = response.data; | |
| if (configs && configs.length > 0) { | |
| return configs[0].id; | |
| } | |
| return null; | |
| } catch (error: any) { | |
| if (error.response) { | |
| console.error(`ERROR Fetching widget configs:`, JSON.stringify(error.response.data)); | |
| } | |
| return null; | |
| } | |
| } | |
| async function isExist(): Promise<string | null> { | |
| try { | |
| const response = await discordAPI.get( | |
| `/applications/${discordApplicationId}/widget-configs`, | |
| ); | |
| const configs = response.data; | |
| if (configs && configs.length > 0) { | |
| return configs[0].id; | |
| } | |
| return null; | |
| } catch (error: any) { | |
| console.error( | |
| `ERROR Fetching widget configs:`, | |
| error.response ? JSON.stringify(error.response.data) : error.message, | |
| ); | |
| throw error; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/providers/discord.ts` around lines 263 - 282, The isExist() helper
currently returns null for both “no widget config exists” and actual fetch
failures, so createWidget() can’t tell whether to create or update. Update
isExist() to distinguish these cases by logging all caught errors (including
non-response/network failures) and either rethrowing or returning a separate
failure signal, while preserving null only for the true “no configs found” path.
Then adjust createWidget() to handle that distinct failure path instead of
treating it as an empty result.
Summary by CodeRabbit