Skip to content

Commit

Permalink
Refactored get.json() to be promise-based + improved err details, m…
Browse files Browse the repository at this point in the history
…oved update footer to `update.footerContent()`, refactored it to use new `get.json()` + drastically reduced nesting
  • Loading branch information
adamlui committed Jan 29, 2025
1 parent 6c32282 commit cc6c5fe
Showing 1 changed file with 121 additions and 113 deletions.
234 changes: 121 additions & 113 deletions duckduckgpt/greasemonkey/duckduckgpt.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
// @description:zu Yengeza izimpendulo ze-AI ku-DuckDuckGo (inikwa amandla yi-GPT-4o!)
// @author KudoAI
// @namespace https://kudoai.com
// @version 2025.1.28.1
// @version 2025.1.29
// @license MIT
// @icon https://assets.ddgpt.com/images/icons/duckduckgpt/icon48.png?v=06af076
// @icon64 https://assets.ddgpt.com/images/icons/duckduckgpt/icon64.png?v=06af076
Expand Down Expand Up @@ -2347,6 +2347,109 @@
config.widerSidebar && !config.anchored ? 85.6 : config.expanded ? 86.9 : 82.6 }%`
},


async footerContent() {

// Init advertisers data
const advertisersData = await get.json(
'https://cdn.jsdelivr.net/gh/KudoAI/ads-library/advertisers/index.json'
).catch(err => log.error(err.message)) ; if (!advertisersData) return

// Pick random advertiser
let chosenAdvertiser
for (const [advertiser, details] of shuffle(applyBoosts(Object.entries(advertisersData))))
if (details.campaigns.text) { chosenAdvertiser = advertiser ; break }
if (!chosenAdvertiser) return

// Init chosen advertiser's campaigns data
const campaignsData = await get.json(
`https://cdn.jsdelivr.net/gh/KudoAI/ads-library/advertisers/${
chosenAdvertiser}/text/campaigns.json`
).catch(err => log.error(err.message)) ; if (!campaignsData) return

// Init vars for ad selection
const reAppName = new RegExp(app.name.toLowerCase(), 'i')
const currentDate = (() => { // in YYYYMMDD format
const today = new Date(), year = today.getFullYear(),
month = String(today.getMonth() + 1).padStart(2, '0'),
day = String(today.getDate()).padStart(2, '0')
return year + month + day
})() ; let adSelected = false

// Select random, active campaign
for (const [campaignName, campaign] of shuffle(applyBoosts(Object.entries(campaignsData)))) {
const campaignIsActive = campaign.active && (!campaign.endDate || currentDate <= campaign.endDate)
if (!campaignIsActive) continue // to next campaign since campaign inactive

// Select random active group
for (const [groupName, adGroup] of shuffle(applyBoosts(Object.entries(campaign.adGroups)))) {

// Skip disqualified groups
if ( // self-group for other apps
/^self$/i.test(groupName) && !reAppName.test(campaignName)
|| ( // non-self group for this app
reAppName.test(campaignName) && !/^self$/i.test(groupName))
|| adGroup.active == false // group explicitly disabled
|| adGroup.targetBrowsers && // target browser(s) exist...
!adGroup.targetBrowsers.some( // ...but doesn't match user's
browser => new RegExp(browser, 'i').test(navigator.userAgent))
|| adGroup.targetLocations && ( // target locale(s) exist...
// ...but user locale is missing or excluded
!env.userLocale || !adGroup.targetLocations.some(
loc => loc.includes(env.userLocale) || env.userLocale.includes(loc)))
) continue // to next group

// Filter out inactive ads, pick random active one
const activeAds = adGroup.ads.filter(ad => ad.active != false)
if (!activeAds.length) continue // to next group since no ads active
const chosenAd = activeAds[Math.floor(chatgpt.randomFloat() * activeAds.length)]

// Build destination URL
let destinationURL = chosenAd.destinationURL || adGroup.destinationURL || campaign.destinationURL || ''
if (destinationURL.includes('http')) { // insert UTM tags
const [baseURL, queryString] = destinationURL.split('?'),
queryParams = new URLSearchParams(queryString || '')
queryParams.set('utm_source', app.name.toLowerCase())
queryParams.set('utm_content', 'app_footer_link')
destinationURL = `${baseURL}?${queryParams.toString()}`
}

// Update footer content
footerContent.setAttribute('class', '') // reset for re-fade
const newFooterContent = destinationURL ? dom.create.anchor(destinationURL) : dom.create.elem('span')
footerContent.replaceWith(newFooterContent) ; footerContent = newFooterContent
footerContent.classList.add('fade-in', 'anchored-hidden')
footerContent.textContent = chosenAd.text
footerContent.setAttribute('title', chosenAd.tooltip || '')
setTimeout(() => footerContent.classList.add('active'), 100) // to trigger fade
adSelected = true ; break
}
if (adSelected) break // out of campaign loop after ad selection
}

function shuffle(list) {
let currentIdx = list.length, tempValue, randomIdx
while (currentIdx != 0) { // elements remain to be shuffled
randomIdx = Math.floor(chatgpt.randomFloat() * currentIdx) ; currentIdx -=1
tempValue = list[currentIdx] ; list[currentIdx] = list[randomIdx] ; list[randomIdx] = tempValue
} return list
}

function applyBoosts(list) {
let boostedList = [...list],
boostedListLength = boostedList.length -1 // for applying multiple boosts
list.forEach(([name, data]) => { // check for boosts
if (data.boost) { // boost flagged entry's selection probability
const boostPercent = parseInt(data.boost) / 100
const entriesNeeded = Math.ceil(boostedListLength / (1 - boostPercent)) // total entries needed
* boostPercent -1 // reduced to boosted entries needed
for (let i = 0 ; i < entriesNeeded ; i++) boostedList.push([name, data]) // saturate list
boostedListLength += entriesNeeded // update for subsequent calculations
}})
return boostedList
}
},

replyPrefix() {
const firstP = appDiv.querySelector('pre p')
if (!firstP) return
Expand Down Expand Up @@ -3321,13 +3424,19 @@

const get = {

json(url, callback) { // for dynamic footer
xhr({ method: 'GET', url: url, onload: resp => {
if (resp.status >= 200 && resp.status < 300) {
try { const data = JSON.parse(resp.responseText) ; callback(null, data) }
catch (err) { callback(err, null) }
} else callback(new Error('Failed to load data: ' + resp.statusText), null)
}})
json(url) {
log.caller = `get.json('${url}')`
return new Promise((resolve, reject) =>
xhr({ method: 'GET', url: url, onload: resp => {
if (resp.status >= 300) { // status error
const errType = resp.status >= 300 && resp.status < 400 ? 'REDIRECT'
: resp.status >= 400 && resp.status < 500 ? 'CLIENT' : 'SERVER'
reject(new Error(`${errType} ERROR: ${resp.status}`))
}
try { resolve(JSON.parse(resp.responseText)) }
catch (err) { reject(new Error(`PARSE ERROR: ${err.message}`)) }
}})
)
},

async reply(msgChain) {
Expand Down Expand Up @@ -3826,7 +3935,10 @@
appFooter.classList.add('fade-in', 'anchored-hidden')
let footerContent = dom.create.anchor('#', app.msgs.link_shareFeedback, { target: '_self' })
footerContent.onclick = () => modals.open('feedback')
appFooter.append(footerContent)
appFooter.append(footerContent);

// Check for active TEXT CAMPAIGNS to replace footer CTA
update.footerContent()

// APPEND DDGPT + footer to DDG
const appElems = [appDiv, appFooter]
Expand All @@ -3847,110 +3959,6 @@
// REPLACE appDivParent max-width w/ min-width for better UI
if (!env.browser.isMobile) Object.assign(appDivParent.style, { maxWidth: '', minWidth: '448px' })

// Check for active TEXT CAMPAIGNS to replace footer CTA
get.json('https://cdn.jsdelivr.net/gh/KudoAI/ads-library/advertisers/index.json',
(err, advertisersData) => { if (err) return

// Init vars
let chosenAdvertiser, adSelected
const re_appName = new RegExp(app.name.toLowerCase(), 'i')
const currentDate = (() => { // in YYYYMMDD format
const today = new Date(), year = today.getFullYear(),
month = String(today.getMonth() + 1).padStart(2, '0'),
day = String(today.getDate()).padStart(2, '0')
return year + month + day
})()

// Select random, active advertiser
for (const [advertiser, details] of shuffle(applyBoosts(Object.entries(advertisersData))))
if (details.campaigns.text) { chosenAdvertiser = advertiser ; break }

// Fetch a random, active creative
if (chosenAdvertiser) {
const campaignsURL = 'https://cdn.jsdelivr.net/gh/KudoAI/ads-library/advertisers/'
+ chosenAdvertiser + '/text/campaigns.json'
get.json(campaignsURL, (err, campaignsData) => { if (err) return

// Select random, active campaign
for (const [campaignName, campaign] of shuffle(applyBoosts(Object.entries(campaignsData)))) {
const campaignIsActive = campaign.active && (
!campaign.endDate || currentDate <= campaign.endDate)
if (!campaignIsActive) continue // to next campaign since campaign inactive

// Select random active group
for (const [groupName, adGroup] of shuffle(
applyBoosts(Object.entries(campaign.adGroups)))) {

// Skip disqualified groups
if ( // self-group for other apps
/^self$/i.test(groupName) && !re_appName.test(campaignName)
|| ( // non-self group for this app
re_appName.test(campaignName) && !/^self$/i.test(groupName))
|| adGroup.active == false // group explicitly disabled
|| adGroup.targetBrowsers && // target browser(s) exist...
!adGroup.targetBrowsers.some( // ...but doesn't match user's
browser => new RegExp(browser, 'i').test(navigator.userAgent))
|| adGroup.targetLocations && ( // target locale(s) exist...
// ...but user locale is missing or excluded
!env.userLocale || !adGroup.targetLocations.some(
loc => loc.includes(env.userLocale) || env.userLocale.includes(loc)))
) continue // to next group

// Filter out inactive ads, pick random active one
const activeAds = adGroup.ads.filter(ad => ad.active != false)
if (!activeAds.length) continue // to next group since no ads active
const chosenAd = ( // random active one
activeAds[Math.floor(chatgpt.randomFloat() * activeAds.length)])

// Build destination URL
let destinationURL = chosenAd.destinationURL || adGroup.destinationURL
|| campaign.destinationURL || ''
if (destinationURL.includes('http')) { // insert UTM tags
const [baseURL, queryString] = destinationURL.split('?'),
queryParams = new URLSearchParams(queryString || '')
queryParams.set('utm_source', app.name.toLowerCase())
queryParams.set('utm_content', 'app_footer_link')
destinationURL = baseURL + '?' + queryParams.toString()
}

// Update footer content
footerContent.setAttribute('class', '') // reset for re-fade
const newFooterContent = destinationURL ? dom.create.anchor(destinationURL)
: document.createElement('span')
footerContent.replaceWith(newFooterContent) ; footerContent = newFooterContent
footerContent.classList.add('fade-in', 'anchored-hidden')
footerContent.textContent = chosenAd.text
footerContent.setAttribute('title', chosenAd.tooltip || '')
setTimeout(() => footerContent.classList.add('active'), 100) // to trigger fade
adSelected = true ; break
}
if (adSelected) break // out of campaign loop after ad selection
}})}

function shuffle(list) {
let currentIdx = list.length, tempValue, randomIdx
while (currentIdx != 0) { // elements remain to be shuffled
randomIdx = Math.floor(chatgpt.randomFloat() * currentIdx) ; currentIdx -= 1
tempValue = list[currentIdx] ; list[currentIdx] = list[randomIdx] ; list[randomIdx] = tempValue
}
return list
}

function applyBoosts(list) {
let boostedList = [...list],
boostedListLength = boostedList.length - 1 // for applying multiple boosts
list.forEach(([name, data]) => { // check for boosts
if (data.boost) { // boost flagged entry's selection probability
const boostPercent = parseInt(data.boost) / 100,
entriesNeeded = Math.ceil(boostedListLength / (1 - boostPercent)) // total entries needed
* boostPercent - 1 // reduced to boosted entries needed
for (let i = 0 ; i < entriesNeeded ; i++) boostedList.push([name, data]) // saturate list
boostedListLength += entriesNeeded // update for subsequent calculations
}})
return boostedList
}
})

// REFERRALIZE links to support author
setTimeout(() => document.querySelectorAll('a[href^="https://www.amazon."]').forEach(anchor => {
const url = new URL(anchor.href) ; url.searchParams.set('tag', 'kudo-ai-20')
Expand Down

0 comments on commit cc6c5fe

Please sign in to comment.