Campaign refill notices: tell an advertiser when an ad is nearly out and when it has stopped, one email per owner with what it delivered, their balance and the bonus ladder; every send recorded and watched for the relaunch, top-up or purchase that follows
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,7 +39,8 @@ const audit = require('./audit'); // counter audit: views vs delivery logs,
|
||||
const leaderboard = require('./leaderboard');
|
||||
const toolkit = require('./toolkit');
|
||||
const badge = require('./badge'); // achievement badge image drawn on the server with ffmpeg (2026-09-16)
|
||||
const friday = require('./friday'); // Five Dollar Friday: +20% credits on Friday packs, the wave, the badge (Marty, 2026-09-20)
|
||||
const friday = require('./friday');
|
||||
const refill = require('./refill'); // campaign running-low / ran-out notices, and what came of them // Five Dollar Friday: +20% credits on Friday packs, the wave, the badge (Marty, 2026-09-20)
|
||||
const snapshot = require('./snapshot'); // daily growth snapshot -> Telegram payments feed (Marty, 2026-09-15)
|
||||
const pipeline = require('./pipeline'); // sponsor follow-up board (coming soon until site setting pipelineMode = on) (Marty, 2026-09-15)
|
||||
const videomaker = require('./videomaker'); // Circuit tool: promo videos with the member's own end card (ffmpeg in the image) // badge-gated promo toolkit + AI Copy Engine (Surge and up) (Marty, 2026-09-14) // referral contest: /leaderboard, Overview card, weekly + monthly winners (Marty, 2026-09-14) // release notes + roadmap: /whats-new, Overview card, Admin > Releases (Marty, 2026-09-14) // blog -> Blotato -> X + Instagram on publish (Marty, 2026-09-13) // admin member card: search, drilldown, edits (Marty, 2026-09-13) // admin-written coaching articles, server-rendered public /blog with SEO metadata (Marty, 2026-09-12)
|
||||
@@ -414,6 +415,15 @@ async function boot() {
|
||||
friday.init({ dataDir: DATA_DIR, siteConfig, chain, ads, accounts, mailer, drip,
|
||||
telegram: async text => { const sc = siteConfig(); if (!sc.telegramBotToken || !sc.telegramEchoChatId) return false; return telegramSend(sc.telegramEchoChatId, '\u{1F7E0} <b>InstantAdPay</b> \u00b7 ' + text, sc.telegramEchoTopicId); },
|
||||
notify: async (memberId, subject, text, kind) => { const a = await accounts.byMemberId(memberId); if (!a || !a.email) return; const html = '<p>' + String(text).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/(https:\/\/[^\s]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>').split('\n\n').join('</p><p>').replace(/\n/g, '<br>') + '</p>'; await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [a.email], subject, html, kind || 'notice'); } });
|
||||
// Campaign refill notices: an advertiser is told when an ad is nearly out and when it has stopped,
|
||||
// with the bonus ladder in the same message, and every send is watched for what they did next.
|
||||
refill.init({
|
||||
dataDir: DATA_DIR, siteConfig, ads, accounts, chain, mailer, drip,
|
||||
creditsFor: async email => { const v = await adminMember.view(email); return (v && v.credits && v.credits.available) || 0; },
|
||||
message: async (to, subject, html) => { await messages.deliver(1, ADMIN_EMAIL || 'house@instantadpay.com', [to], subject, html, 'notice'); },
|
||||
});
|
||||
setTimeout(() => refill.tick().catch(e => console.error('refill', e.message)), 90000);
|
||||
setInterval(() => refill.tick().catch(e => console.error('refill', e.message)), 20 * 60 * 1000);
|
||||
setInterval(() => friday.tick().catch(e => console.error('friday', e.message)), 5 * 60 * 1000);
|
||||
setInterval(() => snapshot.tick().catch(e => console.error('snapshot', e.message)), 10 * 60 * 1000);
|
||||
tank.init({ dataDir: DATA_DIR, chain, accounts, messages, mailer, site: 'https://instantadpay.com' });
|
||||
@@ -2898,6 +2908,14 @@ const server = http.createServer(async (req, res) => {
|
||||
if (!isAdmin(req)) return json(res, 200, { admin: false });
|
||||
return json(res, 200, { admin: true, email: ADMIN_EMAIL });
|
||||
}
|
||||
if (p === '/api/admin/refill' && req.method === 'GET') { // who was told their ads ran out, and what they did next
|
||||
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
|
||||
return json(res, 200, refill.stats());
|
||||
}
|
||||
if (p === '/api/admin/refill/run' && req.method === 'POST') { // send the next batch now instead of waiting for the tick
|
||||
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
|
||||
return json(res, 200, await refill.tick());
|
||||
}
|
||||
if (p === '/api/admin/overview' && req.method === 'GET') {
|
||||
if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
|
||||
const camps = await ads.adminList();
|
||||
|
||||
Reference in New Issue
Block a user