Give a syndicated offer a title a stranger can read

A campaign's name is the member's own internal label, and some of them are
two characters ("MG"). DripOffers rejects anything under five, and more to
the point a click-earner scanning the offerwall learns nothing from "MG".

So the ad's own headline comes first, then the label, and a very short
label is qualified with the destination host — "MG (mailer.gold)" — rather
than dropped or dressed up in marketing copy we invented on the member's
behalf. Nothing usable at all returns null instead of a made-up title.

Found by the backfill: campaign 88 failed on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-18 06:52:24 -05:00
parent d4e2f59f9b
commit 71ab9555e2
2 changed files with 28 additions and 2 deletions
+20 -2
View File
@@ -108,6 +108,23 @@ function countriesFor(geo, tiers) {
return [...new Set(out.map(c => String(c).toUpperCase()))].join(',');
}
// What a click-earner sees in the offerwall list. A campaign's `name` is the member's own
// internal label and is often too short to mean anything to a stranger ("MG"), so the ad's
// headline comes first, then the label, and a very short label is qualified with the
// destination host rather than dropped or dressed up in copy we invented for them. The
// platform's own floor is five characters.
function titleFor(campaign, url) {
const clean = v => String(v || '').replace(/\s+/g, ' ').trim();
const head = clean(campaign.title), label = clean(campaign.name);
let host = '';
try { host = new URL(url).hostname.replace(/^www\./, ''); } catch (e) {}
const pick = head.length >= 5 ? head
: label.length >= 5 ? label
: label && host ? label + ' (' + host + ')'
: host;
return pick.length >= 5 ? pick.slice(0, 250) : null;
}
// Push a campaign. Returns null when it is not worth syndicating (or cannot be targeted
// faithfully) rather than throwing, so the caller treats "too small", "wrong format" and
// "disabled" the same quiet way.
@@ -125,7 +142,8 @@ async function push(campaign, opts) {
const countries = countriesFor(campaign.geo, (opts && opts.tiers) || null);
if (countries === null) return null;
const title = String(campaign.name || campaign.title || 'InstantAdPay').slice(0, 250);
const title = titleFor(campaign, url);
if (!title) return null;
// The offerwall shows this line under the title, so it has to read like an offer rather
// than like an internal campaign record.
const description = String(campaign.title || campaign.body || campaign.name || title).slice(0, 250);
@@ -156,6 +174,6 @@ async function remove(campaignId) {
}
async function packs() { return enabled() ? call('packs', {}) : null; }
module.exports = { enabled, push, readServed, pause, resume, remove, packs,
module.exports = { enabled, push, readServed, pause, resume, remove, packs, titleFor,
clicksFor, countriesFor, kindOk, refFor,
MIN_CREDITS, MAX_CLICKS, MIN_CLICKS, PACK_ID, KINDS };