Add Roll Web View

This commit is contained in:
Ean Milligan 2025-07-17 02:32:14 -04:00
parent 5e94056fc5
commit 1052e7d2e2
13 changed files with 514 additions and 170 deletions

View File

@ -32,6 +32,7 @@
"Mult",
"nojs",
"noodp",
"noopener",
"noydir",
"oldcnt",
"oper",

View File

@ -20,13 +20,15 @@
"singleQuote": true,
"proseWrap": "preserve"
},
"nodeModulesDir": "none",
"imports": {
"@discordeno": "https://deno.land/x/discordeno@12.0.1/mod.ts",
"@imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts",
"@Log4Deno": "https://raw.githubusercontent.com/Burn-E99/Log4Deno/V2.1.1/mod.ts",
"@mysql": "https://deno.land/x/mysql@v2.12.1/mod.ts",
"@std/http": "jsr:@std/http@1.0.15",
"@nanoid": "https://deno.land/x/nanoid@v3.0.0/mod.ts",
"@imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts",
"@showdown": "npm:showdown@2.1.0",
"@std/http": "jsr:@std/http@1.0.15",
"~config": "./config.ts",
"~flags": "./flags.ts",
"artigen/": "./src/artigen/",

View File

@ -10,7 +10,8 @@
"jsr:@std/media-types@^1.1.0": "1.1.0",
"jsr:@std/net@^1.0.4": "1.0.4",
"jsr:@std/path@^1.0.9": "1.0.9",
"jsr:@std/streams@^1.0.9": "1.0.9"
"jsr:@std/streams@^1.0.9": "1.0.9",
"npm:showdown@2.1.0": "2.1.0"
},
"jsr": {
"@std/cli@1.0.17": {
@ -54,6 +55,17 @@
"integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035"
}
},
"npm": {
"commander@9.5.0": {
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="
},
"showdown@2.1.0": {
"integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==",
"dependencies": [
"commander"
]
}
},
"redirects": {
"https://deno.land/std/hash/mod.ts": "https://deno.land/std@0.224.0/hash/mod.ts"
},
@ -837,7 +849,8 @@
},
"workspace": {
"dependencies": [
"jsr:@std/http@1.0.15"
"jsr:@std/http@1.0.15",
"npm:showdown@2.1.0"
]
}
}

View File

@ -172,6 +172,8 @@ const start = () => {
return endpoints.get.apiKey(query);
case '/heatmap.png':
return endpoints.get.heatmapPng();
case '/webview':
return endpoints.get.generateWebView(query);
default:
// Alert API user that they messed up
return stdResp.NotFound('NoAuth Get');

View File

@ -11,7 +11,7 @@ import { removeWorker } from 'artigen/managers/countManager.ts';
import { QueuedRoll } from 'artigen/managers/manager.d.ts';
import { ApiResolveMap, TestResolveMap } from 'artigen/managers/resolveManager.ts';
import { generateCountDetailsEmbed, generateDMFailed, generateRollDistsEmbed, generateRollEmbed } from 'artigen/utils/embeds.ts';
import { generateCountDetailsEmbed, generateDMFailed, generateRollDistsEmbed, generateRollEmbed, toggleWebView } from 'artigen/utils/embeds.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import dbClient from 'db/client.ts';
@ -188,23 +188,29 @@ export const onWorkerComplete = async (workerMessage: MessageEvent<SolvedRoll>,
const respMessage: Embed[] = [
{
color: infoColor1,
description: `This message contains information for a previous roll.\nPlease click on "<@${botId}> *Click to see attachment*" above this message to see the previous roll.`,
description: `This message contains information for a previous roll.\nPlease click on "<@${botId}> *Click to see attachment*" above this message to see the previous roll.
As anyone with the Web View link can view the roll, Web View is disabled by default for privacy. Click the button below to enable Web View and generate a link for this roll.`,
},
];
if (pubAttachments.map((file) => file.blob.size).reduce(basicReducer, 0) < config.maxFileSize) {
// All attachments will fit in one message
newMsg.reply({
newMsg
.reply({
embeds: respMessage,
file: pubAttachments,
});
})
.then((attachmentMsg) => toggleWebView(attachmentMsg, getUserIdForEmbed(rollRequest).toString(), false));
} else {
pubAttachments.forEach((file) => {
newMsg &&
newMsg.reply({
newMsg
.reply({
embeds: respMessage,
file,
});
})
.then((attachmentMsg) => toggleWebView(attachmentMsg, getUserIdForEmbed(rollRequest).toString(), false));
});
}
}

View File

@ -1,4 +1,4 @@
import { CreateMessage, EmbedField } from '@discordeno';
import { ButtonStyles, CreateMessage, DiscordenoMessage, EmbedField, MessageComponentTypes } from '@discordeno';
import { log, LogTypes as LT } from '@Log4Deno';
import config from '~config';
@ -8,9 +8,14 @@ import { ArtigenEmbedNoAttachment, ArtigenEmbedWithAttachment, SolvedRoll } from
import { CountDetails, RollDistributionMap, RollModifiers } from 'artigen/dice/dice.d.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { failColor, infoColor1, infoColor2 } from 'embeds/colors.ts';
import { basicReducer } from 'artigen/utils/reducers.ts';
import { failColor, infoColor1, infoColor2 } from 'embeds/colors.ts';
import { InteractionValueSeparator } from 'events/interactionCreate.ts';
import utils from 'utils/utils.ts';
export const rollingEmbed: CreateMessage = {
embeds: [
{
@ -130,9 +135,10 @@ export const generateRollDistsEmbed = (rollDists: RollDistributionMap): ArtigenE
const totalSize = fields.map((field) => field.name.length + field.value.length).reduce(basicReducer, 0);
if (totalSize > 4_000 || fields.length > 25 || fields.some((field) => field.name.length > 256 || field.value.length > 1024)) {
const rollDistBlob = new Blob([fields.map((field) => `# ${field.name}\n${field.value}`).join('\n\n') as BlobPart], { type: 'text' });
let rollDistErrDesc = 'The roll distribution was omitted from this message as it was over 4,000 characters, ';
if (rollDistBlob.size > config.maxFileSize) {
const rollDistErrDesc =
'The roll distribution was too large to be included and could not be attached below. If you would like to see the roll distribution details, please send the rolls in multiple messages.';
rollDistErrDesc +=
'and was too large to be attached as the file would be too large for Discord to handle. If you would like to see the roll distribution details, please simplify or send the rolls in multiple messages.';
return {
charCount: rollDistTitle.length + rollDistErrDesc.length,
embed: {
@ -143,7 +149,7 @@ export const generateRollDistsEmbed = (rollDists: RollDistributionMap): ArtigenE
hasAttachment: false,
};
} else {
const rollDistErrDesc = 'The roll distribution was too large to be included and has been attached below.';
rollDistErrDesc += 'and has been attached to a followup message as a formatted `.md` file.';
return {
charCount: rollDistTitle.length + rollDistErrDesc.length,
embed: {
@ -225,28 +231,31 @@ export const generateRollEmbed = (
}
const baseDesc = `${line1Details}**${line2Details.shift()}:**\n${line2Details.join(': ')}`;
const fullDesc = `${baseDesc}\n\n${details}`;
const formattingCount = (fullDesc.match(/(\*\*)|(__)|(~~)|(`)/g) ?? []).length / 2 + (fullDesc.match(/(<@)|(<#)/g) ?? []).length;
// Embed desc limit is 4096
if (baseDesc.length + details.length < 4_000) {
// Discord only formats 200 items per message
if (fullDesc.length < 4_000 && formattingCount <= 200) {
// Response is valid size
const desc = `${baseDesc}\n\n${details}`;
return {
charCount: desc.length,
charCount: fullDesc.length,
embed: {
color: infoColor2,
description: desc,
description: fullDesc,
},
hasAttachment: false,
};
}
// Response is too big, collapse it into a .txt file and send that instead.
const b = new Blob([`${baseDesc}\n\n${details}` as BlobPart], { type: 'text' });
details = `${baseDesc}\n\nDetails have been omitted from this message for being over 4000 characters.`;
// Response is too big, collapse it into a .md file and send that instead.
const b = new Blob([fullDesc as BlobPart], { type: 'text' });
details = `${baseDesc}\n\nDetails have been omitted from this message for ${fullDesc.length < 4_000 ? 'being over 4,000 characters' : 'having over 200 formatted items'}.`;
if (b.size > config.maxFileSize) {
// blob is too big, don't attach it
details +=
'\n\nFull details could not be attached to this messaged as a `.txt` file as the file would be too large for Discord to handle. If you would like to see the details of rolls, please send the rolls in multiple messages.';
'\n\nFull details could not be attached as the file would be too large for Discord to handle. If you would like to see the details of rolls, please simplify or send the rolls in multiple messages.';
return {
charCount: details.length,
embed: {
@ -258,7 +267,7 @@ export const generateRollEmbed = (
}
// blob is small enough, attach it
details += '\n\nFull details have been attached to this messaged as a `.txt` file for verification purposes.';
details += '\n\nFull details have been attached to a followup message as a formatted `.md` file for verification purposes.';
return {
charCount: details.length,
embed: {
@ -268,7 +277,40 @@ export const generateRollEmbed = (
hasAttachment: true,
attachment: {
blob: b,
name: 'rollDetails.txt',
name: 'rollDetails.md',
},
};
};
export const webViewCustomId = 'webview';
export const disabledStr = 'disabled';
export const toggleWebView = (attachmentMessage: DiscordenoMessage, ownerId: string, enableWebView: boolean) => {
attachmentMessage
.edit({
embeds: [
{
...attachmentMessage.embeds[0],
fields: [
{
name: 'Web View:',
value: enableWebView ? `[Open Web View](${config.api.publicDomain}api/webview?c=${attachmentMessage.channelId}&m=${attachmentMessage.id})` : `Web View is ${disabledStr}.`,
},
],
},
],
components: [
{
type: MessageComponentTypes.ActionRow,
components: [
{
type: MessageComponentTypes.Button,
label: enableWebView ? 'Disable Web View' : 'Enable Web View',
customId: `${webViewCustomId}${InteractionValueSeparator}${ownerId}${InteractionValueSeparator}${enableWebView ? 'disable' : 'enable'}`,
style: ButtonStyles.Secondary,
},
],
},
],
})
.catch((e) => utils.commonLoggers.messageEditError('embeds.ts:304', attachmentMessage, e));
};

View File

@ -4,6 +4,7 @@ import { apiChannel } from 'endpoints/gets/apiChannel.ts';
import { apiKey } from 'endpoints/gets/apiKey.ts';
import { apiKeyAdmin } from 'endpoints/gets/apiKeyAdmin.ts';
import { apiRoll } from 'endpoints/gets/apiRoll.ts';
import { generateWebView } from 'endpoints/gets/webView.ts';
import { heatmapPng } from 'endpoints/gets/heatmapPng.ts';
import { apiChannelAdd } from 'endpoints/posts/apiChannelAdd.ts';
@ -21,6 +22,7 @@ export default {
apiRoll,
apiKeyAdmin,
apiChannel,
generateWebView,
heatmapPng,
},
post: {

View File

@ -2,12 +2,12 @@ import { STATUS_CODE, STATUS_TEXT } from '@std/http';
export const heatmapPng = (): Response => {
const file = Deno.readFileSync('./src/endpoints/gets/heatmap.png');
const imageHeaders = new Headers();
imageHeaders.append('Content-Type', 'image/png');
const headers = new Headers();
headers.append('Content-Type', 'image/png');
// Send basic OK to indicate key has been sent
return new Response(file, {
status: STATUS_CODE.OK,
statusText: STATUS_TEXT[STATUS_CODE.OK],
headers: imageHeaders,
headers,
});
};

View File

@ -0,0 +1,222 @@
import { DiscordenoMember, getChannel, getMember, getMessage, getRoles } from '@discordeno';
import { log, LogTypes as LT } from '@Log4Deno';
import showdown from '@showdown';
import { STATUS_CODE, STATUS_TEXT } from '@std/http/status';
import config from '~config';
import { disabledStr } from 'artigen/utils/embeds.ts';
import utils from 'utils/utils.ts';
// globalName is added with discord's new username system
interface ModernMemberHOTFIX extends DiscordenoMember {
globalName: string;
}
const converter = new showdown.Converter({
emoji: true,
underline: true,
});
// Utilize the pre-existing stylesheets, do a little tweaking to make it ours
const wrapBasic = (str: string) =>
`<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>The Artificer Roll Web View</title>
<meta name="distribution" content="web">
<meta name="web_author" content="Ean Milligan (ean@milligan.dev)">
<meta name="author" content="Ean Milligan (ean@milligan.dev)">
<meta name="designer" content="Ean Milligan (ean@milligan.dev)">
<meta name="publisher" content="Ean Milligan (ean@milligan.dev)">
<meta name="robots" content="noindex, nofollow">
<link rel="shortcut icon" href="https://discord.burne99.com/TheArtificer/favicon.ico">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@100..900&display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Cinzel|Play">
<link rel="stylesheet" href="https://discord.burne99.com/TheArtificer/theme.css">
<link rel="stylesheet" href="https://discord.burne99.com/TheArtificer/main.css">
<style>
p {
margin: 0;
}
button {
font-family: 'Play', sans-serif;
height: 2.5rem;
cursor: pointer;
color: var(--header-font-color);
background-color: var(--footer-bg-color);
border: 2px solid var(--slug-bg);
border-radius: 8px;
}
button:hover {
background-color: var(--code-bg);
}
.selected {
background-color: var(--page-bg-color);
}
strong {
font-weight: 900;
}
</style>
</head>
<body id="page">
${str}
</body>
</html>`;
const centerHTML = (str: string) => `<center>${str}</center>`;
const badRequestMD = '# Invalid URL for Web View!';
const badRequestHTML = wrapBasic(centerHTML(converter.makeHtml(badRequestMD)));
const notAuthorizedMD = '# Web View is Disabled for this roll!';
const notAuthorizedHTML = wrapBasic(centerHTML(converter.makeHtml(notAuthorizedMD)));
const failedToGetAttachmentMD = '# Failed to get attachment from Discord!';
const failedToGetAttachmentHTML = wrapBasic(centerHTML(converter.makeHtml(failedToGetAttachmentMD)));
interface HtmlResp {
name: string;
html: string;
}
const headerHeight = '3rem';
const generatePage = (files: HtmlResp[]): string =>
wrapBasic(`<header id="fileBtns" style="display: flex; align-items: center; height: ${headerHeight}; line-height: ${headerHeight}; font-size: 1.5rem;">
<a href="https://discord.burne99.com/TheArtificer/" target="_blank" rel="noopener">${config.name} Roll Web View</a>
<span style="margin-left: auto; font-family: 'Play', sans-serif; font-size: 1rem;">Available Files:</span>
${
files
.map(
(f, idx) =>
`<button style="margin-left: 1rem;" id="${f.name}-btn" class="${
idx === 0 ? 'selected' : ''
}" onclick="for (var child of document.getElementById('fileBody').children) {child.style.display = 'none'} document.getElementById('${f.name}').style.display = 'block'; for (var child of document.getElementById('fileBtns').children) {child.className = ''} document.getElementById('${f.name}-btn').className = 'selected';">${f.name}</button>`,
)
.join('')
}
<button style="margin-left: auto;" onclick="document.getElementById('fileBody').style.whiteSpace = (document.getElementById('fileBody').style.whiteSpace === 'pre' ? 'pre-wrap' : 'pre')">Toggle Word Wrap</button>
</header>
<div id="fileBody" style="height: calc(100vh - ${headerHeight}); margin: 0 0.5rem; overflow: auto; white-space: pre-wrap; font-family: 'Roboto', sans-serif; font-weight: 300;">
${files.map((f, idx) => `<div id="${f.name}" style="display: ${idx === 0 ? 'block' : 'none'};">${f.html}</div>`).join('')}
</div>`);
const colorShade = (col: string, amt: number) => {
col = col.replace(/^#/, '');
if (col.length === 3) col = col[0] + col[0] + col[1] + col[1] + col[2] + col[2];
const parts = col.match(/.{2}/g) ?? [];
let r = parts.shift() ?? '00';
let g = parts.shift() ?? '00';
let b = parts.shift() ?? '00';
const [rInt, gInt, bInt] = [parseInt(r, 16) + amt, parseInt(g, 16) + amt, parseInt(b, 16) + amt];
r = Math.max(Math.min(255, rInt), 0).toString(16);
g = Math.max(Math.min(255, gInt), 0).toString(16);
b = Math.max(Math.min(255, bInt), 0).toString(16);
const rr = (r.length < 2 ? '0' : '') + r;
const gg = (g.length < 2 ? '0' : '') + g;
const bb = (b.length < 2 ? '0' : '') + b;
return `#${rr}${gg}${bb}`;
};
const makeMention = (mentionType: string, name: string, backgroundColor: string, color = 'var(--page-font-color)') =>
`<span style="background-color: ${backgroundColor}; color: ${color}; padding: 2px 4px; border-radius: 4px;">${mentionType}${name}</span>`;
export const generateWebView = async (query: Map<string, string>): Promise<Response> => {
const headers = new Headers();
headers.append('Content-Type', 'text/html');
const messageId = BigInt(query.get('m') ?? '0');
const channelId = BigInt(query.get('c') ?? '0');
if (!messageId || !channelId) {
return new Response(badRequestHTML, {
status: STATUS_CODE.BadRequest,
statusText: STATUS_TEXT[STATUS_CODE.BadRequest],
headers,
});
}
const attachmentMessage = await getMessage(channelId, messageId).catch((e) => utils.commonLoggers.messageGetError('webView.ts:23', channelId, messageId, e));
const discordAttachments = attachmentMessage?.attachments ?? [];
const embed = attachmentMessage?.embeds.shift();
const webViewField = embed?.fields?.shift();
if (!attachmentMessage || discordAttachments.length === 0 || !embed || !webViewField) {
return new Response(badRequestHTML, {
status: STATUS_CODE.BadRequest,
statusText: STATUS_TEXT[STATUS_CODE.BadRequest],
headers,
});
}
if (webViewField.value.includes(disabledStr)) {
return new Response(notAuthorizedHTML, {
status: STATUS_CODE.Forbidden,
statusText: STATUS_TEXT[STATUS_CODE.Forbidden],
headers,
});
}
const htmlArr: HtmlResp[] = [];
for (const discordAttachment of discordAttachments) {
const attachment = await fetch(discordAttachment.url).catch((e) => log(LT.LOG, `Failed to get attachment: ${discordAttachment}`, e));
const bodyText = (await attachment?.text()) ?? '';
htmlArr.push({
name: discordAttachment.filename,
html: bodyText ? converter.makeHtml(bodyText) : failedToGetAttachmentHTML,
});
}
let fullPage = generatePage(htmlArr);
if (fullPage.indexOf('<@&')) {
const guildRoles = (await getRoles(attachmentMessage.guildId).catch((e) => log(LT.LOG, `Failed to get Guild Roles: ${attachmentMessage.guildId}`, e))) ?? [];
const rolesToReplace = fullPage.matchAll(/<@&(\d+)>/g);
for (const roleToReplace of rolesToReplace) {
const role = guildRoles.filter((r) => r.id === BigInt(roleToReplace[1] ?? '-1')).shift() ?? { name: 'unknown-role', color: 4211819 };
fullPage = fullPage.replaceAll(
roleToReplace[0],
makeMention('@', role.name, colorShade(`#${role.color.toString(16)}`, -100), colorShade(`#${role.color.toString(16)}`, 50)),
);
}
}
if (fullPage.indexOf('<#')) {
const channelsToReplace = fullPage.matchAll(/<#(\d+)>/g);
for (const channelToReplace of channelsToReplace) {
const channel = (await getChannel(BigInt(channelToReplace[1] ?? '-1')).catch((e) => log(LT.LOG, `Failed to get Channel: ${channelToReplace[1]}`, e))) ?? {
name: 'unknown',
};
fullPage = fullPage.replaceAll(channelToReplace[0], makeMention('#', channel.name ?? 'unknown', '#40446b'));
}
}
if (fullPage.indexOf('<@')) {
const usersToReplace = fullPage.matchAll(/<@(\d+)>/g);
for (const userToReplace of usersToReplace) {
const rawUser = await getMember(attachmentMessage.guildId, BigInt(userToReplace[1] ?? '-1')).catch((e) => log(LT.LOG, `Failed to get Channel: ${userToReplace[1]}`, e));
const user = rawUser ? (rawUser as ModernMemberHOTFIX) : {
name: (_gId: bigint) => 'unknown-user',
username: 'unknown-user',
globalName: 'unknown-user',
};
const nickName = user.name(attachmentMessage.guildId);
const name = nickName === user.username ? user.globalName : nickName ?? user.globalName;
fullPage = fullPage.replaceAll(userToReplace[0], makeMention('@', name ?? user.username, '#40446b'));
}
}
return new Response(fullPage, {
status: STATUS_CODE.OK,
statusText: STATUS_TEXT[STATUS_CODE.OK],
headers,
});
};

View File

@ -1,31 +1,76 @@
import { ButtonData, DiscordMessageComponentTypes, editMessage, Interaction, InteractionResponseTypes, SelectMenuData, sendInteractionResponse } from '@discordeno';
import {
ButtonData,
DiscordenoMessage,
DiscordMessageComponentTypes,
editMessage,
Interaction,
InteractionResponseTypes,
MessageFlags,
SelectMenuData,
sendInteractionResponse,
structures,
} from '@discordeno';
import { log, LogTypes as LT } from '@Log4Deno';
import { toggleWebView, webViewCustomId } from 'artigen/utils/embeds.ts';
import { generateHelpMessage, helpCustomId } from 'commands/helpLibrary/generateHelpMessage.ts';
import { failColor } from 'embeds/colors.ts';
import utils from 'utils/utils.ts';
export const InteractionValueSeparator = '\u205a';
export const interactionCreateHandler = (interaction: Interaction) => {
const ackInteraction = (interaction: Interaction) =>
sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.DeferredUpdateMessage,
}).catch((e: Error) => utils.commonLoggers.messageSendError('interactionCreate.ts:26', interaction, e));
export const interactionCreateHandler = async (interaction: Interaction) => {
try {
if (interaction.data) {
const parsedData = JSON.parse(JSON.stringify(interaction.data)) as SelectMenuData | ButtonData;
if (parsedData.customId.startsWith(helpCustomId) && parsedData.componentType === DiscordMessageComponentTypes.SelectMenu) {
// Acknowledge the request since we're editing the original message
sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.DeferredUpdateMessage,
}).catch((e: Error) => utils.commonLoggers.messageEditError('interactionCreate.ts:26', interaction, e));
ackInteraction(interaction);
// Edit original message
editMessage(BigInt(interaction.channelId ?? '0'), BigInt(interaction.message?.id ?? '0'), generateHelpMessage(parsedData.values[0])).catch((e: Error) =>
editMessage(BigInt(interaction.channelId ?? '0'), BigInt(interaction.message?.id ?? '0'), generateHelpMessage(parsedData.values[0])).catch((e) =>
utils.commonLoggers.messageEditError('interactionCreate.ts:30', interaction, e)
);
return;
}
log(LT.WARN, `UNHANDLED INTERACTION!!! data: ${JSON.stringify(interaction.data)}`);
if (parsedData.customId.startsWith(webViewCustomId) && parsedData.componentType === DiscordMessageComponentTypes.Button && interaction.message) {
const ownerId = parsedData.customId.split(InteractionValueSeparator)[1] ?? 'missingOwnerId';
const userInteractingId = interaction.member?.user.id ?? interaction.user?.id ?? 'missingUserId';
if (ownerId === userInteractingId) {
ackInteraction(interaction);
const enableWebView = parsedData.customId.split(InteractionValueSeparator)[2] === 'enable';
const ddMsg: DiscordenoMessage = await structures.createDiscordenoMessage(interaction.message);
toggleWebView(ddMsg, ownerId, enableWebView);
} else {
sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
flags: MessageFlags.Empheral,
embeds: [
{
color: failColor,
title: 'Not Allowed!',
description: 'Only the original user that requested this roll can disable/enable Web View.',
},
],
},
}).catch((e) => utils.commonLoggers.messageSendError('interactionCreate.ts:57', interaction, e));
}
return;
}
log(LT.WARN, `UNHANDLED INTERACTION!!! data: ${JSON.stringify(interaction.data)} | Full Interaction: ${JSON.stringify(interaction)}`);
} else {
log(LT.WARN, `UNHANDLED INTERACTION!!! Missing data! ${JSON.stringify(interaction)}`);
}

View File

@ -7,9 +7,11 @@ import { log, LogTypes as LT } from '@Log4Deno';
import { DiscordenoMessage, Interaction } from '@discordeno';
const genericLogger = (level: LT, message: string) => log(level, message);
const messageGetError = (location: string, channelId: bigint | string, messageId: bigint | string, err: Error) =>
genericLogger(LT.ERROR, `${location} | Failed to edit message: ${channelId}-${messageId} | Error: ${err.name} - ${err.message}`);
const messageEditError = (location: string, message: DiscordenoMessage | Interaction | string, err: Error) =>
genericLogger(LT.ERROR, `${location} | Failed to edit message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`);
const messageSendError = (location: string, message: DiscordenoMessage | string, err: Error) =>
const messageSendError = (location: string, message: DiscordenoMessage | Interaction | string, err: Error) =>
genericLogger(LT.ERROR, `${location} | Failed to send message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`);
const messageDeleteError = (location: string, message: DiscordenoMessage | string, err: Error) =>
genericLogger(LT.ERROR, `${location} | Failed to delete message: ${JSON.stringify(message)} | Error: ${err.name} - ${err.message}`);
@ -18,8 +20,9 @@ const dbError = (location: string, type: string, err: Error) => genericLogger(LT
export default {
commonLoggers: {
dbError,
messageEditError,
messageSendError,
messageDeleteError,
messageEditError,
messageGetError,
messageSendError,
},
};

View File

@ -1,5 +1,5 @@
body {
font-family: "Play", sans-serif;
font-family: 'Play', sans-serif;
padding: 0;
margin: 0;
@ -11,21 +11,22 @@ body {
display: grid;
grid-template-columns: auto;
grid-template-rows: 3rem calc(100vh - 5rem) 2rem;
grid-template-areas: "header" "page-contents" "footer";
grid-template-areas: 'header' 'page-contents' 'footer';
color: var(--page-font-color);
background-color: var(--page-bg-color);
}
header,
#header {
grid-area: header;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
grid-template-areas: "header-left header-right";
grid-template-areas: 'header-left header-right';
font-family: "Cinzel", serif;
font-family: 'Cinzel', serif;
font-size: 2rem;
line-height: 3rem;
font-weight: 500;
@ -59,7 +60,7 @@ body {
display: grid;
grid-template-columns: 1fr 1.5fr 1.5fr 1fr;
grid-template-rows: auto;
grid-template-areas: ". footer-left footer-right .";
grid-template-areas: '. footer-left footer-right .';
line-height: 2rem;
height: 2rem;
@ -84,7 +85,7 @@ body {
display: grid;
grid-template-columns: auto;
grid-template-rows: fit-content(5rem) fit-content(10rem) fit-content(37rem) auto 1rem;
grid-template-areas: "slogan" "logo-desc" "examples" "api" "final";
grid-template-areas: 'slogan' 'logo-desc' 'examples' 'api' 'final';
overflow-y: auto;
}
@ -105,7 +106,7 @@ body {
display: grid;
grid-template-columns: 11rem auto;
grid-template-rows: auto;
grid-template-areas: "logo description";
grid-template-areas: 'logo description';
}
#logo {

View File

@ -20,7 +20,10 @@
--slug-border: rgb(0, 0, 0);
}
#header a {
header a,
header a:visited,
#header a,
#header a:visited {
color: var(--header-font-color);
text-decoration: none;
}
@ -29,11 +32,13 @@ a {
color: var(--link-new-color);
}
a:active, a:visited {
a:active,
a:visited {
color: var(--link-visited-color);
}
a:hover,
header a:hover,
#header a:hover {
cursor: pointer;
color: var(--link-hover-color);