(
request: FastifyRequest<{
Querystring: GetFaviconParams;
}>,
reply: FastifyReply,
)
| 230 | } |
| 231 | |
| 232 | export async function getFavicon( |
| 233 | request: FastifyRequest<{ |
| 234 | Querystring: GetFaviconParams; |
| 235 | }>, |
| 236 | reply: FastifyReply, |
| 237 | ) { |
| 238 | try { |
| 239 | logger.info({ url: request.query.url }, 'getFavicon'); |
| 240 | const url = validateUrl(request.query.url); |
| 241 | if (!url) { |
| 242 | return reply |
| 243 | .status(404) |
| 244 | .header('Content-Type', 'text/plain') |
| 245 | .send('Not found'); |
| 246 | } |
| 247 | |
| 248 | const cacheKey = createCacheKey(url.toString()); |
| 249 | |
| 250 | // Check cache first |
| 251 | const cached = await getFromCacheBinary(cacheKey); |
| 252 | if (cached) { |
| 253 | reply.header('Content-Type', cached.contentType); |
| 254 | reply.header('Cache-Control', 'public, max-age=604800, immutable'); |
| 255 | return reply.send(cached.buffer); |
| 256 | } |
| 257 | |
| 258 | let imageUrl: URL; |
| 259 | // If it's a direct image URL, use it directly |
| 260 | if (isDirectImage(url)) { |
| 261 | imageUrl = url; |
| 262 | } else { |
| 263 | logger.info({ url: url.toString() }, 'before parseUrlMeta'); |
| 264 | // For website URLs, extract favicon from HTML |
| 265 | const meta = await parseUrlMeta(url.toString()); |
| 266 | logger.info( |
| 267 | { url: url.toString(), favicon: meta?.favicon }, |
| 268 | 'parseUrlMeta result', |
| 269 | ); |
| 270 | if (meta?.favicon) { |
| 271 | imageUrl = new URL(meta.favicon); |
| 272 | } else { |
| 273 | // Try standard favicon location first |
| 274 | const { origin } = url; |
| 275 | imageUrl = new URL(`${origin}/favicon.ico`); |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | logger.info( |
| 280 | { |
| 281 | originalUrl: url.toString(), |
| 282 | imageUrl: imageUrl.toString(), |
| 283 | }, |
| 284 | 'Fetching favicon', |
| 285 | ); |
| 286 | |
| 287 | // Fetch the image |
| 288 | let { buffer, contentType, status } = await fetchImage(imageUrl); |
| 289 |
no test coverage detected