You've already forked NitroSniper
mirror of
https://github.com/neoarz/NitroSniper.git
synced 2026-05-11 13:15:37 +02:00
feat: harden webhook error handlin and escape sender labels
This commit is contained in:
28
index.tsx
28
index.tsx
@@ -14,7 +14,7 @@ import { findByPropsLazy } from "@webpack";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import type { ClaimRequest, FinderProfile, WebhookResult } from "./types";
|
||||
import type { ClaimRequest, WebhookResult } from "./types";
|
||||
import { sendClaimWebhook } from "./webhook";
|
||||
|
||||
const GIFT_LINK_REGEX = /(?:discord\.gift\/|discord\.com\/gifts?\/)([a-zA-Z0-9]{16,24})/;
|
||||
@@ -32,6 +32,10 @@ function resetState() {
|
||||
claiming = false;
|
||||
}
|
||||
|
||||
function toError(error: unknown) {
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
|
||||
function isOwnMessage(message: Message) {
|
||||
return message.author?.id === UserStore.getCurrentUser()?.id;
|
||||
}
|
||||
@@ -52,32 +56,26 @@ function createClaimRequest(message: Message): ClaimRequest | null {
|
||||
const code = message.content ? extractGiftCode(message.content) : null;
|
||||
if (!code) return null;
|
||||
|
||||
const authorId = message.author?.id;
|
||||
const authorAvatar = message.author?.avatar;
|
||||
|
||||
return {
|
||||
code,
|
||||
authorId: message.author?.id,
|
||||
authorId,
|
||||
authorName: message.author?.globalName ?? message.author?.username,
|
||||
authorUsername: message.author?.username,
|
||||
authorAvatarUrl: authorId && authorAvatar
|
||||
? `https://cdn.discordapp.com/avatars/${authorId}/${authorAvatar}.png?size=128`
|
||||
: undefined,
|
||||
channelId: message.channel_id,
|
||||
guildId: message.guild_id,
|
||||
messageId: message.id
|
||||
};
|
||||
}
|
||||
|
||||
function getFinderProfile(): FinderProfile {
|
||||
const currentUser = UserStore.getCurrentUser();
|
||||
|
||||
return {
|
||||
name: currentUser?.globalName ?? currentUser?.username ?? "NitroSniper",
|
||||
iconUrl: currentUser?.avatar
|
||||
? `https://cdn.discordapp.com/avatars/${currentUser.id}/${currentUser.avatar}.png?size=128`
|
||||
: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function notifyClaim(result: WebhookResult, request: ClaimRequest) {
|
||||
void sendClaimWebhook(
|
||||
settings.store.webhookUrl,
|
||||
getFinderProfile(),
|
||||
result,
|
||||
request
|
||||
).catch(webhookError => {
|
||||
@@ -112,7 +110,7 @@ function processQueue() {
|
||||
GiftActions.redeemGiftCode({
|
||||
code: request.code,
|
||||
onRedeemed: () => handleClaimSuccess(request),
|
||||
onError: (error: Error) => handleClaimFailure(request, error)
|
||||
onError: (error: unknown) => handleClaimFailure(request, toError(error))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export async function sendWebhook(_: IpcMainInvokeEvent, webhookUrl: string, pay
|
||||
} catch (error) {
|
||||
return {
|
||||
status: -1,
|
||||
data: String(error)
|
||||
data: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
10
settings.tsx
10
settings.tsx
@@ -4,6 +4,12 @@ import { Button, showToast, Toasts } from "@webpack/common";
|
||||
|
||||
import { sendTestWebhook } from "./webhook";
|
||||
|
||||
function getToastErrorMessage(error: unknown) {
|
||||
return error instanceof Error
|
||||
? error.message
|
||||
: "Failed to send test webhook.";
|
||||
}
|
||||
|
||||
function TestWebhookButton() {
|
||||
const { webhookUrl } = settings.use(["webhookUrl"]);
|
||||
const disabled = webhookUrl.trim().length === 0;
|
||||
@@ -16,8 +22,8 @@ function TestWebhookButton() {
|
||||
.then(() => {
|
||||
showToast("Test webhook sent successfully.", Toasts.Type.SUCCESS);
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
showToast(error.message, Toasts.Type.FAILURE);
|
||||
.catch((error: unknown) => {
|
||||
showToast(getToastErrorMessage(error), Toasts.Type.FAILURE);
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
6
types.ts
6
types.ts
@@ -3,16 +3,12 @@ export interface ClaimRequest {
|
||||
authorId?: string;
|
||||
authorName?: string;
|
||||
authorUsername?: string;
|
||||
authorAvatarUrl?: string;
|
||||
channelId?: string;
|
||||
guildId?: string;
|
||||
messageId?: string;
|
||||
}
|
||||
|
||||
export interface FinderProfile {
|
||||
name: string;
|
||||
iconUrl?: string;
|
||||
}
|
||||
|
||||
export type WebhookResult = "claimed" | "failed";
|
||||
|
||||
export interface WebhookField {
|
||||
|
||||
31
webhook.ts
31
webhook.ts
@@ -2,7 +2,6 @@ import type { PluginNative } from "@utils/types";
|
||||
|
||||
import type {
|
||||
ClaimRequest,
|
||||
FinderProfile,
|
||||
WebhookEmbed,
|
||||
WebhookField,
|
||||
WebhookPayload,
|
||||
@@ -54,6 +53,10 @@ function buildMessageUrl(request: ClaimRequest) {
|
||||
return `https://discordapp.com/channels/${request.guildId ?? "@me"}/${request.channelId}/${request.messageId}`;
|
||||
}
|
||||
|
||||
function escapeMarkdown(value: string) {
|
||||
return value.replace(/([\\`*_{}\[\]()#+\-.!|>~])/g, "\\$1");
|
||||
}
|
||||
|
||||
function buildAuthorField(request: ClaimRequest): WebhookField | null {
|
||||
const label = request.authorName ?? request.authorUsername ?? request.authorId;
|
||||
if (!label) return null;
|
||||
@@ -61,7 +64,7 @@ function buildAuthorField(request: ClaimRequest): WebhookField | null {
|
||||
const profileUrl = buildUserProfileUrl(request.authorId);
|
||||
return {
|
||||
name: "Code sent by:",
|
||||
value: profileUrl ? `[${label}](${profileUrl})` : label,
|
||||
value: profileUrl ? `[${escapeMarkdown(label)}](${profileUrl})` : escapeMarkdown(label),
|
||||
inline: false
|
||||
};
|
||||
}
|
||||
@@ -100,7 +103,17 @@ function getResultPresentation(result: WebhookResult) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildClaimEmbed(finder: FinderProfile, result: WebhookResult, request: ClaimRequest): WebhookEmbed {
|
||||
function buildEmbedAuthor(request: ClaimRequest) {
|
||||
const name = request.authorName ?? request.authorUsername;
|
||||
if (!name) return undefined;
|
||||
|
||||
return {
|
||||
name,
|
||||
icon_url: request.authorAvatarUrl
|
||||
};
|
||||
}
|
||||
|
||||
function buildClaimEmbed(result: WebhookResult, request: ClaimRequest): WebhookEmbed {
|
||||
const presentation = getResultPresentation(result);
|
||||
|
||||
return {
|
||||
@@ -108,10 +121,7 @@ function buildClaimEmbed(finder: FinderProfile, result: WebhookResult, request:
|
||||
color: presentation.color,
|
||||
fields: buildClaimFields(request),
|
||||
timestamp: new Date().toISOString(),
|
||||
author: {
|
||||
name: finder.name,
|
||||
icon_url: finder.iconUrl
|
||||
},
|
||||
author: buildEmbedAuthor(request),
|
||||
footer: {
|
||||
text: WEBHOOK_NAME
|
||||
}
|
||||
@@ -132,9 +142,9 @@ function buildTestWebhookPayload(): WebhookPayload {
|
||||
]);
|
||||
}
|
||||
|
||||
function buildClaimWebhookPayload(finder: FinderProfile, result: WebhookResult, request: ClaimRequest): WebhookPayload {
|
||||
function buildClaimWebhookPayload(result: WebhookResult, request: ClaimRequest): WebhookPayload {
|
||||
return createPayload([
|
||||
buildClaimEmbed(finder, result, request)
|
||||
buildClaimEmbed(result, request)
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -170,14 +180,13 @@ async function postWebhook(url: URL, payload: WebhookPayload) {
|
||||
|
||||
export async function sendClaimWebhook(
|
||||
webhookUrl: string,
|
||||
finder: FinderProfile,
|
||||
result: WebhookResult,
|
||||
request: ClaimRequest
|
||||
) {
|
||||
const url = parseWebhookUrl(webhookUrl);
|
||||
if (!url) return;
|
||||
|
||||
await postWebhook(url, buildClaimWebhookPayload(finder, result, request));
|
||||
await postWebhook(url, buildClaimWebhookPayload(result, request));
|
||||
}
|
||||
|
||||
export async function sendTestWebhook(webhookUrl: string) {
|
||||
|
||||
Reference in New Issue
Block a user