managarten/apps-archived/reader/apps/mobile/services/urlExtractorService.ts
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

131 lines
3 KiB
TypeScript

import { supabase } from '~/utils/supabase';
export interface ExtractedContent {
title: string;
content: string;
excerpt: string;
source: string;
domain: string;
author: string;
publishDate: string;
wordCount: number;
readingTime: number;
tags: string[];
}
export interface ExtractUrlError {
message: string;
code?: 'INVALID_URL' | 'FETCH_FAILED' | 'EXTRACTION_FAILED' | 'NETWORK_ERROR' | 'UNAUTHORIZED';
}
class UrlExtractorService {
async extractFromUrl(
url: string
): Promise<{ data: ExtractedContent | null; error: ExtractUrlError | null }> {
try {
// Basic URL validation
const urlPattern = /^https?:\/\/.+/;
if (!urlPattern.test(url)) {
return {
data: null,
error: {
message: 'Bitte gib eine gültige URL ein (http:// oder https://)',
code: 'INVALID_URL',
},
};
}
const { data, error } = await supabase.functions.invoke('extract-url', {
body: { url },
});
if (error) {
console.error('Error extracting URL:', error);
// Handle specific error cases
if (error.message?.includes('Unauthorized')) {
return {
data: null,
error: {
message: 'Nicht autorisiert. Bitte melde dich erneut an.',
code: 'UNAUTHORIZED',
},
};
}
if (error.message?.includes('Failed to fetch URL')) {
return {
data: null,
error: {
message: 'Die Webseite konnte nicht geladen werden. Überprüfe die URL.',
code: 'FETCH_FAILED',
},
};
}
if (error.message?.includes('Could not extract')) {
return {
data: null,
error: {
message:
'Der Text konnte nicht extrahiert werden. Die Seite ist möglicherweise nicht kompatibel.',
code: 'EXTRACTION_FAILED',
},
};
}
return {
data: null,
error: { message: error.message || 'Ein Fehler ist aufgetreten', code: 'NETWORK_ERROR' },
};
}
if (!data) {
return {
data: null,
error: { message: 'Keine Daten empfangen', code: 'EXTRACTION_FAILED' },
};
}
return { data: data as ExtractedContent, error: null };
} catch (error) {
console.error('Unexpected error in extractFromUrl:', error);
return {
data: null,
error: { message: 'Ein unerwarteter Fehler ist aufgetreten', code: 'NETWORK_ERROR' },
};
}
}
validateUrl(url: string): boolean {
try {
const urlObj = new URL(url);
return ['http:', 'https:'].includes(urlObj.protocol);
} catch {
return false;
}
}
formatExtractedContent(extracted: ExtractedContent): string {
// Format the extracted content with title and metadata
let formatted = extracted.title + '\n\n';
if (extracted.author) {
formatted += `Von: ${extracted.author}\n`;
}
if (extracted.publishDate) {
formatted += `Veröffentlicht: ${extracted.publishDate}\n`;
}
if (extracted.domain) {
formatted += `Quelle: ${extracted.domain}\n`;
}
formatted += '\n' + extracted.content;
return formatted;
}
}
export const urlExtractorService = new UrlExtractorService();