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 { UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
import type { ClaimRequest, FinderProfile, WebhookResult } from "./types";
|
import type { ClaimRequest, WebhookResult } from "./types";
|
||||||
import { sendClaimWebhook } from "./webhook";
|
import { sendClaimWebhook } from "./webhook";
|
||||||
|
|
||||||
const GIFT_LINK_REGEX = /(?:discord\.gift\/|discord\.com\/gifts?\/)([a-zA-Z0-9]{16,24})/;
|
const GIFT_LINK_REGEX = /(?:discord\.gift\/|discord\.com\/gifts?\/)([a-zA-Z0-9]{16,24})/;
|
||||||
@@ -32,6 +32,10 @@ function resetState() {
|
|||||||
claiming = false;
|
claiming = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toError(error: unknown) {
|
||||||
|
return error instanceof Error ? error : new Error(String(error));
|
||||||
|
}
|
||||||
|
|
||||||
function isOwnMessage(message: Message) {
|
function isOwnMessage(message: Message) {
|
||||||
return message.author?.id === UserStore.getCurrentUser()?.id;
|
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;
|
const code = message.content ? extractGiftCode(message.content) : null;
|
||||||
if (!code) return null;
|
if (!code) return null;
|
||||||
|
|
||||||
|
const authorId = message.author?.id;
|
||||||
|
const authorAvatar = message.author?.avatar;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code,
|
code,
|
||||||
authorId: message.author?.id,
|
authorId,
|
||||||
authorName: message.author?.globalName ?? message.author?.username,
|
authorName: message.author?.globalName ?? message.author?.username,
|
||||||
authorUsername: 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,
|
channelId: message.channel_id,
|
||||||
guildId: message.guild_id,
|
guildId: message.guild_id,
|
||||||
messageId: message.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) {
|
function notifyClaim(result: WebhookResult, request: ClaimRequest) {
|
||||||
void sendClaimWebhook(
|
void sendClaimWebhook(
|
||||||
settings.store.webhookUrl,
|
settings.store.webhookUrl,
|
||||||
getFinderProfile(),
|
|
||||||
result,
|
result,
|
||||||
request
|
request
|
||||||
).catch(webhookError => {
|
).catch(webhookError => {
|
||||||
@@ -112,7 +110,7 @@ function processQueue() {
|
|||||||
GiftActions.redeemGiftCode({
|
GiftActions.redeemGiftCode({
|
||||||
code: request.code,
|
code: request.code,
|
||||||
onRedeemed: () => handleClaimSuccess(request),
|
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) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
status: -1,
|
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";
|
import { sendTestWebhook } from "./webhook";
|
||||||
|
|
||||||
|
function getToastErrorMessage(error: unknown) {
|
||||||
|
return error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Failed to send test webhook.";
|
||||||
|
}
|
||||||
|
|
||||||
function TestWebhookButton() {
|
function TestWebhookButton() {
|
||||||
const { webhookUrl } = settings.use(["webhookUrl"]);
|
const { webhookUrl } = settings.use(["webhookUrl"]);
|
||||||
const disabled = webhookUrl.trim().length === 0;
|
const disabled = webhookUrl.trim().length === 0;
|
||||||
@@ -16,8 +22,8 @@ function TestWebhookButton() {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
showToast("Test webhook sent successfully.", Toasts.Type.SUCCESS);
|
showToast("Test webhook sent successfully.", Toasts.Type.SUCCESS);
|
||||||
})
|
})
|
||||||
.catch((error: Error) => {
|
.catch((error: unknown) => {
|
||||||
showToast(error.message, Toasts.Type.FAILURE);
|
showToast(getToastErrorMessage(error), Toasts.Type.FAILURE);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
6
types.ts
6
types.ts
@@ -3,16 +3,12 @@ export interface ClaimRequest {
|
|||||||
authorId?: string;
|
authorId?: string;
|
||||||
authorName?: string;
|
authorName?: string;
|
||||||
authorUsername?: string;
|
authorUsername?: string;
|
||||||
|
authorAvatarUrl?: string;
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
guildId?: string;
|
guildId?: string;
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FinderProfile {
|
|
||||||
name: string;
|
|
||||||
iconUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WebhookResult = "claimed" | "failed";
|
export type WebhookResult = "claimed" | "failed";
|
||||||
|
|
||||||
export interface WebhookField {
|
export interface WebhookField {
|
||||||
|
|||||||
31
webhook.ts
31
webhook.ts
@@ -2,7 +2,6 @@ import type { PluginNative } from "@utils/types";
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ClaimRequest,
|
ClaimRequest,
|
||||||
FinderProfile,
|
|
||||||
WebhookEmbed,
|
WebhookEmbed,
|
||||||
WebhookField,
|
WebhookField,
|
||||||
WebhookPayload,
|
WebhookPayload,
|
||||||
@@ -54,6 +53,10 @@ function buildMessageUrl(request: ClaimRequest) {
|
|||||||
return `https://discordapp.com/channels/${request.guildId ?? "@me"}/${request.channelId}/${request.messageId}`;
|
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 {
|
function buildAuthorField(request: ClaimRequest): WebhookField | null {
|
||||||
const label = request.authorName ?? request.authorUsername ?? request.authorId;
|
const label = request.authorName ?? request.authorUsername ?? request.authorId;
|
||||||
if (!label) return null;
|
if (!label) return null;
|
||||||
@@ -61,7 +64,7 @@ function buildAuthorField(request: ClaimRequest): WebhookField | null {
|
|||||||
const profileUrl = buildUserProfileUrl(request.authorId);
|
const profileUrl = buildUserProfileUrl(request.authorId);
|
||||||
return {
|
return {
|
||||||
name: "Code sent by:",
|
name: "Code sent by:",
|
||||||
value: profileUrl ? `[${label}](${profileUrl})` : label,
|
value: profileUrl ? `[${escapeMarkdown(label)}](${profileUrl})` : escapeMarkdown(label),
|
||||||
inline: false
|
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);
|
const presentation = getResultPresentation(result);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -108,10 +121,7 @@ function buildClaimEmbed(finder: FinderProfile, result: WebhookResult, request:
|
|||||||
color: presentation.color,
|
color: presentation.color,
|
||||||
fields: buildClaimFields(request),
|
fields: buildClaimFields(request),
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
author: {
|
author: buildEmbedAuthor(request),
|
||||||
name: finder.name,
|
|
||||||
icon_url: finder.iconUrl
|
|
||||||
},
|
|
||||||
footer: {
|
footer: {
|
||||||
text: WEBHOOK_NAME
|
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([
|
return createPayload([
|
||||||
buildClaimEmbed(finder, result, request)
|
buildClaimEmbed(result, request)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,14 +180,13 @@ async function postWebhook(url: URL, payload: WebhookPayload) {
|
|||||||
|
|
||||||
export async function sendClaimWebhook(
|
export async function sendClaimWebhook(
|
||||||
webhookUrl: string,
|
webhookUrl: string,
|
||||||
finder: FinderProfile,
|
|
||||||
result: WebhookResult,
|
result: WebhookResult,
|
||||||
request: ClaimRequest
|
request: ClaimRequest
|
||||||
) {
|
) {
|
||||||
const url = parseWebhookUrl(webhookUrl);
|
const url = parseWebhookUrl(webhookUrl);
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
await postWebhook(url, buildClaimWebhookPayload(finder, result, request));
|
await postWebhook(url, buildClaimWebhookPayload(result, request));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendTestWebhook(webhookUrl: string) {
|
export async function sendTestWebhook(webhookUrl: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user