282 lines
16 KiB
Python
282 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate creatives for CX3 offers - with mandatory [unsub] link"""
|
|
import psycopg2, base64, hashlib, os, random
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
from io import BytesIO
|
|
|
|
DB = dict(host='127.0.0.1', port=5432, dbname='adx_system', user='postgres', password='wevads2026')
|
|
MEDIA_DIR = '/opt/wevads-arsenal/public/media'
|
|
MEDIA_URL = 'http://89.167.40.150:5821/media'
|
|
|
|
# Brand configs for CX3 offers
|
|
BRANDS = {
|
|
'GlucoTrust': {'bg':'#1B5E20','fg':'#FFFFFF','accent':'#4CAF50','product':'GlucoTrust Supplement','badge':'GRATIS','lang':'de'},
|
|
'Trimology': {'bg':'#4A148C','fg':'#FFFFFF','accent':'#CE93D8','product':'Trimology Schlankheitskur','badge':'GRATIS','lang':'de'},
|
|
'Wellnee': {'bg':'#0D47A1','fg':'#FFFFFF','accent':'#42A5F5','product':'Wellnee Schmerzpflaster','badge':'GRATIS','lang':'de'},
|
|
'Lulutox': {'bg':'#E65100','fg':'#FFFFFF','accent':'#FF9800','product':'Lulutox Detox Tee','badge':'GRATIS','lang':'de'},
|
|
'Amazon Music': {'bg':'#232F3E','fg':'#FFFFFF','accent':'#FF9900','product':'Amazon Music Unlimited','badge':'KOSTENLOS','lang':'de'},
|
|
'Enence': {'bg':'#1565C0','fg':'#FFFFFF','accent':'#FFD600','product':'Enence Translator','badge':'GRATIS','lang':'de'},
|
|
'Synoshi': {'bg':'#006064','fg':'#FFFFFF','accent':'#00BCD4','product':'Synoshi Power Scrubber','badge':'FREE','lang':'en'},
|
|
'Derila': {'bg':'#311B92','fg':'#FFFFFF','accent':'#B388FF','product':'Derila Memory Pillow','badge':'FREE','lang':'en'},
|
|
'Ryoko': {'bg':'#BF360C','fg':'#FFFFFF','accent':'#FF6E40','product':'Ryoko Pro WiFi','badge':'FREE','lang':'en'},
|
|
'Irish Wins': {'bg':'#1B5E20','fg':'#FFFFFF','accent':'#FFD700','product':'Irish Wins Casino','badge':'FREE SPINS','lang':'en'},
|
|
'Doddle Car': {'bg':'#263238','fg':'#FFFFFF','accent':'#4CAF50','product':'Doddle Car Deals','badge':'FREE QUOTE','lang':'en'},
|
|
'HydroSoothe': {'bg':'#01579B','fg':'#FFFFFF','accent':'#29B6F6','product':'HydroSoothe Patches','badge':'FREE','lang':'en'},
|
|
'Akusoli': {'bg':'#3E2723','fg':'#FFFFFF','accent':'#8D6E63','product':'Akusoli Insoles','badge':'FREE','lang':'en'},
|
|
'Bonus Monster': {'bg':'#880E4F','fg':'#FFD700','accent':'#E91E63','product':'Bonus Monster Casino','badge':'FREE SPINS','lang':'en'},
|
|
'DateHotGirls': {'bg':'#B71C1C','fg':'#FFFFFF','accent':'#FF5252','product':'DateHotGirls','badge':'FREE','lang':'en'},
|
|
'MEDVi': {'bg':'#004D40','fg':'#FFFFFF','accent':'#26A69A','product':'MEDVi GLP-1','badge':'50% OFF','lang':'en'},
|
|
}
|
|
DEFAULT = {'bg':'#37474F','fg':'#FFFFFF','accent':'#FF5722','product':'Special Offer','badge':'FREE','lang':'en'}
|
|
|
|
# Email templates per language
|
|
TEMPLATES = {
|
|
'de': [
|
|
# V1: Urgency
|
|
"""<html><head><meta charset="utf-8"><title>{product}</title></head>
|
|
<body style="margin:0;padding:0;font-family:Arial,sans-serif;background:#f4f4f4;">
|
|
<div style="max-width:600px;margin:0 auto;background:#ffffff;">
|
|
<div style="background:{bg};padding:20px;text-align:center;">
|
|
<h1 style="color:{fg};margin:0;font-size:24px;">⚡ Letzte Chance: {product}</h1>
|
|
</div>
|
|
<div style="text-align:center;padding:15px;"><img src="{img_url}" width="250" style="max-width:250px;" alt="" /></div>
|
|
<div style="padding:20px 30px;">
|
|
<p style="font-size:16px;line-height:1.6;color:#333;">Guten Tag,</p>
|
|
<p style="font-size:16px;line-height:1.6;color:#333;">Wir haben eine <strong>exklusive Einladung</strong> für Sie vorbereitet. Nur heute erhalten Sie Zugang zu unserem {product} Angebot — <strong>komplett kostenlos</strong>.</p>
|
|
<p style="font-size:16px;line-height:1.6;color:#333;">Dieses Angebot läuft in <strong>24 Stunden</strong> ab.</p>
|
|
<div style="text-align:center;padding:20px;">
|
|
<a href="[url]" style="background:{accent};color:{fg};padding:15px 40px;text-decoration:none;border-radius:5px;font-size:18px;font-weight:bold;display:inline-block;">JETZT SICHERN →</a>
|
|
</div>
|
|
<p style="font-size:12px;color:#999;text-align:center;">Werbung | <a href="[unsub]" style="color:#999;">Abmelden</a></p>
|
|
</div></div>
|
|
<img src="[open]" width="1" height="1" style="display:none;" alt="" />
|
|
</body></html>""",
|
|
# V2: Personal
|
|
"""<html><head><meta charset="utf-8"><title>{product}</title></head>
|
|
<body style="margin:0;padding:0;font-family:Georgia,serif;background:#fafafa;">
|
|
<div style="max-width:600px;margin:0 auto;background:#ffffff;border:1px solid #e0e0e0;">
|
|
<div style="padding:25px 30px;border-bottom:3px solid {accent};">
|
|
<h2 style="color:{bg};margin:0;">Ihr persönliches Angebot</h2>
|
|
</div>
|
|
<div style="text-align:center;padding:15px;"><img src="{img_url}" width="250" style="max-width:250px;" alt="" /></div>
|
|
<div style="padding:20px 30px;">
|
|
<p style="font-size:16px;line-height:1.7;color:#444;">Liebe Leserin, lieber Leser,</p>
|
|
<p style="font-size:16px;line-height:1.7;color:#444;">Wir möchten Ihnen den <strong>{product}</strong> vorstellen — ein Produkt, das bereits tausende zufriedene Kunden überzeugt hat.</p>
|
|
<p style="font-size:16px;line-height:1.7;color:#444;">Bestellen Sie jetzt und erhalten Sie <strong>kostenlosen Versand</strong> als Dankeschön.</p>
|
|
<div style="text-align:center;padding:20px;">
|
|
<a href="[url]" style="background:{bg};color:{fg};padding:14px 35px;text-decoration:none;border-radius:3px;font-size:16px;display:inline-block;">Angebot ansehen</a>
|
|
</div>
|
|
<p style="font-size:11px;color:#aaa;text-align:center;border-top:1px solid #eee;padding-top:15px;">Dies ist eine Werbemitteilung. <a href="[unsub]" style="color:#aaa;">Hier abmelden</a></p>
|
|
</div></div>
|
|
<img src="[open]" width="1" height="1" style="display:none;" alt="" />
|
|
</body></html>""",
|
|
# V3: News-style
|
|
"""<html><head><meta charset="utf-8"><title>{product}</title></head>
|
|
<body style="margin:0;padding:0;font-family:Verdana,sans-serif;background:#f0f0f0;">
|
|
<div style="max-width:600px;margin:0 auto;background:#ffffff;">
|
|
<div style="background:#ffffff;padding:15px 20px;border-bottom:2px solid {accent};">
|
|
<span style="color:{bg};font-size:12px;font-weight:bold;text-transform:uppercase;">GESUNDHEIT & WOHLBEFINDEN</span>
|
|
</div>
|
|
<div style="padding:20px;">
|
|
<h2 style="color:#222;font-size:20px;margin:0 0 15px;">{product}: Warum alle darüber sprechen</h2>
|
|
<div style="text-align:center;padding:10px;"><img src="{img_url}" width="250" style="max-width:250px;" alt="" /></div>
|
|
<p style="font-size:15px;line-height:1.6;color:#555;">Experten empfehlen jetzt den {product} als natürliche Lösung. Mehr als <strong>50.000 Kunden</strong> in Deutschland vertrauen bereits darauf.</p>
|
|
<p style="font-size:15px;line-height:1.6;color:#555;">Für kurze Zeit gibt es das Produkt mit einem <strong>Sonderrabatt</strong>.</p>
|
|
<div style="text-align:center;padding:15px;">
|
|
<a href="[url]" style="background:{accent};color:#ffffff;padding:12px 30px;text-decoration:none;border-radius:4px;font-size:15px;display:inline-block;">Mehr erfahren →</a>
|
|
</div>
|
|
</div>
|
|
<div style="background:#f5f5f5;padding:10px;text-align:center;">
|
|
<p style="font-size:10px;color:#999;margin:0;">Gesponsert | <a href="[unsub]" style="color:#999;">Newsletter abbestellen</a></p>
|
|
</div></div>
|
|
<img src="[open]" width="1" height="1" style="display:none;" alt="" />
|
|
</body></html>""",
|
|
],
|
|
'en': [
|
|
# V1: Urgency
|
|
"""<html><head><meta charset="utf-8"><title>{product}</title></head>
|
|
<body style="margin:0;padding:0;font-family:Arial,sans-serif;background:#f4f4f4;">
|
|
<div style="max-width:600px;margin:0 auto;background:#ffffff;">
|
|
<div style="background:{bg};padding:20px;text-align:center;">
|
|
<h1 style="color:{fg};margin:0;font-size:24px;">⚡ Limited Time: {product}</h1>
|
|
</div>
|
|
<div style="text-align:center;padding:15px;"><img src="{img_url}" width="250" style="max-width:250px;" alt="" /></div>
|
|
<div style="padding:20px 30px;">
|
|
<p style="font-size:16px;line-height:1.6;color:#333;">Hi there,</p>
|
|
<p style="font-size:16px;line-height:1.6;color:#333;">We have an <strong>exclusive invitation</strong> for you. Today only, get access to our {product} deal — <strong>completely free</strong>.</p>
|
|
<p style="font-size:16px;line-height:1.6;color:#333;">This offer expires in <strong>24 hours</strong>.</p>
|
|
<div style="text-align:center;padding:20px;">
|
|
<a href="[url]" style="background:{accent};color:{fg};padding:15px 40px;text-decoration:none;border-radius:5px;font-size:18px;font-weight:bold;display:inline-block;">CLAIM NOW →</a>
|
|
</div>
|
|
<p style="font-size:12px;color:#999;text-align:center;">Ad | <a href="[unsub]" style="color:#999;">Unsubscribe</a></p>
|
|
</div></div>
|
|
<img src="[open]" width="1" height="1" style="display:none;" alt="" />
|
|
</body></html>""",
|
|
# V2: Personal
|
|
"""<html><head><meta charset="utf-8"><title>{product}</title></head>
|
|
<body style="margin:0;padding:0;font-family:Georgia,serif;background:#fafafa;">
|
|
<div style="max-width:600px;margin:0 auto;background:#ffffff;border:1px solid #e0e0e0;">
|
|
<div style="padding:25px 30px;border-bottom:3px solid {accent};">
|
|
<h2 style="color:{bg};margin:0;">Your Personal Offer</h2>
|
|
</div>
|
|
<div style="text-align:center;padding:15px;"><img src="{img_url}" width="250" style="max-width:250px;" alt="" /></div>
|
|
<div style="padding:20px 30px;">
|
|
<p style="font-size:16px;line-height:1.7;color:#444;">Dear Reader,</p>
|
|
<p style="font-size:16px;line-height:1.7;color:#444;">We'd like to introduce the <strong>{product}</strong> — a product that thousands of satisfied customers already trust.</p>
|
|
<p style="font-size:16px;line-height:1.7;color:#444;">Order now and get <strong>free shipping</strong> as our thank you.</p>
|
|
<div style="text-align:center;padding:20px;">
|
|
<a href="[url]" style="background:{bg};color:{fg};padding:14px 35px;text-decoration:none;border-radius:3px;font-size:16px;display:inline-block;">View Offer</a>
|
|
</div>
|
|
<p style="font-size:11px;color:#aaa;text-align:center;border-top:1px solid #eee;padding-top:15px;">This is a promotional message. <a href="[unsub]" style="color:#aaa;">Unsubscribe here</a></p>
|
|
</div></div>
|
|
<img src="[open]" width="1" height="1" style="display:none;" alt="" />
|
|
</body></html>""",
|
|
# V3: News-style
|
|
"""<html><head><meta charset="utf-8"><title>{product}</title></head>
|
|
<body style="margin:0;padding:0;font-family:Verdana,sans-serif;background:#f0f0f0;">
|
|
<div style="max-width:600px;margin:0 auto;background:#ffffff;">
|
|
<div style="background:#ffffff;padding:15px 20px;border-bottom:2px solid {accent};">
|
|
<span style="color:{bg};font-size:12px;font-weight:bold;text-transform:uppercase;">HEALTH & WELLNESS</span>
|
|
</div>
|
|
<div style="padding:20px;">
|
|
<h2 style="color:#222;font-size:20px;margin:0 0 15px;">{product}: Why Everyone Is Talking About It</h2>
|
|
<div style="text-align:center;padding:10px;"><img src="{img_url}" width="250" style="max-width:250px;" alt="" /></div>
|
|
<p style="font-size:15px;line-height:1.6;color:#555;">Experts are now recommending {product} as a natural solution. Over <strong>50,000 customers</strong> already trust it.</p>
|
|
<p style="font-size:15px;line-height:1.6;color:#555;">For a limited time, get a <strong>special discount</strong>.</p>
|
|
<div style="text-align:center;padding:15px;">
|
|
<a href="[url]" style="background:{accent};color:#ffffff;padding:12px 30px;text-decoration:none;border-radius:4px;font-size:15px;display:inline-block;">Learn More →</a>
|
|
</div>
|
|
</div>
|
|
<div style="background:#f5f5f5;padding:10px;text-align:center;">
|
|
<p style="font-size:10px;color:#999;margin:0;">Sponsored | <a href="[unsub]" style="color:#999;">Unsubscribe from newsletter</a></p>
|
|
</div></div>
|
|
<img src="[open]" width="1" height="1" style="display:none;" alt="" />
|
|
</body></html>""",
|
|
]
|
|
}
|
|
|
|
SUBJECTS_DE = [
|
|
"Ihr {product} wartet auf Sie", "Exklusiv: {product} kostenlos testen", "⚡ Letzte Chance: {product}",
|
|
"Gesundheitsexperten empfehlen: {product}", "Nur heute: {product} gratis", "{product} — Sonderangebot",
|
|
"Neu für Sie: {product}", "Verpassen Sie nicht: {product}", "🎁 {product} geschenkt",
|
|
"Kostenloses {product} Angebot"
|
|
]
|
|
SUBJECTS_EN = [
|
|
"Your {product} is waiting", "Exclusive: Try {product} free", "⚡ Last chance: {product}",
|
|
"Experts recommend: {product}", "Today only: Free {product}", "{product} — Special offer",
|
|
"New for you: {product}", "Don't miss: {product}", "🎁 Free {product}",
|
|
"Complimentary {product} offer"
|
|
]
|
|
FROM_NAMES = [
|
|
"Health News", "Wellness Daily", "Special Offers", "Your Rewards", "Member Services",
|
|
"Product Alert", "Daily Deals", "Exclusive Club", "Savings Digest", "Offer Central"
|
|
]
|
|
|
|
def hex_to_rgb(h):
|
|
h = h.lstrip('#')
|
|
return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
|
|
|
|
def make_image(brand):
|
|
w, h = 580, 200
|
|
img = Image.new('RGB', (w, h), hex_to_rgb(brand['bg']))
|
|
d = ImageDraw.Draw(img)
|
|
try:
|
|
fl = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 28)
|
|
fs = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 18)
|
|
except:
|
|
fl = fs = ImageFont.load_default()
|
|
|
|
d.rectangle([0,0,w,6], fill=hex_to_rgb(brand['accent']))
|
|
bb = d.textbbox((0,0), brand['product'], font=fl)
|
|
d.text(((w-(bb[2]-bb[0]))//2, 40), brand['product'], fill=hex_to_rgb(brand['fg']), font=fl)
|
|
d.rounded_rectangle([220,125,360,165], radius=20, fill=hex_to_rgb(brand['accent']))
|
|
bb2 = d.textbbox((0,0), brand['badge'], font=fs)
|
|
d.text(((w-(bb2[2]-bb2[0]))//2, 132), brand['badge'], fill=hex_to_rgb(brand['fg']), font=fs)
|
|
d.rectangle([0,h-4,w,h], fill=hex_to_rgb(brand['accent']))
|
|
|
|
buf = BytesIO()
|
|
img.save(buf, format='PNG', optimize=True)
|
|
return buf.getvalue()
|
|
|
|
def save_img(data):
|
|
h = hashlib.md5(data).hexdigest()[:15]
|
|
fp = os.path.join(MEDIA_DIR, f"{h}.png")
|
|
if not os.path.exists(fp):
|
|
with open(fp, 'wb') as f: f.write(data)
|
|
os.chmod(fp, 0o644)
|
|
return f"{MEDIA_URL}/{h}.png"
|
|
|
|
def get_brand(name):
|
|
for k, v in BRANDS.items():
|
|
if k.lower() in name.lower():
|
|
return v
|
|
return DEFAULT
|
|
|
|
def main():
|
|
conn = psycopg2.connect(**DB)
|
|
conn.autocommit = True
|
|
cur = conn.cursor()
|
|
|
|
# Get CX3 offers needing creatives
|
|
cur.execute("""
|
|
SELECT o.id, o.name, o.countries FROM affiliate.offers o
|
|
JOIN admin.brain_offer_config bc ON bc.offer_id=o.id
|
|
WHERE bc.is_active=true AND bc.is_approved=true AND bc.link_status='live'
|
|
AND bc.good_creatives=0 AND o.affiliate_network_name='CX3 Ads'
|
|
AND o.name NOT LIKE '%Test Offer%'
|
|
ORDER BY CAST(REPLACE(REPLACE(o.payout,'$',''),'%','') AS NUMERIC) DESC
|
|
""")
|
|
offers = cur.fetchall()
|
|
|
|
cur.execute("SELECT MAX(id) FROM affiliate.creatives")
|
|
next_cid = (cur.fetchone()[0] or 0) + 1
|
|
|
|
total_created = 0
|
|
for oid, oname, ocountries in offers:
|
|
brand = get_brand(oname)
|
|
lang = brand['lang']
|
|
|
|
# Generate image
|
|
png = make_image(brand)
|
|
img_url = save_img(png)
|
|
|
|
templates = TEMPLATES[lang]
|
|
subjects = SUBJECTS_DE if lang == 'de' else SUBJECTS_EN
|
|
|
|
for i, tmpl in enumerate(templates):
|
|
html = tmpl.format(
|
|
product=brand['product'], bg=brand['bg'], fg=brand['fg'],
|
|
accent=brand['accent'], img_url=img_url
|
|
)
|
|
b64 = base64.b64encode(html.encode('utf-8')).decode('utf-8')
|
|
|
|
vname = f"{brand['product']}_v{i+1}"
|
|
subj = random.choice(subjects).format(product=brand['product'])
|
|
fname = random.choice(FROM_NAMES)
|
|
|
|
cur.execute(
|
|
"INSERT INTO affiliate.creatives(id, offer_id, name, value, status, quality_score, "
|
|
"has_tracking_placeholders, affiliate_network_id, created_by, created_date) "
|
|
"VALUES (%s, %s, %s, %s, 'Activated', 8, true, 7, 'brain', CURRENT_DATE)",
|
|
(next_cid, oid, vname, b64)
|
|
)
|
|
next_cid += 1
|
|
total_created += 1
|
|
|
|
# Update good_creatives count
|
|
cur.execute("UPDATE admin.brain_offer_config SET good_creatives=3 WHERE offer_id=%s", (oid,))
|
|
print(f" ✅ Offer #{oid} {brand['product'][:25]} ({lang}) → 3 creatives + image")
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"TOTAL: {total_created} creatives for {len(offers)} CX3 offers")
|
|
|
|
cur.execute("SELECT COUNT(*) FROM affiliate.creatives WHERE status='Activated' AND quality_score>=3 AND has_tracking_placeholders=true")
|
|
total = cur.fetchone()[0]
|
|
print(f"Total good creatives in system: {total}")
|
|
|
|
cur.close()
|
|
conn.close()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|