// RapidAPI Complete Study Bible Integration Module // This module provides functions to query the Complete Study Bible API via RapidAPI // for enhanced word information to display in popup cards /** * Extract Strong's numbers from search results */ function extractStrongsFromSearchResult(searchResults) { const strongsSet = new Set(); if (!Array.isArray(searchResults)) return []; for (const result of searchResults) { // Look for Strong's fields in various formats if (result.strongs) { const strongs = String(result.strongs).match(/([GH]\d+)/gi); if (strongs) strongs.forEach(s => strongsSet.add(s.toUpperCase())); } if (result.strongs_greek) { const strongs = String(result.strongs_greek).match(/(G\d+)/gi); if (strongs) strongs.forEach(s => strongsSet.add(s.toUpperCase())); } if (result.strongs_hebrew) { const strongs = String(result.strongs_hebrew).match(/(H\d+)/gi); if (strongs) strongs.forEach(s => strongsSet.add(s.toUpperCase())); } // Only scan fields whose key indicates Strong's data. Scanning ALL string fields produced // false positives (e.g. 'G20' from verse text, or 'H2' harvested from 'H2O') that were then // sent to the strongs-detail endpoint and surfaced as unrelated lexical entries. for (const [key, value] of Object.entries(result)) { if (typeof value === 'string' && /strong/i.test(key)) { const matches = value.match(/([GH]\d+)/gi); if (matches) matches.forEach(m => strongsSet.add(m.toUpperCase())); } } } return Array.from(strongsSet); } /** * Query RapidAPI Complete Study Bible for word information * @param {string} word - The word to look up * @param {string} reference - Optional Bible reference (e.g., "John 3:16") * @returns {Promise} Enhanced word information from RapidAPI */ export async function queryRapidAPIWord(word, reference = null) { try { // Get API key from localStorage (user needs to set this) const apiKey = typeof localStorage !== 'undefined' ? localStorage.getItem('rapidapi.key') : null; if (!apiKey) { console.warn('[RapidAPI] No API key found. Set localStorage.setItem("rapidapi.key", "your-key")'); return { success: false, error: 'API key not configured', word: word, reference: reference }; } // Get API host from localStorage (defaults to the Complete Study Bible API) const apiHost = typeof localStorage !== 'undefined' ? localStorage.getItem('rapidapi.host') || 'complete-study-bible.p.rapidapi.com' : 'complete-study-bible.p.rapidapi.com'; console.log('[RapidAPI] Querying word:', word, 'reference:', reference); console.log('[RapidAPI] API Host:', apiHost); console.log('[RapidAPI] API Key:', apiKey ? apiKey.substring(0, 10) + '...' : 'NOT SET'); // Try endpoints based on what we found in the API // Priority: word search -> get Strong's -> get Strong's details const normalizedWord = word.toLowerCase().replace(/\s+/g, ''); // Remove spaces for search-exact const endpoints = [ // Word search endpoints (found in API) `/search/${encodeURIComponent(word)}/`, // Search for word `/search-exact/${encodeURIComponent(normalizedWord)}/`, // Exact word search `/search/${encodeURIComponent(normalizedWord)}/`, // Search without spaces // Fallback patterns `/word/${encodeURIComponent(word.toLowerCase())}`, `/words/${encodeURIComponent(word.toLowerCase())}`, `/search?word=${encodeURIComponent(word.toLowerCase())}` ]; for (const endpoint of endpoints) { try { const url = `https://${apiHost}${endpoint}`; console.log('[RapidAPI] Trying endpoint:', url); const response = await fetch(url, { method: 'GET', headers: { 'X-RapidAPI-Key': apiKey, 'X-RapidAPI-Host': apiHost, 'Content-Type': 'application/json' } }); console.log('[RapidAPI] Response status:', response.status, 'for endpoint:', endpoint); if (response.ok) { const data = await response.json(); console.log('[RapidAPI] ✅ SUCCESS! Response data:', data); // If this is a search result that might contain Strong's numbers, try to get details let finalData = data; if (Array.isArray(data) && data.length > 0) { // Search result - try to extract Strong's numbers and get details const strongsNumbers = extractStrongsFromSearchResult(data); if (strongsNumbers.length > 0) { console.log('[RapidAPI] Found Strong\'s numbers:', strongsNumbers); // Get Strong's details const detailsEndpoint = `/strongs-detail/${strongsNumbers.join(',')}/`; try { const detailsUrl = `https://${apiHost}${detailsEndpoint}`; const detailsResponse = await fetch(detailsUrl, { method: 'GET', headers: { 'X-RapidAPI-Key': apiKey, 'X-RapidAPI-Host': apiHost, 'Content-Type': 'application/json' } }); if (detailsResponse.ok) { const detailsData = await detailsResponse.json(); console.log('[RapidAPI] ✅ Got Strong\'s details:', detailsData); finalData = { searchResults: data, strongsDetails: detailsData }; } } catch (err) { console.log('[RapidAPI] Could not fetch Strong\'s details:', err); } } } return { success: true, data: finalData, word: word, reference: reference, endpoint: endpoint }; } else { const errorText = await response.text(); console.log('[RapidAPI] ❌ Failed:', response.status, errorText.substring(0, 200)); // If it's a 401/403, stop trying - it's an auth issue if (response.status === 401 || response.status === 403) { return { success: false, error: `Authentication failed (${response.status}). Check your API key.`, word: word, reference: reference }; } // Continue to next endpoint } } catch (error) { console.log('[RapidAPI] Error trying endpoint:', endpoint, error.message); // Continue to next endpoint } } // If we get here, all endpoints failed return { success: false, error: 'No working endpoint found. Check RapidAPI playground for correct endpoint structure.', word: word, reference: reference }; } catch (error) { console.warn('[RapidAPI] Failed to query word:', word, error); return { success: false, error: error.message || 'Network error', word: word, reference: reference }; } } // Removed - now trying all endpoints in main function /** * Get current Bible reference from active tab * @returns {string|null} Current reference or null */ function getCurrentReference() { try { const activeTab = window.bibleApp?.tabs?.find(t => t.id === window.bibleApp?.activeTabId); if (!activeTab) return null; const book = activeTab.book || ''; const chapter = activeTab.chapter || ''; const verse = activeTab.verse || ''; if (book && chapter) { return verse ? `${book} ${chapter}:${verse}` : `${book} ${chapter}`; } return null; } catch (_) { return null; } } /** * Parse RapidAPI response into structured word info format * This function adapts the API response to match your popup structure * Adjust based on actual API response format */ export function parseRapidAPIResponse(apiResponse, word) { try { console.log('[RapidAPI] Parsing response:', apiResponse); const data = apiResponse.data || apiResponse; console.log('[RapidAPI] Data structure:', Array.isArray(data) ? `Array[${data.length}]` : Object.keys(data || {})); // Extract information based on common API response structures // Adjust these mappings based on actual API response format const result = { greek: null, hebrew: null, strongs: null, definition: null, transliteration: null, commentaries: [], crossReferences: [], usage: null, morphology: null }; // Handle Strong's details response (most useful!) if (data.strongsDetails || (Array.isArray(data) && data.length > 0 && (data[0].strongs || data[0].number || data[0].greek || data[0].hebrew))) { const strongsData = data.strongsDetails || data; const strongsArray = Array.isArray(strongsData) ? strongsData : [strongsData]; for (const item of strongsArray) { // Extract Strong's number - try many field names if (!result.strongs) { const strongs = item.strongs || item.number || item.strongs_number || item.strong || item.id; if (strongs) { result.strongs = String(strongs).toUpperCase(); } } // Extract definition - try many field names if (!result.definition) { const def = item.definition || item.meaning || item.gloss || item.translation || item.english || item.text || item.description; if (def) { result.definition = String(def); } } // Extract Greek - try many field names and patterns if (!result.greek || !result.greek.text || result.greek.text === '—') { const greekText = item.greek || item.greek_text || item.original_greek || item.greek_word || item.greek_lemma || item.lemma || item.word_greek; const greekTranslit = item.greek_transliteration || item.transliteration || item.translit || item.greek_translit || item.greek_trans; if (greekText) { result.greek = { text: String(greekText), transliteration: greekTranslit ? String(greekTranslit) : '', meaning: result.definition || String(item.definition || item.meaning || '') }; } } // Extract Hebrew - try many field names if (!result.hebrew || !result.hebrew.text || result.hebrew.text === '—') { const hebrewText = item.hebrew || item.hebrew_text || item.original_hebrew || item.hebrew_word || item.hebrew_lemma || item.word_hebrew; const hebrewTranslit = item.hebrew_transliteration || item.transliteration || item.translit || item.hebrew_translit || item.hebrew_trans; if (hebrewText) { result.hebrew = { text: String(hebrewText), transliteration: hebrewTranslit ? String(hebrewTranslit) : '', meaning: result.definition || String(item.definition || item.meaning || '') }; } } // Extract transliteration if (!result.transliteration) { const translit = item.transliteration || item.translit || item.trans; if (translit) { result.transliteration = String(translit); } } // Extract morphology if (!result.morphology) { const morph = item.morphology || item.grammar || item.morph || item.grammatical; if (morph) { result.morphology = String(morph); } } } // Also handle search results if present - calculate OT/NT usage if (data.searchResults && Array.isArray(data.searchResults)) { const OT_BOOKS = new Set(['Genesis','Exodus','Leviticus','Numbers','Deuteronomy','Joshua','Judges','Ruth','1 Samuel','2 Samuel','1 Kings','2 Kings','1 Chronicles','2 Chronicles','Ezra','Nehemiah','Esther','Job','Psalms','Proverbs','Ecclesiastes','Song of Solomon','Isaiah','Jeremiah','Lamentations','Ezekiel','Daniel','Hosea','Joel','Amos','Obadiah','Jonah','Micah','Nahum','Habakkuk','Zephaniah','Haggai','Zechariah','Malachi']); const NT_BOOKS = new Set(['Matthew','Mark','Luke','John','Acts','Romans','1 Corinthians','2 Corinthians','Galatians','Ephesians','Philippians','Colossians','1 Thessalonians','2 Thessalonians','1 Timothy','2 Timothy','Titus','Philemon','Hebrews','James','1 Peter','2 Peter','1 John','2 John','3 John','Jude','Revelation']); let otCount = 0; let ntCount = 0; result.crossReferences = data.searchResults.map(verse => { const book = verse.book__name || verse.book__abbrev || ''; if (OT_BOOKS.has(book)) otCount++; if (NT_BOOKS.has(book)) ntCount++; const chapter = verse.chapter || ''; const verseNum = verse.verse || ''; return `${book} ${chapter}:${verseNum}`; }); result.usage = `Found in ${data.searchResults.length} verse(s)`; result.usageOT = otCount; result.usageNT = ntCount; result.usageTotal = data.searchResults.length; } console.log('[RapidAPI] Parsed Strong\'s details:', result); return result; } // Handle array responses (like verse search results) if (Array.isArray(data) && data.length > 0) { // This looks like a verse search result - extract cross-references and calculate OT/NT usage const OT_BOOKS = new Set(['Genesis','Exodus','Leviticus','Numbers','Deuteronomy','Joshua','Judges','Ruth','1 Samuel','2 Samuel','1 Kings','2 Kings','1 Chronicles','2 Chronicles','Ezra','Nehemiah','Esther','Job','Psalms','Proverbs','Ecclesiastes','Song of Solomon','Isaiah','Jeremiah','Lamentations','Ezekiel','Daniel','Hosea','Joel','Amos','Obadiah','Jonah','Micah','Nahum','Habakkuk','Zephaniah','Haggai','Zechariah','Malachi']); const NT_BOOKS = new Set(['Matthew','Mark','Luke','John','Acts','Romans','1 Corinthians','2 Corinthians','Galatians','Ephesians','Philippians','Colossians','1 Thessalonians','2 Thessalonians','1 Timothy','2 Timothy','Titus','Philemon','Hebrews','James','1 Peter','2 Peter','1 John','2 John','3 John','Jude','Revelation']); let otCount = 0; let ntCount = 0; result.crossReferences = data.map(verse => { const book = verse.book__name || verse.book__abbrev || ''; if (OT_BOOKS.has(book)) otCount++; if (NT_BOOKS.has(book)) ntCount++; const chapter = verse.chapter || ''; const verseNum = verse.verse || ''; return `${book} ${chapter}:${verseNum}`; }); result.usage = `Found in ${data.length} verse(s)`; result.usageOT = otCount; result.usageNT = ntCount; result.usageTotal = data.length; // Try to extract Strong's from the endpoint if available if (apiResponse.endpoint && apiResponse.endpoint.includes('strongs/')) { const match = apiResponse.endpoint.match(/strongs\/([GH]\d+)/i); if (match) { result.strongs = match[1].toUpperCase(); } } console.log('[RapidAPI] Parsed verse array:', result); return result; } // Try to extract ANY text that might be useful // Check for nested structures const checkNested = (obj, paths) => { for (const path of paths) { const keys = path.split('.'); let value = obj; for (const key of keys) { if (value && typeof value === 'object' && key in value) { value = value[key]; } else { value = null; break; } } if (value) return value; } return null; }; // Extract definition - try many possible paths const definition = checkNested(data, [ 'definition', 'meaning', 'gloss', 'text', 'description', 'data.definition', 'result.definition', 'word.definition', 'definitions[0]', 'definitions.0' ]); if (definition) result.definition = String(definition); // Extract Strong's numbers const strongs = checkNested(data, [ 'strongs', 'strongsNumber', 'strong', 'strongNumber', 'data.strongs', 'result.strongs' ]); if (strongs) result.strongs = String(strongs); // Extract Greek const greekText = checkNested(data, [ 'greek', 'greekText', 'originalGreek', 'greek.word', 'greek.text' ]); if (greekText) { result.greek = { text: String(greekText), transliteration: checkNested(data, ['greekTransliteration', 'greek.transliteration']) || '', meaning: result.definition || '' }; } // Extract Hebrew const hebrewText = checkNested(data, [ 'hebrew', 'hebrewText', 'originalHebrew', 'hebrew.word', 'hebrew.text' ]); if (hebrewText) { result.hebrew = { text: String(hebrewText), transliteration: checkNested(data, ['hebrewTransliteration', 'hebrew.transliteration']) || '', meaning: result.definition || '' }; } // Extract transliteration const transliteration = checkNested(data, [ 'transliteration', 'translit', 'data.transliteration' ]); if (transliteration) result.transliteration = String(transliteration); // Extract commentaries const commentaries = checkNested(data, [ 'commentaries', 'commentary', 'comments', 'data.commentaries' ]); if (Array.isArray(commentaries)) { result.commentaries = commentaries.map(c => String(c)); } else if (commentaries) { result.commentaries = [String(commentaries)]; } // Extract cross-references const crossRefs = checkNested(data, [ 'crossReferences', 'references', 'crossRefs', 'xrefs' ]); if (Array.isArray(crossRefs)) { result.crossReferences = crossRefs.map(r => String(r)); } else if (crossRefs) { result.crossReferences = [String(crossRefs)]; } // Extract usage const usage = checkNested(data, [ 'usage', 'usageCount', 'frequency', 'count' ]); if (usage) { result.usage = typeof usage === 'number' ? `Used ${usage} times` : String(usage); } // Extract morphology const morphology = checkNested(data, [ 'morphology', 'grammar', 'morph', 'grammatical' ]); if (morphology) result.morphology = String(morphology); // If we got ANY data, return it const hasData = result.definition || result.strongs || result.greek || result.hebrew || result.commentaries.length > 0 || result.crossReferences.length > 0; if (hasData) { console.log('[RapidAPI] ✅ Parsed data:', result); return result; } else { console.log('[RapidAPI] ⚠️ No recognizable data found in response'); // Return raw data so user can see what we got return { definition: `Raw API Response: ${JSON.stringify(data).substring(0, 200)}...`, rawData: data }; } } catch (error) { console.warn('[RapidAPI] Error parsing response:', error); return null; } } /** * Enhance word info object with RapidAPI Complete Study Bible data * @param {Object} wordInfo - Base word info object * @param {string} word - The word being looked up * @param {string} reference - Optional Bible reference * @returns {Promise} Enhanced word info with RapidAPI data */ export async function enhanceWordInfoWithRapidAPI(wordInfo, word, reference = null) { try { console.log('[RapidAPI] Enhancing word info for:', word, 'reference:', reference); const rapidAPIData = await queryRapidAPIWord(word, reference); console.log('[RapidAPI] Received data:', rapidAPIData); // Merge RapidAPI data into word info const enhanced = { ...wordInfo }; if (rapidAPIData.success && rapidAPIData.data) { const parsed = parseRapidAPIResponse(rapidAPIData, word); if (parsed) { // CRITICAL: Only enhance if we have Greek definition (user requirement) const hasGreekDefinition = parsed.greek && parsed.greek.text && parsed.greek.text !== '—' && parsed.greek.text.trim().length > 0; if (hasGreekDefinition) { // Enhance Greek data - this is required! enhanced.greek = { text: parsed.greek.text, transliteration: parsed.greek.transliteration || '', meaning: parsed.greek.meaning || parsed.definition || '' }; // Mark as having valid data enhanced.hasValidData = true; } // Enhance Hebrew data if available (optional) if (parsed.hebrew && parsed.hebrew.text && parsed.hebrew.text !== '—') { enhanced.hebrew = { text: parsed.hebrew.text, transliteration: parsed.hebrew.transliteration || '', meaning: parsed.hebrew.meaning || parsed.definition || '' }; } // Update usage counts from RapidAPI search results if (parsed.usageOT !== undefined) { enhanced.frequencyOT = parsed.usageOT; } if (parsed.usageNT !== undefined) { enhanced.frequencyNT = parsed.usageNT; } if (parsed.usageTotal !== undefined) { enhanced.frequency = parsed.usageTotal; } // Add RapidAPI section with additional data enhanced.rapidAPI = { available: true, hasGreekDefinition: hasGreekDefinition, strongs: parsed.strongs || null, definition: parsed.definition || null, transliteration: parsed.transliteration || null, commentaries: parsed.commentaries || [], crossReferences: parsed.crossReferences || [], usage: parsed.usage || null, usageOT: parsed.usageOT || null, usageNT: parsed.usageNT || null, usageTotal: parsed.usageTotal || null, morphology: parsed.morphology || null }; } else { enhanced.rapidAPI = { available: true, error: 'Could not parse API response', rawData: rapidAPIData.data }; } } else { // API call failed or returned error enhanced.rapidAPI = { available: false, error: rapidAPIData.error || 'Unknown error', note: 'RapidAPI Complete Study Bible integration is configured but returned no data' }; } return enhanced; } catch (error) { console.warn('[RapidAPI] Error enhancing word info:', error); return { ...wordInfo, rapidAPI: { available: false, error: error.message || 'Unknown error' } }; } } /** * Check if RapidAPI is available and configured * @returns {Promise} True if API is available */ export async function checkRapidAPIAvailability() { try { const apiKey = typeof localStorage !== 'undefined' ? localStorage.getItem('rapidapi.key') : null; if (!apiKey) { console.log('[RapidAPI] Not configured - no API key found'); return false; } // Try a simple test query const testResponse = await queryRapidAPIWord('test'); return testResponse.success === true; } catch (error) { console.warn('[RapidAPI] Availability check failed:', error); return false; } } // Make functions globally available for testing if (typeof window !== 'undefined') { window.queryRapidAPIWord = queryRapidAPIWord; window.enhanceWordInfoWithRapidAPI = enhanceWordInfoWithRapidAPI; window.checkRapidAPIAvailability = checkRapidAPIAvailability; // Simple test function window.testRapidAPIDirect = async function(word = 'love') { console.log('🧪 Testing RapidAPI directly for word:', word); console.log('🧪 API Key:', localStorage.getItem('rapidapi.key') ? 'SET' : 'NOT SET'); console.log('🧪 API Host:', localStorage.getItem('rapidapi.host') || 'complete-study-bible.p.rapidapi.com'); const result = await queryRapidAPIWord(word); console.log('🧪 Result:', result); if (result.success) { console.log('✅ API call succeeded!'); console.log('📦 Response data:', result.data); } else { console.log('❌ API call failed:', result.error); console.log('💡 TIP: Check the RapidAPI playground to find the correct endpoint structure'); console.log('💡 TIP: Make sure you\'re subscribed to the API on RapidAPI'); } return result; }; // Test a custom endpoint (once you find it in the playground) window.testRapidAPICustomEndpoint = async function(endpoint, word = 'love') { const apiKey = localStorage.getItem('rapidapi.key'); const apiHost = localStorage.getItem('rapidapi.host') || 'complete-study-bible.p.rapidapi.com'; if (!apiKey) { console.error('❌ API key not set!'); return; } // Replace {word} or similar placeholders const url = `https://${apiHost}${endpoint.replace('{word}', word).replace('{WORD}', word)}`; console.log('🧪 Testing custom endpoint:', url); try { const response = await fetch(url, { method: 'GET', headers: { 'X-RapidAPI-Key': apiKey, 'X-RapidAPI-Host': apiHost, 'Content-Type': 'application/json' } }); console.log('📊 Response status:', response.status); if (response.ok) { const data = await response.json(); console.log('✅ SUCCESS! Response:', data); return { success: true, data }; } else { const errorText = await response.text(); console.log('❌ Failed:', response.status, errorText.substring(0, 200)); return { success: false, error: `HTTP ${response.status}` }; } } catch (error) { console.error('❌ Error:', error); return { success: false, error: error.message }; } }; }