156 lines
3.8 KiB
TypeScript
156 lines
3.8 KiB
TypeScript
'use node';
|
|
|
|
import { action } from '../_generated/server';
|
|
import { v } from 'convex/values';
|
|
import { internal } from '../_generated/api';
|
|
|
|
/**
|
|
* Extrair preview de link (metadados Open Graph) - função auxiliar
|
|
*/
|
|
async function extrairPreviewLinkHelper(url: string) {
|
|
try {
|
|
// Validar URL
|
|
let urlObj: URL;
|
|
try {
|
|
urlObj = new URL(url);
|
|
} catch {
|
|
return null;
|
|
}
|
|
|
|
// Buscar HTML da página
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
'User-Agent': 'Mozilla/5.0 (compatible; SGSE-Bot/1.0)'
|
|
},
|
|
signal: AbortSignal.timeout(5000) // Timeout de 5 segundos
|
|
});
|
|
|
|
if (!response.ok) {
|
|
return null;
|
|
}
|
|
|
|
const html = await response.text();
|
|
|
|
// Extrair metadados Open Graph e Twitter Cards
|
|
const metadata: {
|
|
titulo?: string;
|
|
descricao?: string;
|
|
imagem?: string;
|
|
site?: string;
|
|
} = {};
|
|
|
|
// Título (og:title ou twitter:title ou <title>)
|
|
const ogTitleMatch = html.match(
|
|
/<meta\s+property=["']og:title["']\s+content=["']([^"']+)["']/i
|
|
);
|
|
const twitterTitleMatch = html.match(
|
|
/<meta\s+name=["']twitter:title["']\s+content=["']([^"']+)["']/i
|
|
);
|
|
const titleMatch = html.match(/<title>([^<]+)<\/title>/i);
|
|
|
|
metadata.titulo = ogTitleMatch?.[1] || twitterTitleMatch?.[1] || titleMatch?.[1] || undefined;
|
|
if (metadata.titulo) {
|
|
metadata.titulo = metadata.titulo.trim().substring(0, 200);
|
|
}
|
|
|
|
// Descrição (og:description ou twitter:description ou meta description)
|
|
const ogDescMatch = html.match(
|
|
/<meta\s+property=["']og:description["']\s+content=["']([^"']+)["']/i
|
|
);
|
|
const twitterDescMatch = html.match(
|
|
/<meta\s+name=["']twitter:description["']\s+content=["']([^"']+)["']/i
|
|
);
|
|
const metaDescMatch = html.match(
|
|
/<meta\s+name=["']description["']\s+content=["']([^"']+)["']/i
|
|
);
|
|
|
|
metadata.descricao =
|
|
ogDescMatch?.[1] || twitterDescMatch?.[1] || metaDescMatch?.[1] || undefined;
|
|
if (metadata.descricao) {
|
|
metadata.descricao = metadata.descricao.trim().substring(0, 300);
|
|
}
|
|
|
|
// Imagem (og:image ou twitter:image)
|
|
const ogImageMatch = html.match(
|
|
/<meta\s+property=["']og:image["']\s+content=["']([^"']+)["']/i
|
|
);
|
|
const twitterImageMatch = html.match(
|
|
/<meta\s+name=["']twitter:image["']\s+content=["']([^"']+)["']/i
|
|
);
|
|
|
|
const imageUrl = ogImageMatch?.[1] || twitterImageMatch?.[1];
|
|
if (imageUrl) {
|
|
// Resolver URL relativa
|
|
try {
|
|
metadata.imagem = new URL(imageUrl, url).href;
|
|
} catch {
|
|
metadata.imagem = imageUrl;
|
|
}
|
|
}
|
|
|
|
// Site (og:site_name ou domínio)
|
|
const ogSiteMatch = html.match(
|
|
/<meta\s+property=["']og:site_name["']\s+content=["']([^"']+)["']/i
|
|
);
|
|
metadata.site = ogSiteMatch?.[1] || urlObj.hostname.replace(/^www\./, '');
|
|
|
|
return {
|
|
url,
|
|
titulo: metadata.titulo,
|
|
descricao: metadata.descricao,
|
|
imagem: metadata.imagem,
|
|
site: metadata.site
|
|
};
|
|
} catch (error) {
|
|
console.error('Erro ao extrair preview de link:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processar preview de link e atualizar mensagem
|
|
*/
|
|
export const processarPreviewLink = action({
|
|
args: {
|
|
mensagemId: v.id('mensagens'),
|
|
url: v.string()
|
|
},
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
// Extrair preview
|
|
const preview = await extrairPreviewLinkHelper(args.url);
|
|
|
|
if (preview) {
|
|
// Atualizar mensagem com preview
|
|
await ctx.runMutation(internal.chat.atualizarLinkPreview, {
|
|
mensagemId: args.mensagemId,
|
|
linkPreview: preview
|
|
});
|
|
}
|
|
|
|
return null;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Extrair preview de link (metadados Open Graph) - versão pública
|
|
*/
|
|
export const extrairPreviewLink = action({
|
|
args: {
|
|
url: v.string()
|
|
},
|
|
returns: v.union(
|
|
v.object({
|
|
url: v.string(),
|
|
titulo: v.optional(v.string()),
|
|
descricao: v.optional(v.string()),
|
|
imagem: v.optional(v.string()),
|
|
site: v.optional(v.string())
|
|
}),
|
|
v.null()
|
|
),
|
|
handler: async (ctx, args) => {
|
|
return await extrairPreviewLinkHelper(args.url);
|
|
}
|
|
});
|