Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/orange-buses-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@embedly/logging": minor
"@embedly/bot": minor
---

add betterstack logging to bot and improve delete command
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ yarn.lock
.turbo

.env

AGENTS.md
19 changes: 9 additions & 10 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,24 @@
},
"dependencies": {
"@discordjs/rest": "^2.6.0",
"@elysiajs/node": "^1.3.1",
"@embedly/builder": "workspace:*",
"@embedly/logging": "workspace:*",
"@embedly/parser": "workspace:*",
"@embedly/platforms": "workspace:*",
"@embedly/types": "workspace:*",
"@logtail/edge": "^0.5.6",
"@logtail/edge": "^0.5.7",
"@logtail/node": "^0.5.6",
"discord-verify": "^1.2.0",
"elysia": "^1.3.21",
"wrangler": "^4.34.0"
"elysia": "^1.4.19",
"wrangler": "^4.56.0"
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
"@biomejs/biome": "2.3.10",
"@embedly/config": "workspace:*",
"@types/node": "^24.3.1",
"discord-api-types": "^0.38.22",
"pkgroll": "^2.14.3",
"tsx": "^4.20.3",
"typescript": "^5.8.3"
"@types/node": "^25.0.3",
"discord-api-types": "^0.38.37",
"pkgroll": "^2.21.4",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
8 changes: 4 additions & 4 deletions apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ import { Elysia, t } from "elysia";

const app = (env: Env, ctx: ExecutionContext) =>
new Elysia({ aot: false, normalize: false })
.onError(({ error }) => {
console.error(error);
.onError(({ set }) => {
set.status = 500;
return { error: "Internal server error" };
})
.decorate({ env, ctx })
.derive(({ ctx, env }) => {
Expand Down Expand Up @@ -84,7 +85,6 @@ const app = (env: Env, ctx: ExecutionContext) =>
err.context = post_log_ctx;

logger.error(...formatBetterStack(err, err.context));
console.error(error);

return status(err.status!, err);
}
Expand Down Expand Up @@ -127,7 +127,7 @@ const app = (env: Env, ctx: ExecutionContext) =>
const err = EMBEDLY_NO_LINK_IN_MESSAGE;
return status(err.status!, err);
}
const url = GENERIC_LINK_REGEX.exec(message.content)?.[0]!;
const url = GENERIC_LINK_REGEX.exec(message.content)![0];
const platform = getPlatformFromURL(url);
if (!platform) {
const err = EMBEDLY_NO_VALID_LINK;
Expand Down
4,493 changes: 3,942 additions & 551 deletions apps/api/worker-configuration.d.ts

Large diffs are not rendered by default.

25 changes: 13 additions & 12 deletions apps/bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,27 @@
"build": "pkgroll"
},
"devDependencies": {
"@biomejs/biome": "2.1.1",
"@biomejs/biome": "2.3.10",
"@embedly/api": "workspace:*",
"@embedly/builder": "workspace:*",
"@embedly/config": "workspace:*",
"@embedly/logging": "workspace:*",
"@embedly/parser": "workspace:*",
"@embedly/platforms": "workspace:*",
"@types/node": "^24.0.14",
"discord-api-types": "^0.38.16",
"pkgroll": "^2.14.3",
"tsx": "^4.20.3",
"typescript": "^5.8.3"
"@types/node": "^25.0.3",
"discord-api-types": "^0.38.37",
"pkgroll": "^2.21.4",
"tsx": "^4.21.0",
"typescript": "^5.9.3"
},
"dependencies": {
"@discordjs/core": "^2.2.0",
"@discordjs/rest": "^2.5.1",
"@discordjs/ws": "^2.0.3",
"@elysiajs/eden": "^1.3.3",
"@discordjs/core": "^2.4.0",
"@discordjs/rest": "^2.6.0",
"@discordjs/ws": "^2.0.4",
"@elysiajs/eden": "^1.4.5",
"@logtail/node": "^0.5.6",
"@sapphire/discord.js-utilities": "^7.3.3",
"@sapphire/framework": "^5.3.6",
"discord.js": "~14.21.0"
"@sapphire/framework": "^5.4.0",
"discord.js": "~14.25.1"
}
}
53 changes: 53 additions & 0 deletions apps/bot/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Logtail } from "@logtail/node";
import { container, SapphireClient } from "@sapphire/framework";
import {
ActivityType,
GatewayIntentBits,
PresenceUpdateStatus
} from "discord.js";

declare module "@sapphire/framework" {
interface Container {
betterstack: Logtail;
embed_authors: Map<string, string>;
}
}

export class EmbedlyClient extends SapphireClient {
public constructor() {
super({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
],
presence: {
activities: [
{
state: "Better embeds = Better Conversations",
type: ActivityType.Custom,
name: "Embedly"
}
],
afk: false,
status: PresenceUpdateStatus.Online
}
});
}

public override async login(token?: string) {
container.betterstack = new Logtail(
process.env.BETTERSTACK_SOURCE_TOKEN!,
{
endpoint: process.env.BETTERSTACK_INGESTING_HOST
}
);
container.embed_authors = new Map();
return super.login(token);
}

public override destroy() {
container.betterstack?.flush();
return super.destroy();
}
}
80 changes: 71 additions & 9 deletions apps/bot/src/commands/delete.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {
EMBEDLY_DELETE_FAILED,
EMBEDLY_DELETE_FAILED_WARN,
EMBEDLY_DELETE_SUCCESS,
EMBEDLY_DELETE_SUCCESS_INFO,
formatBetterStack,
formatDiscord
} from "@embedly/logging";
import { Command } from "@sapphire/framework";
Expand Down Expand Up @@ -49,6 +52,13 @@ export class DeleteCommand extends Command {
if (!interaction.inGuild()) return;
const msg = interaction.targetMessage;
if (msg.author.id !== this.container.client.id) {
this.container.betterstack.warn(
...formatBetterStack(EMBEDLY_DELETE_FAILED_WARN, {
message_id: msg.id,
user_id: interaction.user.id,
reason: "not_bot_message"
})
);
return await interaction.reply({
content: formatDiscord(EMBEDLY_DELETE_FAILED, {
message_id: msg.id
Expand All @@ -61,20 +71,64 @@ export class DeleteCommand extends Command {
flags: MessageFlags.Ephemeral
});

if (!msg.deletable) {
this.container.betterstack.warn(
...formatBetterStack(EMBEDLY_DELETE_FAILED_WARN, {
message_id: msg.id,
user_id: interaction.user.id,
reason: "not_deletable"
})
);
return await interaction.editReply({
content: formatDiscord(EMBEDLY_DELETE_FAILED, {
message_id: msg.id
})
});
}

let original_author_id = this.container.embed_authors.get(msg.id);

if (!original_author_id) {
try {
const reference = await msg.fetchReference();
original_author_id = reference.author.id;
} catch {
this.container.betterstack.warn(
...formatBetterStack(EMBEDLY_DELETE_FAILED_WARN, {
message_id: msg.id,
user_id: interaction.user.id,
reason: "no_author_mapping_and_no_reference"
})
);
return await interaction.editReply({
content: formatDiscord(EMBEDLY_DELETE_FAILED, {
message_id: msg.id
})
});
}
}

const guild = await interaction.guild!.fetch();
const runner = await guild.members.fetch(
interaction.member.user.id
);
const reference = await msg.fetchReference();

if (
(!runner.permissions.has(
PermissionFlagsBits.ManageMessages,
true
) &&
runner.id !== reference.author.id) ||
!msg.deletable
) {
const has_manage_permission = runner.permissions.has(
PermissionFlagsBits.ManageMessages,
true
);
const is_original_poster = runner.id === original_author_id;

if (!has_manage_permission && !is_original_poster) {
this.container.betterstack.warn(
...formatBetterStack(EMBEDLY_DELETE_FAILED_WARN, {
message_id: msg.id,
user_id: interaction.user.id,
original_author_id,
has_manage_permission,
reason: "insufficient_permissions"
})
);
return await interaction.editReply({
content: formatDiscord(EMBEDLY_DELETE_FAILED, {
message_id: msg.id
Expand All @@ -83,6 +137,14 @@ export class DeleteCommand extends Command {
}

await msg.delete();
this.container.embed_authors.delete(msg.id);
this.container.betterstack.info(
...formatBetterStack(EMBEDLY_DELETE_SUCCESS_INFO, {
message_id: msg.id,
user_id: interaction.user.id,
original_author_id
})
);
return await interaction.editReply({
content: formatDiscord(EMBEDLY_DELETE_SUCCESS, {
message_id: msg.id
Expand Down
71 changes: 42 additions & 29 deletions apps/bot/src/commands/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ import { treaty } from "@elysiajs/eden";
import type { App } from "@embedly/api";
import { Embed, EmbedFlags } from "@embedly/builder";
import {
EMBEDLY_EMBED_CREATED_COMMAND,
EMBEDLY_NO_LINK_IN_MESSAGE,
EMBEDLY_NO_LINK_WARN,
EMBEDLY_NO_VALID_LINK,
EMBEDLY_NO_VALID_LINK_WARN,
type EmbedlyInteractionContext,
formatBetterStack,
formatDiscord
} from "@embedly/logging";
import {
Expand Down Expand Up @@ -102,14 +106,20 @@ export class EmbedCommand extends Command {
user_id: interaction.user.id
} satisfies EmbedlyInteractionContext;
if (!hasLink(content)) {
this.container.betterstack.warn(
...formatBetterStack(EMBEDLY_NO_LINK_WARN, log_ctx)
);
return await interaction.reply({
content: formatDiscord(EMBEDLY_NO_LINK_IN_MESSAGE, log_ctx),
flags: ["Ephemeral"]
});
}
const url = GENERIC_LINK_REGEX.exec(content)?.[0]!;
const url = GENERIC_LINK_REGEX.exec(content)![0];
const platform = getPlatformFromURL(url);
if (!platform) {
this.container.betterstack.warn(
...formatBetterStack(EMBEDLY_NO_VALID_LINK_WARN, log_ctx)
);
return await interaction.reply({
content: formatDiscord(EMBEDLY_NO_VALID_LINK, log_ctx),
flags: ["Ephemeral"]
Expand All @@ -131,38 +141,41 @@ export class EmbedCommand extends Command {
);

if (error?.status === 400 || error?.status === 500) {
const error_context = {
...log_ctx,
...error.value.context!
};
this.container.betterstack.error(
...formatBetterStack(error.value, error_context)
);
return await interaction.editReply({
content: formatDiscord(error.value, {
...log_ctx,
...error.value.context!
})
content: formatDiscord(error.value, error_context)
});
}

try {
const embed = await Platforms[platform.type].createEmbed(data);
return await interaction.editReply({
components: [Embed.getDiscordEmbed(embed, flags)!],
flags: ["IsComponentsV2"],
allowedMentions: {
parse: [],
repliedUser: false
}
});
} catch (error) {
console.error(error);
return await interaction.editReply({
content: formatDiscord(
Platforms[platform.type].log_messages.failed,
{
...log_ctx,
platform: platform.type,
post_id: await Platforms[platform.type].parsePostId(url),
post_url: url
}
)
});
}
const embed = await Platforms[platform.type].createEmbed(data);
const bot_message = await interaction.editReply({
components: [Embed.getDiscordEmbed(embed, flags)!],
flags: ["IsComponentsV2"],
allowedMentions: {
parse: [],
repliedUser: false
}
});
this.container.embed_authors.set(
bot_message.id,
interaction.user.id
);
this.container.betterstack.info(
...formatBetterStack(EMBEDLY_EMBED_CREATED_COMMAND, {
interaction_id: interaction.id,
user_id: interaction.user.id,
bot_message_id: bot_message.id,
platform: platform.type,
url
})
);
return bot_message;
}

public override async contextMenuRun(
Expand Down
Loading