Files
BSN-Chatsystem/scripts/kombi_deals.py
T

144 lines
6.9 KiB
Python

#!/usr/bin/env python3
"""Kombi-Script: Brettspiel-Deals + Multi-Kategorie. Ausführung bei jeder Top-30-Anfrage."""
import os, time
from datetime import datetime
exec(open('/home/hermes/.hermes/skills/finance/amazon-brettspiel-deals/scripts/search_deals.py').read().split('RESOURCES')[0])
from amazon_creatorsapi import AmazonCreatorsApi, Country
from amazon_creatorsapi import models as m
PARTNER_TAG = "60pro05-21"
COUNTRY = Country.DE
R_COMMON = [
m.SearchItemsResource.ITEM_INFO_DOT_TITLE,
m.SearchItemsResource.IMAGES_DOT_PRIMARY_DOT_LARGE,
m.SearchItemsResource.OFFERS_V2_DOT_LISTINGS_DOT_PRICE,
m.SearchItemsResource.ITEM_INFO_DOT_BY_LINE_INFO,
]
MULTI_CATEGORIES = [
("Heißluftfritteusen", ["Heißluftfritteuse", "Airfryer", "Air Fryer"], 10, None),
("Staubsauger & Wischroboter", ["Staubsauger", "Saugroboter", "Wischsauger", "Akkusauger"], 10, None),
("Laptops & Notebooks", ["Laptop", "Notebook"], 5, None),
("Apple-Produkte", ["Apple iPhone", "Apple iPad", "Apple Watch", "Apple AirPods"], 5, None),
("Tablets", ["Tablet"], 5, None),
("Smartphones", ["Smartphone"], 5, None),
]
def search_deals(api, keywords, count, node=None, with_savings=True):
deals, seen = [], set()
for kw in keywords:
if len(deals) >= count * 3: break
for page in range(1, 5):
if len(deals) >= count * 3: break
try:
kwargs = dict(keywords=kw, item_count=50, item_page=page, resources=R_COMMON)
if node:
kwargs["browse_node_id"] = node
resp = api.search_items(**kwargs)
time.sleep(1.1)
if not resp or not resp.items: continue
for item in resp.items:
if len(deals) >= count * 3: break
try:
asin = str(item.asin)
if asin in seen: continue
seen.add(asin)
title = str(item.item_info.title.display_value)
brand = str(item.item_info.by_line_info.brand.display_value) if item.item_info.by_line_info and item.item_info.by_line_info.brand else ""
if not item.offers_v2 or not item.offers_v2.listings: continue
li = item.offers_v2.listings[0]
if not li.price or not li.price.money: continue
pr = float(li.price.money.amount)
sp, se, lp = 0, 0, pr
if li.price.savings and li.price.savings.money:
se = float(li.price.savings.money.amount)
sp = int(li.price.savings.percentage)
if li.price.saving_basis and li.price.saving_basis.money:
lp = float(li.price.saving_basis.money.amount)
if not with_savings or sp >= 10:
img = ""
if item.images and item.images.primary and item.images.primary.large:
img = str(item.images.primary.large.url)
deals.append({"t":title,"b":brand,"img":img,"pr":pr,"lp":lp,"sp":sp,"se":se,
"url":f"https://www.amazon.de/dp/{asin}?tag={PARTNER_TAG}"})
except: continue
except Exception as e:
print(f" E {kw}: {e}")
if with_savings:
deals.sort(key=lambda x: x["sp"], reverse=True)
return deals[:count]
def generate_combined_html(board_deal_html_path, multi_results, output_path):
# Read board game HTML
with open(board_deal_html_path) as f:
board_html = f.read()
# Build multi-category sections
now = datetime.now().strftime("%d.%m.%Y, %H:%M")
sec = ""
for cat_name, deals in multi_results.items():
if not deals: continue
label = f"{cat_name} ({len(deals)})"
sec += f'<h2 style="margin-top:2.5rem;color:#c75b3a;border-bottom:2px solid #e8e0d3;padding-bottom:0.3rem;">{label}</h2>\n'
for d in deals:
img = f'<img src="{d["img"]}" style="width:100px;height:100px;object-fit:contain;" onerror="this.style.display=\'none\'">' if d["img"] else ""
br = f'<br><span style="color:#888;font-size:0.85rem;">{d["b"]}</span>' if d["b"] else ""
pct = f'-{d["sp"]}%' if d["sp"] > 0 else ''
saved = f'({d["se"]:.2f} €)' if d["se"] > 0 else ''
has_deal = d["sp"] > 0
uvp_line = f'<span style="text-decoration:line-through;color:#999;margin-left:0.5rem;">UVP {d["lp"]:.2f} €</span>' if has_deal else ''
badge_line = f'<span style="background:#b12704;color:#fff;padding:2px 6px;border-radius:3px;margin-left:0.5rem;font-size:0.85rem;">{pct}</span><span style="color:#b12704;margin-left:0.3rem;">{saved}</span>' if has_deal else ''
sec += f"""<div style="display:flex;gap:1rem;padding:1rem 0;border-bottom:1px solid #eee;">
{img}<div style="flex:1;"><strong>{d['t'][:120]}</strong>{br}<br>
<span style="color:#b12704;font-size:1.2rem;font-weight:bold;">{d['pr']:.2f} €</span>
{uvp_line}
{badge_line}<br>
<a href="{d['url']}" target="_blank" style="color:#007185;">Zu Amazon →</a></div></div>\n"""
footer_marker = '<p class="meta" style="margin-top:2rem;">'
combined = board_html.replace(footer_marker, sec + footer_marker)
with open(output_path, 'w') as f:
f.write(combined)
return output_path
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--board-html', type=str, required=True, help='Path to board game deals HTML')
parser.add_argument('--output', type=str, default=None)
args = parser.parse_args()
if args.output is None:
args.output = os.path.expanduser(f"~/workspace/kombi_deals_{datetime.now().strftime('%Y-%m-%d')}.html")
api = AmazonCreatorsApi(CREDENTIAL_ID, CREDENTIAL_SECRET, "3.2", PARTNER_TAG, country=COUNTRY, throttling=1.0)
print("Connected!\n")
results = {}
for cat_name, keywords, count, node in MULTI_CATEGORIES:
print(f"--- {cat_name} ---")
deals = search_deals(api, keywords, count, node, with_savings=True)
results[cat_name] = deals
print(f" => {len(deals)}/{count}")
for d in deals:
print(f" -{d['sp']}% | {d['pr']:.2f}€ | {d['t'][:80]}")
# Bücher: Top 5 Bestseller (keine Angebots-Filterung)
print("--- Bücher-Bestseller ---")
books = search_deals(api, ["Bestseller Roman", "Bestseller Buch", "Thriller Bestseller"], 5, "541686", with_savings=False)
results["Bücher-Bestseller (Top 5)"] = books
print(f" => {len(books)}/5")
for d in books:
print(f" {d['pr']:.2f}€ | {d['t'][:80]}")
output = generate_combined_html(args.board_html, results, args.output)
total = sum(len(v) for v in results.values())
print(f"\n{total} Non-Brettspiel-Deals hinzugefügt → {output}")
if __name__ == "__main__":
main()