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
5 changes: 5 additions & 0 deletions .changeset/ninety-pillows-read.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@embedly/bot": minor
---

add direct fetch fallback when api fails
58 changes: 11 additions & 47 deletions apps/bot/src/commands/embed.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { treaty } from "@elysiajs/eden";
import type { App } from "@embedly/api";
import {
Embed,
EmbedFlagNames,
Expand Down Expand Up @@ -35,8 +33,7 @@ import {
ApplicationIntegrationType,
InteractionContextType
} from "discord.js";

const app = treaty<App>(process.env.EMBEDLY_API_DOMAIN!);
import { fetchPostData } from "../fetch.ts";

export class EmbedCommand extends Command {
public constructor(
Expand Down Expand Up @@ -151,57 +148,24 @@ export class EmbedCommand extends Command {

await interaction.deferReply();

const { data, error } = await this.container.tracer.startActiveSpan(
"fetch_from_api",
async (s) => {
s.setAttribute("embedly.platform", platform.type);
s.setAttribute("embedly.url", url);

const otelHeaders: Record<string, string> = {};
propagation.inject(context.active(), otelHeaders);

const res = await app.api.scrape.post(
{
platform: platform.type,
url
},
{
headers: {
authorization: `Bearer ${process.env.DISCORD_BOT_TOKEN}`,
...otelHeaders
}
}
);
if (res.error) {
s.setStatus({
code: SpanStatusCode.ERROR,
message:
"detail" in res.error.value
? res.error.value.detail
: res.error.value.type
});
s.recordException(
"detail" in res.error.value
? res.error.value.detail
: res.error.value.type
);
}
s.end();
return res;
}
);
const otel_headers: Record<string, string> = {};
propagation.inject(context.active(), otel_headers);

if (error?.status === 400 || error?.status === 500) {
let data: Record<string, any>;
try {
data = await fetchPostData(platform.type, url, otel_headers);
} catch (fetch_error: any) {
const error_context = {
...log_ctx,
platform: platform.type,
...("context" in error.value ? error.value.context : {})
error_message: fetch_error.message,
error_stack: fetch_error.stack
};
this.container.logger.error(
formatLog(error.value, error_context)
formatLog(EMBEDLY_UNHANDLED_ERROR, error_context)
);
return await interaction.editReply({
content: formatDiscord(error.value, error_context)
content: formatDiscord(EMBEDLY_UNHANDLED_ERROR, error_context)
});
}

Expand Down
77 changes: 77 additions & 0 deletions apps/bot/src/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { treaty } from "@elysiajs/eden";
import type { App } from "@embedly/api";
import Platforms, {
type EmbedlyPlatformType
} from "@embedly/platforms";
import { SpanStatusCode } from "@opentelemetry/api";
import { container } from "@sapphire/framework";

const app = treaty<App>(process.env.EMBEDLY_API_DOMAIN!);

export async function fetchPostData(
platform_type: EmbedlyPlatformType,
url: string,
otel_headers: Record<string, string>
): Promise<Record<string, any>> {
const handler = Platforms[platform_type];

const { data, error } = await container.tracer.startActiveSpan(
"fetch_from_api",
async (s) => {
s.setAttribute("embedly.platform", platform_type);
s.setAttribute("embedly.url", url);

const res = await app.api.scrape.post(
{ platform: platform_type, url },
{
headers: {
authorization: `Bearer ${process.env.DISCORD_BOT_TOKEN}`,
...otel_headers
}
}
);

if (res.error) {
s.setStatus({
code: SpanStatusCode.ERROR,
message:
"detail" in res.error.value
? res.error.value.detail
: res.error.value.type
});
}
s.end();
return res;
}
);

if (!error) {
return data;
}

return await container.tracer.startActiveSpan(
"fetch_direct_fallback",
async (s) => {
s.setAttribute("embedly.platform", platform_type);
s.setAttribute("embedly.url", url);
s.setAttribute("embedly.api_error_status", error.status);

try {
const post_id = await handler.parsePostId(url);
s.setAttribute("embedly.post_id", post_id);
const post_data = await handler.fetchPost(post_id);
s.setStatus({ code: SpanStatusCode.OK });
return post_data as Record<string, any>;
} catch (fallback_error: any) {
s.setStatus({
code: SpanStatusCode.ERROR,
message: fallback_error.message ?? String(fallback_error)
});
s.recordException(fallback_error);
throw fallback_error;
} finally {
s.end();
}
}
);
}
81 changes: 22 additions & 59 deletions apps/bot/src/listeners/messageCreate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { treaty } from "@elysiajs/eden";
import type { App } from "@embedly/api";
import {
Embed,
EmbedFlagNames,
Expand Down Expand Up @@ -28,8 +26,7 @@ import {
} from "@opentelemetry/api";
import { Events, Listener } from "@sapphire/framework";
import { type Message, MessageFlags } from "discord.js";

const app = treaty<App>(process.env.EMBEDLY_API_DOMAIN!);
import { fetchPostData } from "../fetch.ts";

export class MessageListener extends Listener<
typeof Events.MessageCreate
Expand Down Expand Up @@ -84,63 +81,29 @@ export class MessageListener extends Listener<
);
if (!platform) continue;

const { data, error } =
await this.container.tracer.startActiveSpan(
"fetch_from_api",
async (s) => {
s.setAttribute("embedly.platform", platform.type);
s.setAttribute("embedly.url", url);

const otelHeaders: Record<string, string> = {};
propagation.inject(context.active(), otelHeaders);
const otel_headers: Record<string, string> = {};
propagation.inject(context.active(), otel_headers);

const res = await app.api.scrape.post(
{
platform: platform.type,
url
},
{
headers: {
authorization: `Bearer ${process.env.DISCORD_BOT_TOKEN}`,
...otelHeaders
}
}
);
if (res.error) {
s.setStatus({
code: SpanStatusCode.ERROR,
message:
"detail" in res.error.value
? res.error.value.detail
: res.error.value.type
});
s.recordException(
"detail" in res.error.value
? res.error.value.detail
: res.error.value.type
);
}
s.end();
return res;
}
let data: Record<string, any>;
try {
data = await fetchPostData(
platform.type,
url,
otel_headers
);
} catch (fetch_error: any) {
const error_context: EmbedlyInteractionContext &
EmbedlyPostContext = {
message_id: message.id,
user_id: message.author.id,
source: "message",
platform: platform.type,
error_message: fetch_error.message,
error_stack: fetch_error.stack
};
this.container.logger.error(
formatLog(EMBEDLY_UNHANDLED_ERROR, error_context)
);

if (error) {
if ("detail" in error.value) {
const error_context: EmbedlyInteractionContext &
EmbedlyPostContext = {
...("context" in error.value
? error.value.context
: {}),
message_id: message.id,
user_id: message.author.id,
source: "message",
platform: platform.type
};
this.container.logger.error(
formatLog(error.value, error_context)
);
}
return;
}

Expand Down
Loading