LinkSpin test area: InstantAdPay engine fork rebranded, network registry, sponsor carry-over with engine activation and claim window, rotator with /r/ redirects, link-domain mini-sites
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
// sendy.js — silent newsletter opt-in via Sendy.
|
||||
// API key from SENDY_API_KEY env, else DATA_DIR/sendy.key on the volume (same
|
||||
// pattern as sendgrid.key). URL + list have safe non-secret defaults (the
|
||||
// hashed list id is public — it appears in Sendy's own subscribe-form HTML).
|
||||
// subscribe() is fire-and-forget and never throws: a Sendy hiccup must never
|
||||
// break signup. boolean=true = silent (no confirmation email); Sendy itself
|
||||
// refuses unsubscribed/bounced addresses, so opt-out always wins.
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const DATA_DIR = process.env.DATA_DIR || path.join(__dirname, 'data');
|
||||
const URL_BASE = (process.env.SENDY_URL || 'https://valuedreply.xyz').replace(/\/+$/, '');
|
||||
const LIST = process.env.SENDY_LIST || 'W892hm1pgk3pIiK7OBYfHTWw'; // LinkSpin Newsletter (brand 3)
|
||||
|
||||
function apiKey() {
|
||||
if (process.env.SENDY_API_KEY) return process.env.SENDY_API_KEY.trim();
|
||||
try { return fs.readFileSync(path.join(DATA_DIR, 'sendy.key'), 'utf8').trim(); } catch (e) { return ''; }
|
||||
}
|
||||
function enabled() { return !!apiKey(); }
|
||||
|
||||
function subscribe(email, name) {
|
||||
const key = apiKey();
|
||||
const e = String(email || '').trim();
|
||||
if (!key || !e) return Promise.resolve(false);
|
||||
const body = new URLSearchParams({ api_key: key, list: LIST, email: e, name: String(name || ''), boolean: 'true' }).toString();
|
||||
const u = new URL(URL_BASE + '/subscribe');
|
||||
return new Promise(resolve => {
|
||||
const req = https.request({ hostname: u.hostname, path: u.pathname, method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(body) }, timeout: 10000 },
|
||||
res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(/^(1|true|already)/i.test(d.trim()))); });
|
||||
req.on('error', () => resolve(false));
|
||||
req.on('timeout', () => { req.destroy(); resolve(false); });
|
||||
req.end(body);
|
||||
});
|
||||
}
|
||||
|
||||
// 'Subscribed' | 'Unsubscribed' | 'Unconfirmed' | 'Bounced' | 'Soft bounced' | 'Complained' | 'Email does not exist in list' | '' (no key / unreachable)
|
||||
function status(email) {
|
||||
const key = apiKey(); const e = String(email || '').trim();
|
||||
if (!key || !e) return Promise.resolve('');
|
||||
const body = new URLSearchParams({ api_key: key, email: e, list_id: LIST }).toString();
|
||||
const u = new URL(URL_BASE + '/api/subscribers/subscription-status.php');
|
||||
return new Promise(resolve => {
|
||||
const req = https.request({ hostname: u.hostname, path: u.pathname, method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(body) }, timeout: 10000 },
|
||||
res => { let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d.trim().slice(0, 60))); });
|
||||
req.on('error', () => resolve('')); req.on('timeout', () => { req.destroy(); resolve(''); }); req.end(body);
|
||||
});
|
||||
}
|
||||
module.exports = { subscribe, enabled, status };
|
||||
Reference in New Issue
Block a user