From 27f7aff29858252d78aa45cfcd0c279e6b829847 Mon Sep 17 00:00:00 2001 From: DEVKaxtusik <123556490+DEVKaxtusik@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:57:54 +0200 Subject: [PATCH] feat: add auto widget creation functionality and setup script --- src/index.ts | 2 +- src/providers/discord.ts | 301 ++++++++++++++++++++++++++++++++++++++- src/setup.ts | 9 ++ 3 files changed, 307 insertions(+), 5 deletions(-) create mode 100644 src/setup.ts diff --git a/src/index.ts b/src/index.ts index 82d387d..87ee6f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import dotenv from 'dotenv' dotenv.config(); -import { patchIdentity } from "./providers/discord"; +import { createWidget, patchIdentity } from "./providers/discord"; import { fetchOsuProfile } from "./providers/osu"; import { toIdentity } from "./types/osuProfile"; diff --git a/src/providers/discord.ts b/src/providers/discord.ts index ac92f9b..13a8c81 100644 --- a/src/providers/discord.ts +++ b/src/providers/discord.ts @@ -1,20 +1,313 @@ import axios from "axios"; +const widgetData = { + display_name: "osu! Stats", + surfaces: { + widget_bottom: { + layout: "widget_bottom_stats", + components: { + stat_5: { + fields: { + value: { + value_type: "data", + presentation_type: "text", + value: "osu_top_play", + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Top Play pp", + }, + }, + }, + stat_6: { + fields: { + value: { + value_type: "data", + presentation_type: "text", + value: "osu_playtime", + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Playtime", + }, + }, + }, + stat_3: { + fields: { + value: { + value_type: "data", + presentation_type: "text", + value: "osu_country_placement", + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Country Placement", + }, + }, + }, + stat_2: { + fields: { + value: { + value_type: "data", + presentation_type: "text", + value: "osu_global_placement", + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Global Placement", + }, + }, + }, + stat_4: { + fields: { + value: { + value_type: "data", + presentation_type: "text", + value: "osu_overall_pp", + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Overall pp", + }, + }, + }, + stat_1: { + fields: { + value: { + value_type: "data", + presentation_type: "text", + value: "osu_country", + }, + icon: { + value_type: "data", + presentation_type: "image", + value: "osu_flag", + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Country", + }, + }, + }, + }, + }, + add_widget_preview: { + layout: "add_widget_preview_contained", + components: { + contained_image: { + fields: { + image: { + value_type: "data", + presentation_type: "image", + value: "osu_profile", + }, + }, + }, + }, + }, + widget_top: { + layout: "widget_top_contained", + components: { + subtitle_2: { + fields: { + text: { + value_type: "data", + presentation_type: "text", + value: "osu_is_supporter", + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Supporter", + }, + }, + }, + title: { + fields: { + text: { + value_type: "data", + presentation_type: "text", + value: "osu_name", + }, + }, + }, + subtitle_1: { + fields: { + text: { + presentation_type: "text", + value_type: "data", + value: "osu_fav_map", + fallback: { + value_type: "custom_string", + presentation_type: "text", + value: "None", + }, + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Favorite Map", + }, + }, + }, + subtitle_3: { + fields: { + text: { + value_type: "data", + presentation_type: "number", + value: "osu_playcount", + }, + label: { + value_type: "custom_string", + presentation_type: "text", + value: "Playcount", + }, + }, + }, + contained_image: { + fields: { + image: { + value_type: "data", + presentation_type: "image", + value: "osu_profile", + }, + }, + }, + }, + }, + mini_profile: { + layout: "mini_profile_contained_stat", + components: { + stat: { + fields: { + text: { + value_type: "data", + presentation_type: "text", + value: "osu_name", + }, + label: { + value_type: "data", + presentation_type: "text", + value: "osu_global_placement", + }, + }, + }, + contained_image: { + fields: { + image: { + value_type: "data", + presentation_type: "image", + value: "osu_profile", + }, + }, + }, + }, + }, + activity_accessory: { + layout: "activity_accessory_stat", + components: { + stat: { + fields: { + text: { + value_type: "custom_string", + presentation_type: "text", + value: "Check out my stats", + }, + }, + }, + }, + }, + }, +}; + const discordApplicationId = process.env.APP_ID!; const discordAPI = axios.create({ baseURL: "https://discord.com/api/v10", headers: { - "Authorization": "Bot " + process.env.DISCORD_TOKEN! - } + Authorization: "Bot " + process.env.DISCORD_TOKEN!, + }, }); +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); + } +} + +async function isExist(): Promise { + 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 publishWidget(configId: string): Promise { + try { + await discordAPI.post( + `/applications/${discordApplicationId}/widget-configs/${configId}/publish` + ); + } catch (error: any) { + console.error(`ERROR Publishing widget config ${configId}:`, error.message); + if (error.response) { + console.error(`ERROR Details:`, JSON.stringify(error.response.data)); + } + throw error; + } +} + export async function patchIdentity(identity: any, userID: string) { try { - await discordAPI.patch(`/applications/${discordApplicationId}/users/${userID}/identities/0/profile`, identity); + await discordAPI.patch( + `/applications/${discordApplicationId}/users/${userID}/identities/0/profile`, + identity, + ); } catch (error: any) { console.error(`ERROR Discord API Patch failed: ${error.message}`); if (error.response) { - console.error(`ERROR Status: ${error.response.status} | Data:`, JSON.stringify(error.response.data)); + console.error( + `ERROR Status: ${error.response.status} | Data:`, + JSON.stringify(error.response.data), + ); } throw error; } diff --git a/src/setup.ts b/src/setup.ts new file mode 100644 index 0000000..d8b6fb9 --- /dev/null +++ b/src/setup.ts @@ -0,0 +1,9 @@ +import dotenv from 'dotenv'; +dotenv.config(); +import { createWidget } from "./providers/discord"; + +async function setup() { + await createWidget(); +} + +setup(); \ No newline at end of file