import { WikimediaProject, SearchResult, StudyPlan } from '../types'; export const WIKIMEDIA_PROJECTS: WikimediaProject[] = [ { id: 'wikipedia', name: 'Wikipedia', description: 'The free encyclopedia', apiUrl: 'https://en.wikipedia.org/w/api.php', color: 'bg-primary-500', icon: 'Book' }, { id: 'wikibooks', name: 'Wikibooks', description: 'Free textbooks and manuals', apiUrl: 'https://en.wikibooks.org/w/api.php', color: 'bg-secondary-500', icon: 'BookOpen' }, { id: 'wikiquote', name: 'Wikiquote', description: 'Collection of quotations', apiUrl: 'https://en.wikiquote.org/w/api.php', color: 'bg-accent-500', icon: 'Quote' }, { id: 'wikiversity', name: 'Wikiversity', description: 'Free learning resources', apiUrl: 'https://en.wikiversity.org/w/api.php', color: 'bg-success-500', icon: 'GraduationCap' }, { id: 'wiktionary', name: 'Wiktionary', description: 'Free dictionary', apiUrl: 'https://en.wiktionary.org/w/api.php', color: 'bg-warning-500', icon: 'Languages' }, { id: 'wikisource', name: 'Wikisource', description: 'Free library of source texts', apiUrl: 'https://en.wikisource.org/w/api.php', color: 'bg-error-500', icon: 'FileText' } ]; export class WikimediaAPI { private static async makeRequest(apiUrl: string, params: Record): Promise { const url = new URL(apiUrl); Object.entries(params).forEach(([key, value]) => { url.searchParams.append(key, value); }); try { const response = await fetch(url.toString()); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('API request failed:', error); throw error; } } static async search(query: string, project: string = 'wikipedia', limit: number = 10): Promise { const projectData = WIKIMEDIA_PROJECTS.find(p => p.id === project); if (!projectData) { throw new Error(`Unknown project: ${project}`); } const params = { action: 'query', format: 'json', list: 'search', srsearch: query, srlimit: limit.toString(), srprop: 'snippet|titlesnippet|size|timestamp', origin: '*' }; try { const data = await this.makeRequest(projectData.apiUrl, params); return data.query?.search?.map((result: any) => ({ title: result.title, pageid: result.pageid, size: result.size, snippet: result.snippet || 'No snippet available', timestamp: result.timestamp, project: project, url: this.buildProjectUrl(project, result.title) })) || []; } catch (error) { console.error(`Search failed for ${project}:`, error); return []; } } static async searchMultipleProjects(query: string, projects: string[] = ['wikipedia', 'wikibooks', 'wikiversity'], limit: number = 5): Promise { const searchPromises = projects.map(project => this.search(query, project, limit)); try { const results = await Promise.allSettled(searchPromises); const allResults: SearchResult[] = []; results.forEach((result) => { if (result.status === 'fulfilled') { allResults.push(...result.value); } }); return allResults.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); } catch (error) { console.error('Multi-project search failed:', error); return []; } } static async getPageContent(title: string, project: string = 'wikipedia'): Promise { const projectData = WIKIMEDIA_PROJECTS.find(p => p.id === project); if (!projectData) { throw new Error(`Unknown project: ${project}`); } // Handle language-specific Wikipedia projects let apiUrl = projectData.apiUrl; if (project.includes('-wikipedia')) { const langCode = project.split('-')[0]; apiUrl = `https://${langCode}.wikipedia.org/w/api.php`; } // Try multiple approaches to get the best content const approaches = [ // Approach 1: Get full extract { action: 'query', format: 'json', titles: title, prop: 'extracts', exintro: 'false', explaintext: 'true', exsectionformat: 'plain', origin: '*' }, // Approach 2: Get intro only if full content fails { action: 'query', format: 'json', titles: title, prop: 'extracts', exintro: 'true', explaintext: 'true', exsentences: '10', origin: '*' } ]; for (const params of approaches) { try { const data = await this.makeRequest(apiUrl, params); const pages = data.query?.pages || {}; const pageId = Object.keys(pages)[0]; if (pageId !== '-1' && pages[pageId]?.extract) { const content = pages[pageId].extract; if (content.length > 200) { return content; } } } catch (error) { console.log(`Approach failed for ${title}:`, error); continue; } } // Fallback: Return a helpful message const projectName = projectData.name; return `This article exists but the content could not be fully loaded through the API. The article "${title}" is available on ${projectName}, but due to API limitations or the article's structure, we cannot display the full content here. This might happen because: - The article is a disambiguation page - The content is primarily in tables, lists, or special formatting - The article has restricted content access - There are temporary API limitations For the complete article with all formatting, images, and references, please visit the original source using the "View Original" button.`; } static async getRandomArticles(project: string = 'wikipedia', count: number = 5): Promise { const projectData = WIKIMEDIA_PROJECTS.find(p => p.id === project); if (!projectData) { throw new Error(`Unknown project: ${project}`); } const params = { action: 'query', format: 'json', list: 'random', rnnamespace: '0', rnlimit: count.toString(), origin: '*' }; try { const data = await this.makeRequest(projectData.apiUrl, params); const randomPages = data.query?.random || []; // Get snippets for random articles const results: SearchResult[] = []; for (const page of randomPages) { try { const snippet = await this.getPageSnippet(page.title, project); results.push({ title: page.title, pageid: page.id, size: 0, snippet: snippet, timestamp: new Date().toISOString(), project: project, url: this.buildProjectUrl(project, page.title) }); } catch (error) { // If we can't get snippet, still include the article results.push({ title: page.title, pageid: page.id, size: 0, snippet: `Random article from ${projectData.name}`, timestamp: new Date().toISOString(), project: project, url: this.buildProjectUrl(project, page.title) }); } } return results; } catch (error) { console.error(`Failed to get random articles from ${project}:`, error); return []; } } private static async getPageSnippet(title: string, project: string): Promise { const projectData = WIKIMEDIA_PROJECTS.find(p => p.id === project); if (!projectData) { return 'No description available'; } const params = { action: 'query', format: 'json', titles: title, prop: 'extracts', exintro: 'true', explaintext: 'true', exsentences: '2', origin: '*' }; try { const data = await this.makeRequest(projectData.apiUrl, params); const pages = data.query?.pages || {}; const pageId = Object.keys(pages)[0]; if (pageId === '-1') { return 'No description available'; } const extract = pages[pageId]?.extract || 'No description available'; return extract.length > 200 ? extract.substring(0, 200) + '...' : extract; } catch (error) { return 'No description available'; } } static async generateStudyPlan(topic: string, difficulty: 'beginner' | 'intermediate' | 'advanced' = 'beginner'): Promise { try { // Search across multiple projects for comprehensive content const searchResults = await this.searchMultipleProjects(topic, ['wikipedia', 'wikibooks', 'wikiversity'], 15); if (searchResults.length === 0) { throw new Error('No content found for this topic'); } // Filter and organize results const topicCount = difficulty === 'beginner' ? 5 : difficulty === 'intermediate' ? 8 : 12; const selectedResults = searchResults.slice(0, topicCount); // Generate study plan structure with real data const studyPlan: StudyPlan = { id: `plan-${Date.now()}`, title: `${topic} Study Plan`, description: `Comprehensive ${difficulty} level study plan for ${topic} using real Wikimedia content`, difficulty, estimatedTime: this.estimateStudyTime(selectedResults.length, difficulty), created: new Date().toISOString(), topics: await this.generateTopicsFromResults(selectedResults, difficulty) }; return studyPlan; } catch (error) { console.error('Failed to generate study plan:', error); throw error; } } private static async generateTopicsFromResults(results: SearchResult[], difficulty: string): Promise { const topics = []; for (let i = 0; i < results.length; i++) { const result = results[i]; // Get more detailed content for each topic let detailedContent = result.snippet; try { const fullContent = await this.getPageSnippet(result.title, result.project); if (fullContent && fullContent !== 'No description available') { detailedContent = fullContent; } } catch (error) { console.log(`Could not get detailed content for ${result.title}`); } topics.push({ id: `topic-${Date.now()}-${i}`, title: result.title, description: detailedContent.replace(/<[^>]*>/g, '').substring(0, 200) + '...', content: detailedContent, completed: false, estimatedTime: `${Math.ceil(Math.random() * 2 + 1)} hours`, resources: [ { title: result.title, url: result.url, type: 'article' as const, project: result.project } ] }); } return topics; } private static buildProjectUrl(project: string, title: string): string { const baseUrls: Record = { wikipedia: 'https://en.wikipedia.org/wiki/', wikibooks: 'https://en.wikibooks.org/wiki/', wikiquote: 'https://en.wikiquote.org/wiki/', wikiversity: 'https://en.wikiversity.org/wiki/', wiktionary: 'https://en.wiktionary.org/wiki/', wikisource: 'https://en.wikisource.org/wiki/', }; const baseUrl = baseUrls[project] || baseUrls.wikipedia; return baseUrl + encodeURIComponent(title.replace(/ /g, '_')); } private static estimateStudyTime(contentCount: number, difficulty: string): string { const baseHours = contentCount * 1.5; // More realistic time estimate const multiplier = difficulty === 'beginner' ? 1 : difficulty === 'intermediate' ? 1.5 : 2; const totalHours = Math.ceil(baseHours * multiplier); if (totalHours < 24) { return `${totalHours} hours`; } else { const weeks = Math.ceil(totalHours / 10); // Assuming 10 hours per week return `${weeks} weeks`; } } // Real-time progress tracking methods static getStoredProgress(): any { try { const stored = localStorage.getItem('wikistro-progress'); return stored ? JSON.parse(stored) : this.getDefaultProgress(); } catch (error) { return this.getDefaultProgress(); } } static saveProgress(progressData: any): void { try { localStorage.setItem('wikistro-progress', JSON.stringify(progressData)); } catch (error) { console.error('Failed to save progress:', error); } } static getStoredStudyPlans(): StudyPlan[] { try { const stored = localStorage.getItem('wikistro-study-plans'); return stored ? JSON.parse(stored) : []; } catch (error) { return []; } } static saveStudyPlans(plans: StudyPlan[]): void { try { localStorage.setItem('wikistro-study-plans', JSON.stringify(plans)); } catch (error) { console.error('Failed to save study plans:', error); } } private static getDefaultProgress() { return { studyStreak: 0, topicsCompleted: 0, totalStudyTime: 0, achievements: 0, weeklyGoal: { current: 0, target: 12 }, recentActivity: [], completedTopics: [], lastStudyDate: null }; } }