diff --git a/assets/js/AdminBoard.js b/assets/js/AdminBoard.js index 26b97e9..1c5f71e 100644 --- a/assets/js/AdminBoard.js +++ b/assets/js/AdminBoard.js @@ -173,9 +173,10 @@ const extractCardsMinterName = (cardIdentifier) => { if (parts.length < 3) { throw new Error('Invalid identifier format'); } + if (parts.slice(2, -1).join('-') === 'TOPIC') { console.log(`TOPIC found in identifier: ${cardIdentifier} - not including in duplicatesList`) - return + return 'topic' } // Extract minterName (everything from the second part to the second-to-last part) const minterName = parts.slice(2, -1).join('-'); @@ -186,73 +187,39 @@ const extractCardsMinterName = (cardIdentifier) => { const processCards = async (validEncryptedCards) => { const latestCardsMap = new Map() - // Step 1: Filter and keep the most recent card per identifier - validEncryptedCards.forEach(card => { + // Step 1: Process all cards in parallel + await Promise.all(validEncryptedCards.map(async card => { const timestamp = card.updated || card.created || 0 const existingCard = latestCardsMap.get(card.identifier) if (!existingCard || timestamp > (existingCard.updated || existingCard.created || 0)) { latestCardsMap.set(card.identifier, card) } - }) + })) + + console.log(`latestCardsMap, by timestamp`, latestCardsMap) // Step 2: Extract unique cards const uniqueValidCards = Array.from(latestCardsMap.values()) - // Step 3: Group by minterName and select the most recent card per minterName - const minterNameMap = new Map() - - for (const card of validEncryptedCards) { - const minterName = await extractCardsMinterName(card.identifier) - const existingCard = minterNameMap.get(minterName) - const cardTimestamp = card.updated || card.created || 0 - const existingTimestamp = existingCard?.updated || existingCard?.created || 0 - - if (!existingCardMinterNames.includes(minterName)) { - existingCardMinterNames.push(minterName) - console.log(`cardsMinterName: ${minterName} - added to list`) - } - - // Keep only the most recent card for each minterName - if (!existingCard || cardTimestamp > existingTimestamp) { - minterNameMap.set(minterName, card) - } - } - - // Step 4: Filter cards to ensure each minterName is included only once - const finalCards = [] - const seenMinterNames = new Set() - - for (const [minterName, card] of minterNameMap.entries()) { - if (!seenMinterNames.has(minterName)) { - finalCards.push(card) - seenMinterNames.add(minterName) // Mark the minterName as seen - } - } - - // Step 5: Sort by the most recent timestamp - finalCards.sort((a, b) => { - const timestampA = a.updated || a.created || 0 - const timestampB = b.updated || b.created || 0 - return timestampB - timestampA - }) - - return finalCards + return uniqueValidCards } + //Main function to load the Minter Cards ---------------------------------------- const fetchAllEncryptedCards = async () => { const encryptedCardsContainer = document.getElementById("encrypted-cards-container"); encryptedCardsContainer.innerHTML = "
Loading cards...
"; try { - const response = await qortalRequest({ - action: "SEARCH_QDN_RESOURCES", - service: "MAIL_PRIVATE", - query: encryptedCardIdentifierPrefix, - mode: "ALL" - }); + // const response = await qortalRequest({ + // action: "SEARCH_QDN_RESOURCES", + // service: "MAIL_PRIVATE", + // query: encryptedCardIdentifierPrefix, + // mode: "ALL" + // }) + const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0) if (!response || !Array.isArray(response) || response.length === 0) { encryptedCardsContainer.innerHTML = "No cards found.
"; @@ -262,19 +229,21 @@ const fetchAllEncryptedCards = async () => { // Validate cards and filter const validatedEncryptedCards = await Promise.all( response.map(async card => { - const isValid = await validateEncryptedCardIdentifier(card); - return isValid ? card : null; + const isValid = await validateEncryptedCardIdentifier(card) + return isValid ? card : null }) - ); + ) + console.log(`validatedEncryptedCards:`, validatedEncryptedCards, `... running next filter...`) const validEncryptedCards = validatedEncryptedCards.filter(card => card !== null); + console.log(`validEncryptedcards:`, validEncryptedCards) if (validEncryptedCards.length === 0) { encryptedCardsContainer.innerHTML = "No valid cards found.
"; return; } const finalCards = await processCards(validEncryptedCards) - + console.log(`finalCards:`,finalCards) // Display skeleton cards immediately encryptedCardsContainer.innerHTML = ""; finalCards.forEach(card => { @@ -371,14 +340,16 @@ const createEncryptedSkeletonCardHTML = (cardIdentifier) => { // Function to check and fech an existing Minter Card if attempting to publish twice ---------------------------------------- const fetchExistingEncryptedCard = async (minterName) => { try { - // Step 1: Perform the search - const response = await qortalRequest({ - action: "SEARCH_QDN_RESOURCES", - service: "MAIL_PRIVATE", - identifier: encryptedCardIdentifierPrefix, - query: minterName, - mode: "ALL", - }); + // const response = await qortalRequest({ + // action: "SEARCH_QDN_RESOURCES", + // service: "MAIL_PRIVATE", + // identifier: encryptedCardIdentifierPrefix, + // query: minterName, + // mode: "ALL", + // }) + + //CHANGED to searchSimple to speed up search results. + const response = await searchSimple('MAIL_PRIVATE', `${encryptedCardIdentifierPrefix}`, '', 0) console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`); @@ -598,17 +569,18 @@ const publishEncryptedCard = async (isTopicModePassed = false) => { const getEncryptedCommentCount = async (cardIdentifier) => { try { - const response = await qortalRequest({ - action: 'SEARCH_QDN_RESOURCES', - service: 'MAIL_PRIVATE', - query: `comment-${cardIdentifier}`, - mode: "ALL" - }); + // const response = await qortalRequest({ + // action: 'SEARCH_QDN_RESOURCES', + // service: 'MAIL_PRIVATE', + // query: `comment-${cardIdentifier}`, + // mode: "ALL" + // }) + const response = await searchSimple('MAIL_PRIVATE', `comment-${cardIdentifier}`, '', 0) // Just return the count; no need to decrypt each comment here - return Array.isArray(response) ? response.length : 0; + return Array.isArray(response) ? response.length : 0 } catch (error) { - console.error(`Error fetching comment count for ${cardIdentifier}:`, error); - return 0; + console.error(`Error fetching comment count for ${cardIdentifier}:`, error) + return 0 } }; @@ -666,14 +638,17 @@ const postEncryptedComment = async (cardIdentifier) => { //Fetch the comments for a card with passed card identifier ---------------------------- const fetchEncryptedComments = async (cardIdentifier) => { try { - const response = await qortalRequest({ - action: 'SEARCH_QDN_RESOURCES', - service: 'MAIL_PRIVATE', - query: `comment-${cardIdentifier}`, - mode: "ALL" - }); + // const response = await qortalRequest({ + // action: 'SEARCH_QDN_RESOURCES', + // service: 'MAIL_PRIVATE', + // query: `comment-${cardIdentifier}`, + // mode: "ALL" + // }) + const response = await searchSimple('MAIL_PRIVATE', `comment-${cardIdentifier}`, '', 0) - return response; + if (response) { + return response; + } } catch (error) { console.error(`Error fetching comments for ${cardIdentifier}:`, error); return []; @@ -683,7 +658,9 @@ const fetchEncryptedComments = async (cardIdentifier) => { // display the comments on the card, with passed cardIdentifier to identify the card -------------- const displayEncryptedComments = async (cardIdentifier) => { try { + const comments = await fetchEncryptedComments(cardIdentifier); + const commentsContainer = document.getElementById(`comments-container-${cardIdentifier}`); // Fetch and display each comment @@ -712,8 +689,8 @@ const displayEncryptedComments = async (cardIdentifier) => { commentsContainer.insertAdjacentHTML('beforeend', commentHTML); } } catch (error) { - console.error(`Error displaying comments for ${cardIdentifier}:`, error); - alert("Failed to load comments. Please try again."); + console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error); + } }; @@ -757,7 +734,7 @@ const calculateAdminBoardPollResults = async (pollData, minterGroupMembers, mint // 4) Process votes for (const vote of pollData.votes) { const voterAddress = await getAddressFromPublicKey(vote.voterPublicKey); - console.log(`voter address: ${voterAddress}, optionIndex: ${vote.optionIndex}`); + // console.log(`voter address: ${voterAddress}, optionIndex: ${vote.optionIndex}`); if (vote.optionIndex === 0) { if (adminAddresses.includes(voterAddress)) { diff --git a/assets/js/MinterBoard.js b/assets/js/MinterBoard.js index d235acd..7133459 100644 --- a/assets/js/MinterBoard.js +++ b/assets/js/MinterBoard.js @@ -120,17 +120,17 @@ const loadMinterBoardPage = async () => { const extractMinterCardsMinterName = async (cardIdentifier) => { // Ensure the identifier starts with the prefix if (!cardIdentifier.startsWith(`${cardIdentifierPrefix}-`)) { - throw new Error('Invalid identifier format or prefix mismatch'); + throw new Error('Invalid identifier format or prefix mismatch') } // Split the identifier into parts - const parts = cardIdentifier.split('-'); + const parts = cardIdentifier.split('-') // Ensure the format has at least 3 parts if (parts.length < 3) { - throw new Error('Invalid identifier format'); + throw new Error('Invalid identifier format') } try { - const nameFromIdentifier = await searchSimple('BLOG_POST', cardIdentifier, "", 1) - const minterName = await nameFromIdentifier.name + const searchSimpleResults = await searchSimple('BLOG_POST', `${cardIdentifier}`, '', 1) + const minterName = await searchSimpleResults.name return minterName } catch (error) { throw error @@ -195,12 +195,14 @@ const loadCards = async () => { cardsContainer.innerHTML = "Loading cards...
"; try { - const response = await qortalRequest({ - action: "SEARCH_QDN_RESOURCES", - service: "BLOG_POST", - query: cardIdentifierPrefix, - mode: "ALL" - }); + // const response = await qortalRequest({ + // action: "SEARCH_QDN_RESOURCES", + // service: "BLOG_POST", + // query: cardIdentifierPrefix, + // mode: "ALL" + // }) + + const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, '' , 0) if (!response || !Array.isArray(response) || response.length === 0) { cardsContainer.innerHTML = "No cards found.
"; @@ -320,14 +322,16 @@ const createSkeletonCardHTML = (cardIdentifier) => { const fetchExistingCard = async () => { try { // Step 1: Perform the search - const response = await qortalRequest({ - action: "SEARCH_QDN_RESOURCES", - service: "BLOG_POST", - identifier: cardIdentifierPrefix, - name: userState.accountName, - mode: "ALL", - exactMatchNames: true // Search for the exact userName only when finding existing cards - }); + // const response = await qortalRequest({ + // action: "SEARCH_QDN_RESOURCES", + // service: "BLOG_POST", + // identifier: cardIdentifierPrefix, + // name: userState.accountName, + // mode: "ALL", + // exactMatchNames: true // Search for the exact userName only when finding existing cards + // }) + // Changed to searchSimple to improve load times. + const response = await searchSimple('BLOG_POST', `${cardIdentifierPrefix}`, `${userState.accountName}`, 0) console.log(`SEARCH_QDN_RESOURCES response: ${JSON.stringify(response, null, 2)}`); @@ -335,9 +339,11 @@ const fetchExistingCard = async () => { if (!response || !Array.isArray(response) || response.length === 0) { console.log("No cards found for the current user."); return null; + } else if (response.length === 1) { // we don't need to go through all of the rest of the checks and filtering nonsense if there's only a single result, just return it. + return response[0] } - // Step 3: Validate cards asynchronously + // Validate cards asynchronously, check that they are not comments, etc. const validatedCards = await Promise.all( response.map(async card => { const isValid = await validateCardStructure(card); @@ -345,14 +351,14 @@ const fetchExistingCard = async () => { }) ); - // Step 4: Filter out invalid cards + // Filter out invalid cards const validCards = validatedCards.filter(card => card !== null); if (validCards.length > 0) { - // Step 5: Sort by most recent timestamp + // Sort by most recent timestamp const mostRecentCard = validCards.sort((a, b) => b.created - a.created)[0]; - // Step 6: Fetch full card data + // Fetch full card data const cardDataResponse = await qortalRequest({ action: "FETCH_QDN_RESOURCE", name: userState.accountName, // User's account name @@ -490,7 +496,7 @@ const calculatePollResults = async (pollData, minterGroupMembers, minterAdmins) for (const vote of pollData.votes) { const voterAddress = await getAddressFromPublicKey(vote.voterPublicKey) - console.log(`voter address: ${voterAddress}`) + // console.log(`voter address: ${voterAddress}`) if (vote.optionIndex === 0) { adminAddresses.includes(voterAddress) ? adminYes++ : memberAddresses.includes(voterAddress) ? minterYes++ : console.log(`voter ${voterAddress} is not a minter nor an admin...Not including results...`) @@ -555,18 +561,19 @@ const postComment = async (cardIdentifier) => { //Fetch the comments for a card with passed card identifier ---------------------------- const fetchCommentsForCard = async (cardIdentifier) => { try { - const response = await qortalRequest({ - action: 'SEARCH_QDN_RESOURCES', - service: 'BLOG_POST', - query: `comment-${cardIdentifier}`, - mode: "ALL" - }); - return response; + // const response = await qortalRequest({ + // action: 'SEARCH_QDN_RESOURCES', + // service: 'BLOG_POST', + // query: `comment-${cardIdentifier}`, + // mode: "ALL" + // }) + const response = await searchSimple('BLOG_POST',`comment-${cardIdentifier}`, '', 0) + return response } catch (error) { - console.error(`Error fetching comments for ${cardIdentifier}:`, error); - return []; + console.error(`Error fetching comments for ${cardIdentifier}:`, error) + return [] } -}; +} // display the comments on the card, with passed cardIdentifier to identify the card -------------- const displayComments = async (cardIdentifier) => { @@ -594,8 +601,7 @@ const displayComments = async (cardIdentifier) => { commentsContainer.insertAdjacentHTML('beforeend', commentHTML); } } catch (error) { - console.error(`Error displaying comments for ${cardIdentifier}:`, error); - alert("Failed to load comments. Please try again."); + console.error(`Error displaying comments (or no comments) for ${cardIdentifier}:`, error); } }; @@ -624,12 +630,14 @@ const toggleComments = async (cardIdentifier) => { const countComments = async (cardIdentifier) => { try { - const response = await qortalRequest({ - action: 'SEARCH_QDN_RESOURCES', - service: 'BLOG_POST', - query: `comment-${cardIdentifier}`, - mode: "ALL" - }); + // const response = await qortalRequest({ + // action: 'SEARCH_QDN_RESOURCES', + // service: 'BLOG_POST', + // query: `comment-${cardIdentifier}`, + // mode: "ALL" + // }) + // Changed to searchSimple to hopefully improve load times... + const response = await searchSimple('BLOG_POST', `comment-${cardIdentifier}`, '', 0) // Just return the count; no need to decrypt each comment here return Array.isArray(response) ? response.length : 0; } catch (error) { diff --git a/assets/js/Q-Mintership.js b/assets/js/Q-Mintership.js index de115e8..e32e15a 100644 --- a/assets/js/Q-Mintership.js +++ b/assets/js/Q-Mintership.js @@ -649,8 +649,8 @@ const generateAttachmentID = (room, fileIndex = null) => { const findMessagePage = async (room, identifier, limit) => { const { service, query } = getServiceAndQuery(room) - - const allMessages = await searchAllWithOffset(service, query, 0, 0, room) + //TODO check that searchSimple change worked. + const allMessages = await searchSimple(service, query, '', 0, 0, room, 'false') const idx = allMessages.findIndex(msg => msg.identifier === identifier); if (idx === -1) { @@ -746,7 +746,8 @@ const getServiceAndQuery = (room) => { }; const fetchResourceList = async (service, query, limit, offset, room) => { - return await searchAllWithOffset(service, query, limit, offset, room); + //TODO check + return await searchSimple(service, query, '', limit, offset, room, 'false'); }; const handleNoMessagesScenario = (isPolling, page, response, messagesContainer) => { diff --git a/assets/js/QortalApi.js b/assets/js/QortalApi.js index 04c7f14..c3da022 100644 --- a/assets/js/QortalApi.js +++ b/assets/js/QortalApi.js @@ -1,71 +1,71 @@ // Set the forumAdminGroups variable -let adminGroups = ["Q-Mintership-admin", "dev-group", "Mintership-Forum-Admins"]; -let adminGroupIDs = ["721", "1", "673"]; +let adminGroups = ["Q-Mintership-admin", "dev-group", "Mintership-Forum-Admins"] +let adminGroupIDs = ["721", "1", "673"] // Settings to allow non-devmode development with 'live-server' module -let baseUrl = ''; -let isOutsideOfUiDevelopment = false; +let baseUrl = '' +let isOutsideOfUiDevelopment = false if (typeof qortalRequest === 'function') { - console.log('qortalRequest is available as a function. Setting development mode to false and baseUrl to nothing.'); - isOutsideOfUiDevelopment = false; - baseUrl = ''; + console.log('qortalRequest is available as a function. Setting development mode to false and baseUrl to nothing.') + isOutsideOfUiDevelopment = false + baseUrl = '' } else { - console.log('qortalRequest is not available as a function. Setting baseUrl to localhost.'); - isOutsideOfUiDevelopment = true; - baseUrl = "http://localhost:12391"; -}; + console.log('qortalRequest is not available as a function. Setting baseUrl to localhost.') + isOutsideOfUiDevelopment = true + baseUrl = "http://localhost:12391" +} // USEFUL UTILITIES ----- ----- ----- // Generate a short random ID to utilize at the end of unique identifiers. const uid = async () => { - console.log('uid function called'); - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - const charactersLength = characters.length; + console.log('uid function called') + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let result = '' + const charactersLength = characters.length for (let i = 0; i < 6; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - }; - console.log('Generated uid:', result); - return result; -}; + result += characters.charAt(Math.floor(Math.random() * charactersLength)) + } + console.log('Generated uid:', result) + return result +} // a non-async version of the uid function, in case non-async functions need it. Ultimately we can probably remove uid but need to ensure no apps are using it asynchronously first. so this is kept for that purpose for now. const randomID = () => { - console.log('randomID non-async'); - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - const charactersLength = characters.length; + console.log('randomID non-async') + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let result = '' + const charactersLength = characters.length for (let i = 0; i < 6; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - }; - console.log('Generated uid:', result); - return result; + result += characters.charAt(Math.floor(Math.random() * charactersLength)) + } + console.log('Generated uid:', result) + return result } // Turn a unix timestamp into a human-readable date const timestampToHumanReadableDate = async(timestamp) => { - const date = new Date(timestamp); - const day = date.getDate(); - const month = date.getMonth() + 1; - const year = date.getFullYear() - 2000; - const hours = date.getHours(); - const minutes = date.getMinutes(); - const seconds = date.getSeconds(); + const date = new Date(timestamp) + const day = date.getDate() + const month = date.getMonth() + 1 + const year = date.getFullYear() - 2000 + const hours = date.getHours() + const minutes = date.getMinutes() + const seconds = date.getSeconds() - const formattedDate = `${month}.${day}.${year}@${hours}:${minutes}:${seconds}`; - console.log('Formatted date:', formattedDate); - return formattedDate; -}; + const formattedDate = `${month}.${day}.${year}@${hours}:${minutes}:${seconds}` + console.log('Formatted date:', formattedDate) + return formattedDate +} // Base64 encode a string const base64EncodeString = async (str) => { - const encodedString = btoa(String.fromCharCode.apply(null, new Uint8Array(new TextEncoder().encode(str).buffer))); - console.log('Encoded string:', encodedString); - return encodedString; -}; + const encodedString = btoa(String.fromCharCode.apply(null, new Uint8Array(new TextEncoder().encode(str).buffer))) + console.log('Encoded string:', encodedString) + return encodedString +} // const decryptToUnit8ArraySubject = -// base64ToUint8Array(decryptedData); +// base64ToUint8Array(decryptedData) // const responseData = uint8ArrayToObject( // decryptToUnit8ArraySubject -// ); +// ) const base64ToUint8Array = async (base64) => { const binaryString = atob(base64) @@ -93,28 +93,28 @@ const uint8ArrayToObject = async (uint8Array) => { const objectToBase64 = async (obj) => { // Step 1: Convert the object to a JSON string - const jsonString = JSON.stringify(obj); + const jsonString = JSON.stringify(obj) // Step 2: Create a Blob from the JSON string - const blob = new Blob([jsonString], { type: 'application/json' }); + const blob = new Blob([jsonString], { type: 'application/json' }) // Step 3: Create a FileReader to read the Blob as a base64-encoded string return new Promise((resolve, reject) => { - const reader = new FileReader(); + const reader = new FileReader() reader.onloadend = () => { if (typeof reader.result === 'string') { - // Remove 'data:application/json;base64,' prefix - const base64 = reader.result.replace('data:application/json;base64,', ''); - console.log(`base64 resolution: ${base64}`); - resolve(base64); + // Remove 'data:application/jsonbase64,' prefix + const base64 = reader.result.replace('data:application/jsonbase64,', '') + console.log(`base64 resolution: ${base64}`) + resolve(base64) } else { - reject(new Error('Failed to read the Blob as a base64-encoded string')); + reject(new Error('Failed to read the Blob as a base64-encoded string')) } - }; + } reader.onerror = () => { - reject(reader.error); - }; - reader.readAsDataURL(blob); - }); -}; + reject(reader.error) + } + reader.readAsDataURL(blob) + }) +} // User state util const userState = { @@ -124,114 +124,114 @@ const userState = { isAdmin: false, isMinterAdmin: false, isForumAdmin: false -}; +} // USER-RELATED QORTAL CALLS ------------------------------------------ // Obtain the address of the authenticated user checking userState.accountAddress first. const getUserAddress = async () => { try { if (userState.accountAddress) { - console.log('User address found in state:', userState.accountAddress); - return userState.accountAddress; - }; - const userAccount = await qortalRequest({ action: "GET_USER_ACCOUNT" }); + console.log('User address found in state:', userState.accountAddress) + return userState.accountAddress + } + const userAccount = await qortalRequest({ action: "GET_USER_ACCOUNT" }) if (userAccount) { - console.log('Account address:', userAccount.address); - userState.accountAddress = userAccount.address; - console.log('Account address added to state:', userState.accountAddress); - return userState.accountAddress; - }; + console.log('Account address:', userAccount.address) + userState.accountAddress = userAccount.address + console.log('Account address added to state:', userState.accountAddress) + return userState.accountAddress + } } catch (error) { - console.error('Error fetching account address:', error); - throw error; - }; -}; + console.error('Error fetching account address:', error) + throw error + } +} const fetchOwnerAddressFromName = async (name) => { - console.log('fetchOwnerAddressFromName called'); - console.log('name:', name); + console.log('fetchOwnerAddressFromName called') + console.log('name:', name) try { const response = await fetch(`${baseUrl}/names/${name}`, { headers: { 'Accept': 'application/json' }, method: 'GET', - }); - const data = await response.json(); - console.log('Fetched owner address:', data.owner); - return data.owner; + }) + const data = await response.json() + console.log('Fetched owner address:', data.owner) + return data.owner } catch (error) { - console.error('Error fetching owner address:', error); - return null; - }; -}; + console.error('Error fetching owner address:', error) + return null + } +} const verifyUserIsAdmin = async () => { - console.log('verifyUserIsAdmin called (QortalApi.js)'); + console.log('verifyUserIsAdmin called (QortalApi.js)') try { - const accountAddress = userState.accountAddress || await getUserAddress(); - userState.accountAddress = accountAddress; + const accountAddress = userState.accountAddress || await getUserAddress() + userState.accountAddress = accountAddress - const userGroups = await getUserGroups(accountAddress); - console.log('userGroups:', userGroups); + const userGroups = await getUserGroups(accountAddress) + console.log('userGroups:', userGroups) - const minterGroupAdmins = await fetchMinterGroupAdmins(); - console.log('minterGroupAdmins.members:', minterGroupAdmins); + const minterGroupAdmins = await fetchMinterGroupAdmins() + console.log('minterGroupAdmins.members:', minterGroupAdmins) if (!Array.isArray(userGroups)) { - throw new Error('userGroups is not an array or is undefined'); + throw new Error('userGroups is not an array or is undefined') } if (!Array.isArray(minterGroupAdmins)) { - throw new Error('minterGroupAdmins.members is not an array or is undefined'); + throw new Error('minterGroupAdmins.members is not an array or is undefined') } - const isAdmin = userGroups.some(group => adminGroups.includes(group.groupName)); - const isMinterAdmin = minterGroupAdmins.some(admin => admin.member === userState.accountAddress && admin.isAdmin); + const isAdmin = userGroups.some(group => adminGroups.includes(group.groupName)) + const isMinterAdmin = minterGroupAdmins.some(admin => admin.member === userState.accountAddress && admin.isAdmin) if (isMinterAdmin) { - userState.isMinterAdmin = true; + userState.isMinterAdmin = true } if (isAdmin) { - userState.isAdmin = true; - userState.isForumAdmin = true; + userState.isAdmin = true + userState.isForumAdmin = true } - return userState.isAdmin; + return userState.isAdmin } catch (error) { - console.error('Error verifying user admin status:', error); - throw error; + console.error('Error verifying user admin status:', error) + throw error } -}; +} const verifyAddressIsAdmin = async (address) => { - console.log('verifyAddressIsAdmin called'); - console.log('address:', address); + console.log('verifyAddressIsAdmin called') + console.log('address:', address) try { if (!address) { - console.log('No address provided'); - return false; - }; - const userGroups = await getUserGroups(address); - const minterGroupAdmins = await fetchMinterGroupAdmins(); + console.log('No address provided') + return false + } + const userGroups = await getUserGroups(address) + const minterGroupAdmins = await fetchMinterGroupAdmins() const isAdmin = await userGroups.some(group => adminGroups.includes(group.groupName)) const isMinterAdmin = minterGroupAdmins.members.some(admin => admin.member === address && admin.isAdmin) if ((isMinterAdmin) || (isAdmin)) { - return true; + return true } else { return false - }; + } } catch (error) { - console.error('Error verifying address admin status:', error); + console.error('Error verifying address admin status:', error) throw error } -}; +} const getNameInfo = async (name) => { - console.log('getNameInfo called'); - console.log('name:', name); + console.log('getNameInfo called') + console.log('name:', name) try { - const response = await fetch(`${baseUrl}/names/${name}`); - const data = await response.json(); - console.log('Fetched name info:', data); + const response = await fetch(`${baseUrl}/names/${name}`) + const data = await response.json() + console.log('Fetched name info:', data) return { name: data.name, reducedName: data.reducedName, @@ -241,311 +241,293 @@ const getNameInfo = async (name) => { updated: data.updated, isForSale: data.isForSale, salePrice: data.salePrice - }; + } } catch (error) { - console.log('Error fetching name info:', error); - return null; + console.log('Error fetching name info:', error) + return null } -}; +} const getPublicKeyByName = async (name) => { - console.log('getPublicKeyByName called'); - console.log('name:', name); + try { - const nameInfo = await getNameInfo(name); - const address = nameInfo.owner; - const publicKey = await getPublicKeyFromAddress(address); - console.log(`Found public key: for name: ${name}`, publicKey); - return publicKey; + const nameInfo = await getNameInfo(name) + const address = nameInfo.owner + const publicKey = await getPublicKeyFromAddress(address) + console.log(`Found public key: for name: ${name}`, publicKey) + return publicKey } catch (error) { - console.log('Error obtaining public key from name:', error); - return null; + console.log('Error obtaining public key from name:', error) + return null } -}; +} const getPublicKeyFromAddress = async (address) => { - console.log('getPublicKeyFromAddress called'); - console.log('address:', address); try { const response = await fetch(`${baseUrl}/addresses/${address}`,{ method: 'GET', headers: { 'Accept': 'application/json' } - }); - const data = await response.json(); - const publicKey = data.publicKey; - console.log('Fetched public key:', publicKey); - return publicKey; + }) + const data = await response.json() + const publicKey = data.publicKey + + return publicKey } catch (error) { - console.log('Error fetching public key from address:', error); - return null; + console.log('Error fetching public key from address:', error) + return null } -}; +} const getAddressFromPublicKey = async (publicKey) => { - console.log('getAddressFromPublicKey called'); + try { const response = await fetch(`${baseUrl}/addresses/convert/${publicKey}`,{ method: 'GET', headers: { 'Accept': 'text/plain' } - }); - const address = await response.text(); - console.log('Converted Address:', address); - return address; + }) + const address = await response.text() + + return address } catch (error) { - console.log('Error converting public key to address:', error); - return null; + console.log('Error converting public key to address:', error) + return null } -}; +} const login = async () => { - console.log('login called'); + try { if (userState.accountName && (userState.isAdmin || userState.isLoggedIn) && userState.accountAddress) { - console.log(`Account name found in userState: '${userState.accountName}', no need to call API...skipping API call.`); - return userState.accountName; + console.log(`Account name found in userState: '${userState.accountName}', no need to call API...skipping API call.`) + return userState.accountName } - const accountAddress = userState.accountAddress || await getUserAddress(); + const accountAddress = userState.accountAddress || await getUserAddress() const accountNames = await qortalRequest({ action: "GET_ACCOUNT_NAMES", address: accountAddress, - }); + }) if (accountNames) { - userState.isLoggedIn = true; - userState.accountName = accountNames[0].name; - userState.accountAddress = accountAddress; - console.log('All account names:', accountNames); - console.log('Main name (in state):', userState.accountName); - console.log('User has been logged in successfully!'); - return userState.accountName; + userState.isLoggedIn = true + userState.accountName = accountNames[0].name + userState.accountAddress = accountAddress + + console.log('User has been logged in successfully!') + return userState.accountName } else { - throw new Error("No account names found. Are you logged in? Do you have a registered name?"); + throw new Error("No account names found. Are you logged in? Do you have a registered name?") } } catch (error) { - console.error('Error fetching account names:', error); - throw error; + console.error('Error fetching account names:', error) + throw error } -}; +} const getNamesFromAddress = async (address) => { try { const response = await fetch(`${baseUrl}/names/address/${address}?limit=20`, { method: 'GET', headers: { 'Accept': 'application/json' } - }); - const names = await response.json(); - return names.length > 0 ? names[0].name : address; // Return name if found, else return address + }) + const names = await response.json() + return names.length > 0 ? names[0].name : address // Return name if found, else return address } catch (error) { - console.error(`Error fetching names for address ${address}:`, error); - return address; + console.error(`Error fetching names for address ${address}:`, error) + return address +} } -}; // QORTAL GROUP-RELATED CALLS ------------------------------------------------------------------------------------ const getUserGroups = async (userAddress) => { - console.log('getUserGroups called'); - console.log('userAddress:', userAddress); + try { if (!userAddress && userState.accountAddress) { - console.log('No address passed to getUserGroups call... using address from state...'); - userAddress = userState.accountAddress; + userAddress = userState.accountAddress } + const response = await fetch(`${baseUrl}/groups/member/${userAddress}`, { method: 'GET', headers: { 'accept': 'application/json' } - }); - const data = await response.json(); - console.log('Fetched user groups:', data); - return data; + }) + + const data = await response.json() + + return data } catch (error) { - console.error('Error fetching user groups:', error); - throw error; + console.error('Error fetching user groups:', error) + throw error } -}; +} const fetchMinterGroupAdmins = async () => { - console.log('calling for minter admins') + const response = await fetch(`${baseUrl}/groups/members/694?onlyAdmins=true&limit=0&reverse=true`,{ method: 'GET', headers: { 'Accept': 'application/json' } - }); - const admins = await response.json(); + }) + const admins = await response.json() if (!Array.isArray(admins.members)) { - throw new Error("Expected 'members' to be an array but got a different structure"); + throw new Error("Expected 'members' to be an array but got a different structure") } - const adminMembers = admins.members - console.log('Fetched minter admins', admins); - return adminMembers; + + return adminMembers //use what is returned .member to obtain each member... {"member": "memberAddress", "isAdmin": "true"} } const fetchAllAdminGroupsMembers = async () => { try { - let adminGroupMemberAddresses = []; // Declare outside loop to accumulate results + let adminGroupMemberAddresses = [] // Declare outside loop to accumulate results for (const groupID of adminGroupIDs) { const response = await fetch(`${baseUrl}/groups/members/${groupID}?limit=0`, { method: 'GET', headers: { 'Accept': 'application/json' }, - }); + }) - const groupData = await response.json(); + const groupData = await response.json() if (groupData.members && Array.isArray(groupData.members)) { - adminGroupMemberAddresses.push(...groupData.members); // Merge members into the array + adminGroupMemberAddresses.push(...groupData.members) // Merge members into the array } else { - console.warn(`Group ${groupID} did not return valid members.`); + console.warn(`Group ${groupID} did not return valid members.`) } } - return adminGroupMemberAddresses; + return adminGroupMemberAddresses } catch (error) { - console.log('Error fetching admin group members', error); + console.log('Error fetching admin group members', error) } -}; +} const fetchMinterGroupMembers = async () => { try { const response = await fetch(`${baseUrl}/groups/members/694?limit=0`, { method: 'GET', headers: { 'Accept': 'application/json' }, - }); + }) if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); + throw new Error(`HTTP error! Status: ${response.status}`) } - const data = await response.json(); + const data = await response.json() - // Ensure the structure of the response is as expected if (!Array.isArray(data.members)) { - throw new Error("Expected 'members' to be an array but got a different structure"); + throw new Error("Expected 'members' to be an array but got a different structure") } - console.log(`MinterGroupMembers have been fetched.`) - return data.members; + return data.members //use what is returned .member to obtain each member... {"member": "memberAddress", "joined": "{timestamp}"} } catch (error) { - console.error("Error fetching minter group members:", error); - return []; // Return an empty array to prevent further errors + console.error("Error fetching minter group members:", error) + return [] // Return an empty array to prevent further errors } - }; + } const fetchAllGroups = async (limit) => { - console.log('fetchAllGroups called'); - console.log('limit:', limit); if (!limit) { - limit = 2000; + limit = 2000 } try { - const response = await fetch(`${baseUrl}/groups?limit=${limit}&reverse=true`); - const data = await response.json(); - console.log('Fetched all groups:', data); - return data; + const response = await fetch(`${baseUrl}/groups?limit=${limit}&reverse=true`) + const data = await response.json() + + return data } catch (error) { - console.error('Error fetching all groups:', error); + console.error('Error fetching all groups:', error) } -}; +} const fetchAdminGroupsMembersPublicKeys = async () => { try { - let adminGroupMemberAddresses = []; // Declare outside loop to accumulate results + let adminGroupMemberAddresses = [] // Declare outside loop to accumulate results for (const groupID of adminGroupIDs) { const response = await fetch(`${baseUrl}/groups/members/${groupID}?limit=0`, { method: 'GET', headers: { 'Accept': 'application/json' }, - }); + }) - const groupData = await response.json(); + const groupData = await response.json() if (groupData.members && Array.isArray(groupData.members)) { - adminGroupMemberAddresses.push(...groupData.members); // Merge members into the array + adminGroupMemberAddresses.push(...groupData.members) // Merge members into the array } else { - console.warn(`Group ${groupID} did not return valid members.`); + console.warn(`Group ${groupID} did not return valid members.`) } } // Check if adminGroupMemberAddresses has valid data if (!Array.isArray(adminGroupMemberAddresses)) { - throw new Error("Expected 'adminGroupMemberAddresses' to be an array but got a different structure"); + throw new Error("Expected 'adminGroupMemberAddresses' to be an array but got a different structure") } - let allMemberPublicKeys = []; // Declare outside loop to accumulate results + let allMemberPublicKeys = [] // Declare outside loop to accumulate results for (const member of adminGroupMemberAddresses) { - const memberPublicKey = await getPublicKeyFromAddress(member.member); - allMemberPublicKeys.push(memberPublicKey); + const memberPublicKey = await getPublicKeyFromAddress(member.member) + allMemberPublicKeys.push(memberPublicKey) } // Check if allMemberPublicKeys has valid data if (!Array.isArray(allMemberPublicKeys)) { - throw new Error("Expected 'allMemberPublicKeys' to be an array but got a different structure"); + throw new Error("Expected 'allMemberPublicKeys' to be an array but got a different structure") } - console.log(`AdminGroupMemberPublicKeys have been fetched.`); - return allMemberPublicKeys; + console.log(`AdminGroupMemberPublicKeys have been fetched.`) + return allMemberPublicKeys } catch (error) { - console.error('Error fetching admin group member public keys:', error); - return []; // Return an empty array to prevent further errors + console.error('Error fetching admin group member public keys:', error) + return [] // Return an empty array to prevent further errors } -}; +} // QDN data calls -------------------------------------------------------------------------------------------------- const searchLatestDataByIdentifier = async (identifier) => { - console.log('fetchAllDataByIdentifier called'); - console.log('identifier:', identifier); try { const response = await fetch(`${baseUrl}/arbitrary/resources/search?service=DOCUMENT&identifier=${identifier}&includestatus=true&mode=ALL&limit=0&reverse=true`, { method: 'GET', headers: { 'Accept': 'application/json' } - }); - const latestData = await response.json(); - console.log('Fetched latest data by identifier:', latestData); - return latestData; + }) + const latestData = await response.json() + + return latestData } catch (error) { - console.error('Error fetching latest published data:', error); - return null; + console.error('Error fetching latest published data:', error) + return null } -}; +} const publishMultipleResources = async (resources, publicKeys = null, isPrivate = false) => { - console.log('publishMultipleResources called'); - console.log('resources:', resources); - const request = { action: 'PUBLISH_MULTIPLE_QDN_RESOURCES', resources: resources, - }; + } if (isPrivate && publicKeys) { - request.encrypt = true; - request.publicKeys = publicKeys; - }; + request.encrypt = true + request.publicKeys = publicKeys + } try { - const response = await qortalRequest(request); - console.log('Multiple resources published successfully:', response); + const response = await qortalRequest(request) + console.log('Multiple resources published successfully:', response) } catch (error) { - console.error('Error publishing multiple resources:', error); - }; -}; + console.error('Error publishing multiple resources:', error) + } +} -// the object must be in base64 when sent +// NOTE - the object must be in base64 when sent const decryptObject = async (encryptedData) => { const response = await qortalRequest({ action: 'DECRYPT_DATA', encryptedData, // has to be in base64 format // publicKey: publisherPublicKey // requires the public key of the opposite user with whom you've created the encrypted data. For DIRECT messages only. - }); + }) const decryptedObject = response return decryptedObject } -// const decryptAndParseObject = async (base64Data) => { -// const decrypted = await decryptObject(base64Data); -// return JSON.parse(atob(decrypted)); -// }; const decryptAndParseObject = async (base64Data) => { const decrypto = await decryptObject(base64Data) @@ -560,64 +542,61 @@ const decryptAndParseObject = async (base64Data) => { // Decode the byte array using TextDecoder const decoder = new TextDecoder() const jsonString = decoder.decode(bytes) - // Convert the JSON string back into an object const obj = JSON.parse(jsonString) return obj } - const searchResourcesWithMetadata = async (query, limit) => { - console.log('searchResourcesWithMetadata called'); try { if (limit == 0) { - limit = 0; + limit = 0 } else if (!limit || (limit < 10 && limit != 0)) { - limit = 200; + limit = 200 } const response = await fetch(`${baseUrl}/arbitrary/resources/search?query=${query}&mode=ALL&includestatus=true&includemetadata=true&limit=${limit}&reverse=true`, { method: 'GET', headers: { 'accept': 'application/json' } - }); - const data = await response.json(); - console.log('Search results with metadata:', data); - return data; + }) + const data = await response.json() + console.log('Search results with metadata:', data) + return data } catch (error) { - console.error('Error searching for resources with metadata:', error); - throw error; + console.error('Error searching for resources with metadata:', error) + throw error } -}; +} const searchAllResources = async (query, limit, after, reverse=false) => { - console.log('searchAllResources called. Query:', query, 'Limit:', limit,'Reverse:', reverse); + console.log('searchAllResources called. Query:', query, 'Limit:', limit,'Reverse:', reverse) try { if (limit == 0) { - limit = 0; + limit = 0 } if (!limit || (limit < 10 && limit != 0)) { - limit = 200; + limit = 200 } if (after == null || after === undefined) { - after = 0; + after = 0 } const response = await fetch(`${baseUrl}/arbitrary/resources/search?query=${query}&mode=ALL&after=${after}&includestatus=false&includemetadata=false&limit=${limit}&reverse=${reverse}`, { method: 'GET', headers: { 'accept': 'application/json' } - }); - const data = await response.json(); - console.log('Search results with metadata:', data); - return data; + }) + const data = await response.json() + console.log('Search results with metadata:', data) + return data } catch (error) { - console.error('Error searching for resources with metadata:', error); - throw error; + console.error('Error searching for resources with metadata:', error) + throw error } -}; +} const searchAllWithOffset = async (service, query, limit, offset, room) => { try { if (!service || (service === "BLOG_POST" && room !== "admins")) { - console.log("Performing search for BLOG_POST..."); + console.log("Performing search for BLOG_POST...") const response = await qortalRequest({ action: "SEARCH_QDN_RESOURCES", service: "BLOG_POST", @@ -626,13 +605,13 @@ const searchAllWithOffset = async (service, query, limit, offset, room) => { offset, mode: "ALL", reverse: false, - }); - return response; + }) + return response } if (room === "admins") { - service = service || "MAIL_PRIVATE"; // Default to MAIL_PRIVATE if no service provided - console.log("Performing search for MAIL_PRIVATE in Admin room..."); + service = service || "MAIL_PRIVATE" // Default to MAIL_PRIVATE if no service provided + console.log("Performing search for MAIL_PRIVATE in Admin room...") const response = await qortalRequest({ action: "SEARCH_QDN_RESOURCES", service, @@ -641,253 +620,299 @@ const searchAllWithOffset = async (service, query, limit, offset, room) => { offset, mode: "ALL", reverse: false, - }); - return response; + }) + return response } - console.warn("Invalid parameters passed to searchAllWithOffset"); - return []; // Return empty array if no valid conditions match + console.warn("Invalid parameters passed to searchAllWithOffset") + return [] // Return empty array if no valid conditions match } catch (error) { - console.error("Error during SEARCH_QDN_RESOURCES:", error); - return []; // Return empty array on error + console.error("Error during SEARCH_QDN_RESOURCES:", error) + return [] // Return empty array on error } -}; +} // NOTE - This function does a search and will return EITHER AN ARRAY OR A SINGLE OBJECT. if you want to guarantee a single object, pass 1 as limit. i.e. await searchSimple(service, identifier, "", 1) will return a single object. -const searchSimple = async (service, identifier, name, limit = 20) => { +const searchSimple = async (service, identifier, name, limit = 1500, offset = 0, room='', reverse='true') => { try { - let urlSuffix = `service=${service}&identifier=${identifier}&name=${name}&limit=${limit}`; + let urlSuffix = `service=${service}&identifier=${identifier}&name=${name}&prefix=true&limit=${limit}&offset=${offset}&reverse=${reverse}` - if (name && !identifier) { - console.log('name only searchSimple', name); - urlSuffix = `service=${service}&name=${name}&limit=${limit}`; - } else if (!name && identifier) { - console.log('identifier only searchSimple', identifier); - urlSuffix = `service=${service}&identifier=${identifier}&limit=${limit}`; - } else if (!name && !identifier) { - console.error(`name: ${name} AND identifier: ${identifier} not passed. Must include at least one...`); - return null; + if (name && !identifier && !room) { + console.log('name only searchSimple', name) + urlSuffix = `service=${service}&name=${name}&limit=${limit}&prefix=true&reverse=${reverse}` + } else if (!name && identifier && !room) { + console.log('identifier only searchSimple', identifier) + urlSuffix = `service=${service}&identifier=${identifier}&limit=${limit}&prefix=true&reverse=${reverse}` + } else if (!name && !identifier && !room) { + console.error(`name: ${name} AND identifier: ${identifier} not passed. Must include at least one...`) + return null + } else { - console.log(`name: ${name} AND identifier: ${identifier} passed, searching by both...`); + console.log(`final searchSimple params = service: '${service}', identifier: '${identifier}', name: '${name}', limit: '${limit}', offset: '${offset}', room: '${room}', reverse: '${reverse}'`) } - const response = await fetch(`${baseUrl}/arbitrary/resources/searchsimple?${urlSuffix}`, { method: 'GET', headers: { 'accept': 'application/json' } - }); - - const data = await response.json(); + }) + const data = await response.json() if (!Array.isArray(data)) { - console.log("searchSimple: data is not an array?", data); - return null; + console.log("searchSimple: data is not an array?", data) + return null } if (data.length === 0) { - console.log("searchSimple: no results found"); - return null; // Return null when no items + console.log("searchSimple: no results found") + return null // Return null when no items } - if (data.length === 1 || limit === 1) { - console.log("searchSimple: single result returned", data[0]); - return data[0]; // Return just the single object + if (limit === 1) { + console.log("searchSimple: limit=1 passed, only result returned", data[0]) + return data[0] // Return just the single object } - console.log("searchSimple: multiple results returned", data); - return data; + console.log("searchSimple: multiple results returned", data) + return data } catch (error) { - console.error("error during searchSimple", error); - throw error; + console.error("error during searchSimple", error) + throw error } - }; + } const searchAllCountOnly = async (query, room) => { try { - let offset = 0; - const limit = 100; // Chunk size for fetching - let totalCount = 0; - let hasMore = true; + let offset = 0 + const limit = 100 // Chunk size for fetching + let totalCount = 0 + let hasMore = true + const qMintershipForumIdentifierPrefix = 'mintership-forum-message' - if (room === "admins") { - while (hasMore) { - const response = await qortalRequest({ - action: "SEARCH_QDN_RESOURCES", - service: "MAIL_PRIVATE", - query: query, - limit: limit, - offset: offset, - mode: "ALL", - reverse: false - }); - - if (response && response.length > 0) { - totalCount += response.length; - offset = totalCount; - console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`); - } else { - hasMore = false; - } - } - }else { - // Fetch in chunks to accumulate the count - while (hasMore) { - const response = await qortalRequest({ - action: "SEARCH_QDN_RESOURCES", - service: "BLOG_POST", - query: query, - limit: limit, - offset: offset, - mode: "ALL", - reverse: false - }); - - if (response && response.length > 0) { - totalCount += response.length; - offset = totalCount; - console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`); - } else { - hasMore = false; + if (!query.includes(qMintershipForumIdentifierPrefix)) { + + try { + console.log(`'mintership-forum-message' not found, switching to actual query...`) + + if (room === "admins") { + while (hasMore) { + const response = await qortalRequest({ + action: "SEARCH_QDN_RESOURCES", + service: "MAIL_PRIVATE", + query: query, + limit: limit, + offset: offset, + mode: "ALL", + reverse: false + }) + + if (response && response.length > 0) { + totalCount += response.length + offset = totalCount + console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`) + + } else { + hasMore = false + } + } + + }else { + // Fetch in chunks to accumulate the count + while (hasMore) { + const response = await qortalRequest({ + action: "SEARCH_QDN_RESOURCES", + service: "BLOG_POST", + query: query, + limit: limit, + offset: offset, + mode: "ALL", + reverse: false + }) + + if (response && response.length > 0) { + totalCount += response.length + offset = totalCount + console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`) + } else { + hasMore = false + } + } + } + + return totalCount + + } catch (error) { + console.error("Error during SEARCH_QDN_RESOURCES:", error) + throw error } } - } - - return totalCount; + + if (room === "admins") { + while (hasMore) { + const response = await searchSimple('MAIL_PRIVATE', query, '', limit, offset, room, false) + + if (response && response.length > 0) { + totalCount += response.length + offset = totalCount + console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`) + + } else { + hasMore = false + } + } + + }else { + + while (hasMore) { + const response = await searchSimple('BLOG_POST', query, '', limit, offset, room, false) + + if (response && response.length > 0) { + totalCount += response.length + offset = totalCount + console.log(`Fetched ${response.length} items, total count: ${totalCount}, current offset: ${offset}`) + + } else { + hasMore = false + } + } + } + + return totalCount + } catch (error) { - console.error("Error during SEARCH_QDN_RESOURCES:", error); - throw error; + console.error("Error during SEARCH_QDN_RESOURCES:", error) + throw error } -} +} const searchResourcesWithStatus = async (query, limit, status = 'local') => { - console.log('searchResourcesWithStatus called'); - console.log('query:', query); - console.log('limit:', limit); - console.log('status:', status); + console.log('searchResourcesWithStatus called') + console.log('query:', query) + console.log('limit:', limit) + console.log('status:', status) try { // Set default limit if not provided or too low if (!limit || limit < 10) { - limit = 200; + limit = 200 } // Make API request const response = await fetch(`${baseUrl}/arbitrary/resources/search?query=${query}&includestatus=true&limit=${limit}&reverse=true`, { method: 'GET', headers: { 'accept': 'application/json' } - }); + }) - const data = await response.json(); + const data = await response.json() // Filter based on status if provided if (status) { if (status === 'notLocal') { - const notDownloaded = data.filter((resource) => resource.status.status === 'published'); - console.log('notDownloaded:', notDownloaded); - return notDownloaded; + const notDownloaded = data.filter((resource) => resource.status.status === 'published') + console.log('notDownloaded:', notDownloaded) + return notDownloaded } else if (status === 'local') { const downloaded = data.filter((resource) => resource.status.status === 'ready' || resource.status.status === 'downloaded' || resource.status.status === 'building' || (resource.status.status && resource.status.status !== 'published') - ); - return downloaded; + ) + return downloaded } } // Return all data if no specific status is provided - console.log('Returning all data...'); - return data; + console.log('Returning all data...') + return data } catch (error) { - console.error('Error searching for resources with metadata:', error); - throw error; + console.error('Error searching for resources with metadata:', error) + throw error } -}; +} const getResourceMetadata = async (service, name, identifier) => { - console.log('getResourceMetadata called'); - console.log('service:', service); - console.log('name:', name); - console.log('identifier:', identifier); + console.log('getResourceMetadata called') + console.log('service:', service) + console.log('name:', name) + console.log('identifier:', identifier) try { const response = await fetch(`${baseUrl}/arbitrary/metadata/${service}/${name}/${identifier}`, { method: 'GET', headers: { 'accept': 'application/json' } - }); - const data = await response.json(); - console.log('Fetched resource metadata:', data); - return data; + }) + const data = await response.json() + console.log('Fetched resource metadata:', data) + return data } catch (error) { - console.error('Error fetching resource metadata:', error); - throw error; + console.error('Error fetching resource metadata:', error) + throw error } -}; +} const fetchFileBase64 = async (service, name, identifier) => { - const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}/?encoding=base64`; + const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}/?encoding=base64` try { const response = await fetch(url,{ method: 'GET', headers: { 'accept': 'text/plain' } - }); - return response; + }) + return response } catch (error) { - console.error("Error fetching the image file:", error); + console.error("Error fetching the image file:", error) } -}; +} async function loadImageHtml(service, name, identifier, filename, mimeType) { try { - const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}`; + const url = `${baseUrl}/arbitrary/${service}/${name}/${identifier}` // Fetch the file as a blob - const response = await fetch(url); + const response = await fetch(url) // Convert the response to a Blob - const fileBlob = new Blob([response], { type: mimeType }); + const fileBlob = new Blob([response], { type: mimeType }) // Create an Object URL from the Blob - const objectUrl = URL.createObjectURL(fileBlob); + const objectUrl = URL.createObjectURL(fileBlob) // Use the Object URL as the image source - const attachmentHtml = ` `; + const attachmentHtml = ` ` - return attachmentHtml; + return attachmentHtml } catch (error) { - console.error("Error fetching the image:", error); + console.error("Error fetching the image:", error) } } const fetchAndSaveAttachment = async (service, name, identifier, filename, mimeType) => { try { if (!filename || !mimeType) { - console.error("Filename and mimeType are required"); - return; + console.error("Filename and mimeType are required") + return } // If it's a private file, we fetch with ?encoding=base64 and decrypt if (service === "MAIL_PRIVATE") { - service = "FILE_PRIVATE"; + service = "FILE_PRIVATE" } - const baseUrlWithParams = `${baseUrl}/arbitrary/${service}/${name}/${identifier}?async=true&attempts=5`; + const baseUrlWithParams = `${baseUrl}/arbitrary/${service}/${name}/${identifier}?async=true&attempts=5` if (service === "FILE_PRIVATE") { // 1) We want the encrypted base64 - const urlPrivate = `${baseUrlWithParams}&encoding=base64`; + const urlPrivate = `${baseUrlWithParams}&encoding=base64` const response = await fetch(urlPrivate, { method: 'GET', headers: { 'accept': 'text/plain' } - }); + }) if (!response.ok) { - throw new Error(`File not found (HTTP ${response.status}): ${urlPrivate}`); + throw new Error(`File not found (HTTP ${response.status}): ${urlPrivate}`) } // 2) Get the encrypted base64 text - const encryptedBase64Data = await response.text(); - console.log("Fetched Encrypted Base64 Data:", encryptedBase64Data); + const encryptedBase64Data = await response.text() + console.log("Fetched Encrypted Base64 Data:", encryptedBase64Data) // 3) Decrypt => returns decrypted base64 - const decryptedBase64 = await decryptObject(encryptedBase64Data); - console.log("Decrypted Base64 Data:", decryptedBase64); + const decryptedBase64 = await decryptObject(encryptedBase64Data) + console.log("Decrypted Base64 Data:", decryptedBase64) // 4) Convert that to a Blob - const fileBlob = base64ToBlob(decryptedBase64, mimeType); + const fileBlob = base64ToBlob(decryptedBase64, mimeType) // 5) Save the file using qortalRequest await qortalRequest({ @@ -895,36 +920,36 @@ const fetchAndSaveAttachment = async (service, name, identifier, filename, mimeT blob: fileBlob, filename, mimeType - }); - console.log("Encrypted file saved successfully:", filename); + }) + console.log("Encrypted file saved successfully:", filename) } else { // Normal, unencrypted file const response = await fetch(baseUrlWithParams, { method: 'GET', headers: { 'accept': 'text/plain' } - }); + }) if (!response.ok) { - throw new Error(`File not found (HTTP ${response.status}): ${baseUrlWithParams}`); + throw new Error(`File not found (HTTP ${response.status}): ${baseUrlWithParams}`) } - const blob = await response.blob(); + const blob = await response.blob() await qortalRequest({ action: "SAVE_FILE", blob, filename, mimeType - }); - console.log("File saved successfully:", filename); + }) + console.log("File saved successfully:", filename) } } catch (error) { console.error( `Error fetching or saving attachment (service: ${service}, name: ${name}, identifier: ${identifier}):`, error - ); + ) } - }; + } /** @@ -935,111 +960,111 @@ const fetchAndSaveAttachment = async (service, name, identifier, filename, mimeT */ const base64ToBlob = (base64String, mimeType) => { // Decode base64 to binary string - const binaryString = atob(base64String); + const binaryString = atob(base64String) // Convert binary string to Uint8Array - const len = binaryString.length; - const bytes = new Uint8Array(len); + const len = binaryString.length + const bytes = new Uint8Array(len) for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); + bytes[i] = binaryString.charCodeAt(i) } // Create a blob from the Uint8Array - return new Blob([bytes], { type: mimeType }); - }; + return new Blob([bytes], { type: mimeType }) + } const fetchEncryptedImageBase64 = async (service, name, identifier, mimeType) => { try { // Fix potential typo: use &async=... - const urlPrivate = `${baseUrl}/arbitrary/${service}/${name}/${identifier}?encoding=base64&async=true&attempts=5`; + const urlPrivate = `${baseUrl}/arbitrary/${service}/${name}/${identifier}?encoding=base64&async=true&attempts=5` const response = await fetch(urlPrivate, { method: 'GET', headers: { 'accept': 'text/plain' } - }); + }) if (!response.ok) { // Return null to "skip" the missing file - console.warn(`File not found (HTTP ${response.status}): ${urlPrivate}`); - return null; + console.warn(`File not found (HTTP ${response.status}): ${urlPrivate}`) + return null } // 2) Read the base64 text - const encryptedBase64Data = await response.text(); - console.log("Fetched Encrypted Base64 Data:", encryptedBase64Data); + const encryptedBase64Data = await response.text() + console.log("Fetched Encrypted Base64 Data:", encryptedBase64Data) // 3) Decrypt => returns the *decrypted* base64 string - const decryptedBase64 = await decryptObject(encryptedBase64Data); - console.log("Decrypted Base64 Data:", decryptedBase64); + const decryptedBase64 = await decryptObject(encryptedBase64Data) + console.log("Decrypted Base64 Data:", decryptedBase64) // 4) Convert that decrypted base64 into a Blob - const fileBlob = base64ToBlob(decryptedBase64, mimeType); + const fileBlob = base64ToBlob(decryptedBase64, mimeType) // 5) (Optional) Create an object URL - const objectUrl = URL.createObjectURL(fileBlob); - console.log("Object URL:", objectUrl); + const objectUrl = URL.createObjectURL(fileBlob) + console.log("Object URL:", objectUrl) // Return the base64 or objectUrl, whichever you need - return decryptedBase64; + return decryptedBase64 } catch (error) { - console.error("Skipping file due to error in fetchEncryptedImageBase64:", error); - return null; // indicates "missing or failed" + console.error("Skipping file due to error in fetchEncryptedImageBase64:", error) + return null // indicates "missing or failed" } - }; + } const renderData = async (service, name, identifier) => { - console.log('renderData called'); - console.log('service:', service); - console.log('name:', name); - console.log('identifier:', identifier); + console.log('renderData called') + console.log('service:', service) + console.log('name:', name) + console.log('identifier:', identifier) try { const response = await fetch(`${baseUrl}/render/${service}/${name}?identifier=${identifier}`, { method: 'GET', headers: { 'accept': '*/*' } - }); + }) // If the response is not OK (status 200-299), throw an error if (!response.ok) { - throw new Error('Error rendering data'); + throw new Error('Error rendering data') } - const responseText = await response.text(); + const responseText = await response.text() // Check if the response includes indicating it's an HTML document if (responseText.includes(' { - console.log('getProductDetails called'); - console.log('service:', service); - console.log('name:', name); - console.log('identifier:', identifier); + console.log('getProductDetails called') + console.log('service:', service) + console.log('name:', name) + console.log('identifier:', identifier) try { const response = await fetch(`${baseUrl}/arbitrary/metadata/${service}/${name}/${identifier}`, { method: 'GET', headers: { 'accept': 'application/json' } - }); - const data = await response.json(); - console.log('Fetched product details:', data); - return data; + }) + const data = await response.json() + console.log('Fetched product details:', data) + return data } catch (error) { - console.error('Error fetching product details:', error); - throw error; + console.error('Error fetching product details:', error) + throw error } -}; +} // Qortal poll-related calls ---------------------------------------------------------------------- @@ -1049,14 +1074,14 @@ const fetchPollResults = async (pollName) => { const response = await fetch(`${baseUrl}/polls/votes/${pollName}`, { method: 'GET', headers: { 'Accept': 'application/json' } - }); - const pollData = await response.json(); - return pollData; + }) + const pollData = await response.json() + return pollData } catch (error) { - console.error(`Error fetching poll results for ${pollName}:`, error); - return null; + console.error(`Error fetching poll results for ${pollName}:`, error) + return null } - }; + } // Vote YES on a poll ------------------------------ const voteYesOnPoll = async (poll) => { @@ -1064,7 +1089,7 @@ const voteYesOnPoll = async (poll) => { action: "VOTE_ON_POLL", pollName: poll, optionIndex: 0, - }); + }) } // Vote NO on a poll ----------------------------- @@ -1073,7 +1098,7 @@ const voteYesOnPoll = async (poll) => { action: "VOTE_ON_POLL", pollName: poll, optionIndex: 1, - }); + }) } // export { @@ -1100,4 +1125,4 @@ const voteYesOnPoll = async (poll) => { // getPublicKeyByName, // objectToBase64, // fetchMinterGroupAdmins -// }; +// } diff --git a/index.html b/index.html index b20e496..9251b59 100644 --- a/index.html +++ b/index.html @@ -68,7 +68,7 @@+ MASSIVE performance improvements today. Increased performance of both the Forum and Minter Board by a HUGE margin today by leveraging async better, and utilizing searchSimple for all search calls. This makes the forum and boards load 100x faster. Also, may have determined the cause of the QDN bug... we will research further, but as of now, I am definitely getting better results withOUT following names. Will update as time goes on. Also... PLEASE NOTE - POLLS ARE NOT TO BE UTILIZED THROUGH QOMBO OR ANY OTHER APP. Polls in the Minter and Admin Boards are TIED to the cards that published them, and the results are FILTERED to display only the results of MINTERS and ADMINS. Therefore, utilizing outside tools to read (or create) polls is not only an exercise in futility, but also will provide no useful information whatsoever. Please realize that the poll data is utilized to show direct support of the cards by minters and admins, and pols are NOT MEANT TO BE CREATED OR VIEWED OUTIDE OF Q-MINTERSHIP. Thank you.
++ it seems there were some issues caused by what can only be described as the QDN bug. I will be checking into this in more detail in the near future, but it seems like the previous publish did not get the update as it was supposed to out to many people, and as such many of the changes were not there in the code that was on each node. Due to this, many issues were happening. Including the fact that the identifier change didn't take place, or at least that is what seems to have happened. Will verify this shortly. Until then will publish another update now that should resolve some lingering issues.
+Q-Mintership v0.61beta
+Q-Mintership v0.63beta