#!/usr/bin/env python3
"""StudioSoft Healthcare — run cron hc41 (20 Sep 2026 02:00 WIB / 19 Sep 19:00 UTC).

Topik Threads : "Bagaimana Data Analytics Membantu Rumah Sakit Memahami Kebutuhan Pasien"
                (artikel https://studiosoft.id/data-analytics-rumah-sakit-kebutuhan-pasien/
                 — RE-AIR: terakhir tayang di Threads SS 23 Agu 2026 ~4 minggu lalu,
                 belum masuk seri hc31-hc40) -> carousel BARU ss_hc41_analytics_*.
LinkedIn      : TEXT-only, artikel `panduan-seo-klinik-rumah-sakit` (di LinkedIn SS hanya
                pernah status ERROR 20 Agu -> belum pernah tayang, TIDAK ada di antrean LI).
Facebook      : TEXT-only tanpa media, replies=[], artikel `klinik-kecil-butuh-digital-marketing`
                (belum pernah dipakai di FB SS — status FREE di audit hc41).

ATURAN (FIXED 5 & 15 Sep 2026):
- HANYA THREADS SS yang boleh carousel/image. FB & LinkedIn SS: TEXT saja.
- IG SS tidak disentuh (punya desain sendiri).
- Carousel = 09:00 WIB. Threads <=500 char, hashtag huruf kecil, 0 mention, ada pancingan diskusi.
- Standar desain 16 Sep 2026: engine ss_carousel_lib.py, bg #122B63, rata kiri, blok tengah
  vertikal, tanpa separator, bullet dot, Poppins Bold 85 / Regular 36 / sub 30, auto-fit.
"""
import json, os, re, subprocess, sys
from datetime import datetime, timezone, timedelta

WS = "/home/mantapman/.hermes/profiles/atun/workspace"
sys.path.insert(0, WS)
from PIL import Image
from repliz_client import ReplizClient, ACC, IMG_DIR, IMG_BASE_URL
from diskusi_engine import tambahkan_diskusi

DRY = os.environ.get("DRY", "") == "1"
c = ReplizClient()

TITLE = "Bagaimana Data Analytics Membantu Rumah Sakit Memahami Kebutuhan Pasien"
ARTICLE_URL = "https://studiosoft.id/data-analytics-rumah-sakit-kebutuhan-pasien/"
LI_TITLE = "Panduan SEO untuk Klinik dan Rumah Sakit: Biar Mudah Ditemukan Pasien di Google"
LI_URL = "https://studiosoft.id/panduan-seo-klinik-rumah-sakit/"
FB_TITLE = "Mengapa Klinik Kecil Juga Butuh Digital Marketing \u2014 Mitos dan Fakta"
FB_URL = "https://studiosoft.id/klinik-kecil-butuh-digital-marketing/"
HASHTAG_LINE = "#studiosoft #dataanalytics #rumahsakit #klinik #kesehatan"

MEDIA_FILES = [
    "ss_hc41_analytics_01_cover.jpg",
    "ss_hc41_analytics_02_data.jpg",
    "ss_hc41_analytics_03_contoh.jpg",
    "ss_hc41_analytics_04_langkah.jpg",
    "ss_hc41_analytics_05_cta.jpg",
]
SRC_PNG = os.path.join(WS, "studiosoft_hc41_carousel")
MEDIA_URLS = [IMG_BASE_URL.rstrip("/") + "/openclaw-img/" + f for f in MEDIA_FILES]
INGRESS = ["103.84.155.153", "103.84.155.217"]

THREADS_BASE = """RS dan klinik menghasilkan data setiap hari: kunjungan website, booking, review, sampai hasil iklan.

Masalahnya bukan kurang data \u2014 datanya jarang dibaca. Padahal di situ tersimpan jawaban: apa yang sebenarnya dibutuhkan pasien?

""" + ARTICLE_URL

LI_BASE = """Pasien baru pindah rumah di Surabaya, anaknya batuk, dan ia butuh dokter terdekat. Yang ia lakukan cuma satu: buka Google, ketik \u201cklinik terdekat\u201d, lalu pilih satu dari sepuluh hasil pertama.

Di situ letak persoalannya. Selama mendampingi RS dan klinik, saya sering melihat layanan yang bagus kalah bukan karena mutunya kurang, tapi karena tidak muncul di halaman pertama \u2014 padahal pasiennya mencari setiap hari.

Ada empat hal yang paling menentukan.

Pertama, riset kata kunci yang khas kesehatan. Pasien mencari dengan niat yang sangat spesifik: \u201cdokter spesialis jantung Surabaya\u201d (cari dokter tertentu), \u201cbiaya operasi katarak\u201d (cari informasi harga), \u201cklinik gigi terdekat\u201d (ingin datang hari ini), sampai \u201cgejala diabetes gestasional\u201d (baru cari edukasi). Konten yang baik harus melayani semuanya, dari yang informatif sampai yang transaksional.

Kedua, local SEO sebagai prioritas. Pasien memilih layanan di sekitar lokasi mereka, jadi Google Business Profile harus lengkap, nama-alamat-telepon konsisten di semua platform, review dikelola dengan baik, dan RS terdaftar di direktori kesehatan serta Google Maps.

Ketiga, konten lokal. Artikel layanan yang menyebutkan kota dan area membuat RS relevan untuk pencarian bergaya \u201cdekat saya\u201d.

Keempat, fondasi teknis: kecepatan website, struktur URL yang bersih, schema markup untuk dokter dan layanan, serta versi mobile yang nyaman. Tanpa ini, konten terbaik pun sulit naik.

SEO bukan biaya sekali pakai \u2014 ia bekerja 24 jam dan tidak menagih per klik. Yang dibutuhkan konsistensi dan kesabaran.

Bacaan lengkapnya: """ + LI_URL

FB_BASE = """Pertanyaan yang sering saya dengar dari pemilik klinik kecil: \u201cKlinik saya kecil, apa perlu digital marketing?\u201d

Justru sebaliknya. Klinik kecil paling diuntungkan \u2014 biaya lebih rendah, jangkauan lebih tepat, dan hasilnya lebih cepat terasa.

Empat anggapan yang perlu diluruskan:

Pertama, \u201cpasien cukup dari mulut ke mulut\u201d. Mulut ke mulut sekarang terjadi di Google Review dan grup Facebook. Satu ulasan positif bisa dibaca ribuan orang.

Kedua, \u201cdigital marketing itu mahal\u201d. Bisa dimulai dari Rp0: optimasi Google Business Profile, konten Instagram, dan SEO. Iklan Facebook pun bisa mulai dari Rp20.000 per hari.

Ketiga, \u201cpasien tidak mencari klinik kecil di internet\u201d. Pasien mencari solusi, bukan ukuran klinik. Untuk pencarian seperti \u201cklinik gigi terdekat\u201d, klinik kecil yang rajin SEO lokal bisa mengalahkan RS besar di radius tertentu.

Keempat, \u201ctidak punya waktu mengurus media sosial\u201d. Kanal digital bisa dikelola tim profesional \u2014 biasanya lebih murah daripada menggaji satu staf marketing tetap.

Langkah awalnya sederhana: optimalkan Google Business Profile, kelola Instagram dengan konten edukasi, pasang WhatsApp Business, mulai blog sederhana, dan minta review dari pasien yang puas.

Bacaan lengkapnya: """ + FB_URL

CTA_RE = re.compile(r"komentar|pendapat|diskusi|setuju|share|pengalaman|menurut|bagaimana|apa yang|komen|tanggapan|pandangan", re.I)
BAN_RE = re.compile(r"kecantikan|skincare|jerawat|sembuh total", re.I)
# Pancingan diskusi KURASI (on-topic: sudut pandang manajemen RS/klinik, bukan sudut pasien).
# Tone per platform dipilih supaya bauran 40/30/30 (hc40 semua netral -> run ini opini+netral+sanjungan).
CURATED_T = {
    "opini": "Bold statement: kebanyakan RS punya data lengkap tapi tidak pernah membacanya. Setuju atau tidak?",
    "netral": "Menurut Anda, data apa yang paling jarang dibaca di RS atau klinik Anda? Yuk diskusi di komentar.",
    "sanjungan": "Buat yang keputusannya sudah berbasis data, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_LI = {
    "netral": "Menurut Anda, apa tantangan terbesar SEO layanan kesehatan? Yuk diskusi di komentar.",
    "opini": "Bold statement: RS yang rajin SEO lokal bisa mengalahkan RS besar di radius tertentu. Setuju atau tidak?",
    "sanjungan": "Buat tim yang sudah konsisten menggarap SEO lokal, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_FB = {
    "sanjungan": "Buat yang kanal digital kliniknya sudah jalan, Anda selangkah lebih maju! \U0001f44f",
    "netral": "Menurut Anda, langkah digital pertama apa yang paling penting untuk klinik kecil? Yuk diskusi di komentar.",
    "opini": "Bold statement: klinik kecil justru paling diuntungkan digital marketing. Setuju atau tidak?",
}


def build(base, platform, industri, tones, limit=None, ban=None, max_try=120):
    for tone in tones:
        for _ in range(max_try):
            txt = tambahkan_diskusi(base, platform=platform, industri=industri, tone=tone, force=True)
            if not CTA_RE.search(txt[-200:]):
                continue
            if limit is not None and len(txt) > limit:
                continue
            if ban is not None and ban.search(txt):
                continue
            if "..." in txt:
                continue
            return txt
    return None


def build_curated(base, curated, tones, limit=None):
    """Pakai pancingan kurasi (on-topic) dulu; tone sesuai urutan yang diminta."""
    for tone in tones:
        hook = (curated or {}).get(tone)
        if not hook:
            continue
        cand = base.rstrip() + "\n\n" + hook
        if CTA_RE.search(cand[-200:]) and (limit is None or len(cand) <= limit) and "@" not in cand:
            return cand
    return None


def build_safe(base, platform, industri, tones, limit=None, ban=None, curated=None):
    txt = build_curated(base, curated, tones, limit=limit)
    if txt is not None:
        return txt
    txt = build(base, platform, industri, tones, limit=limit, ban=ban)
    if txt is not None:
        return txt
    for tone in tones:
        hook = (curated or {}).get(tone)
        if not hook:
            continue
        cand = base.rstrip() + "\n\n" + hook
        if CTA_RE.search(cand[-200:]) and (limit is None or len(cand) <= limit):
            return cand
    return None


THREADS = build_safe(THREADS_BASE, "threads", "rumah_sakit", ["opini", "netral"], limit=500, ban=BAN_RE,
                     curated=CURATED_T)
LI = build_safe(LI_BASE, "linkedin", "rumah_sakit", ["netral", "opini"], ban=BAN_RE, curated=CURATED_LI)
FB = build_safe(FB_BASE, "facebook", "rumah_sakit", ["sanjungan", "netral"], ban=BAN_RE, curated=CURATED_FB)

REPLY1 = ("Baca selengkapnya: %s\n\nButuh bantuan mengelola kanal digital klinik Anda?\n"
          "www.studiosoft.id" % ARTICLE_URL)
REPLY2 = HASHTAG_LINE


def check_caption(name, txt, limit=None, require_cta=True):
    ok = True
    if txt is None:
        print("  \u274c %s: gagal disusun" % name)
        return False
    if limit and len(txt) > limit:
        print("  \u274c %s: %d char > %d" % (name, len(txt), limit))
        ok = False
    m = re.findall(r"@\w+", txt)
    if m:
        print("  \u274c %s: ada mention %s" % (name, m))
        ok = False
    caps = [t for t in re.findall(r"#(\w+)", txt) if t != t.lower()]
    if caps:
        print("  \u274c %s: hashtag KAPITAL %s" % (name, caps))
        ok = False
    if require_cta and not CTA_RE.search(txt[-200:]):
        print("  \u274c %s: tidak ada pancingan diskusi di ekor caption" % name)
        ok = False
    print("  %s %s: %d char" % ("\u2705" if ok else "\u26a0\ufe0f", name, len(txt)))
    return ok


def all_docs():
    docs, page = [], 1
    while True:
        d = c.get("/public/schedule?page=%d&limit=100" % page)
        docs.extend(d.get("docs", []))
        if page >= (d.get("totalPages") or 1):
            break
        page += 1
    return docs


def scan_media(statuses=("pending", "scheduled", "draft")):
    hits = []
    for s in all_docs():
        if s.get("accountId") not in (ACC.STUDIOSOFT_THREADS, ACC.STUDIOSOFT_LINKEDIN,
                                      ACC.STUDIOSOFT_FB, ACC.STUDIOSOFT_IG):
            continue
        if (s.get("status") or "").lower() not in statuses:
            continue
        for m in (s.get("medias") or []):
            base = os.path.basename((m.get("url") or "").split("?")[0]).lower()
            if base in [f.lower() for f in MEDIA_FILES]:
                hits.append((s.get("accountId"), (s.get("scheduleAt") or "")[:16],
                             (s.get("title") or "")[:40], s.get("type"), s.get("status")))
                break
    return hits


def busy_by_account(aid):
    busy = set()
    for s in all_docs():
        if s.get("accountId") == aid and (s.get("status") or "").lower() in ("pending", "scheduled", "draft"):
            busy.add((s.get("scheduleAt") or "")[:19])
    return busy


def next_free_09():
    busy = busy_by_account(ACC.STUDIOSOFT_THREADS)
    now = datetime.now(timezone.utc)
    start = datetime(now.year, now.month, now.day, tzinfo=timezone.utc)
    if now.hour >= 2:
        start += timedelta(days=1)
    for day in range(0, 20):
        cand = (start + timedelta(days=day)).strftime("%Y-%m-%dT02:00:00")
        if cand not in busy:
            lab = (datetime.fromisoformat(cand) + timedelta(hours=7)).strftime("%d %b %Y 09:00 WIB")
            return cand + ".000Z", lab
    return None, None


def next_free_li():
    SLOTS = [("05:00 WIB", 22, -1), ("08:00 WIB", 1, 0), ("11:00 WIB", 4, 0), ("14:00 WIB", 7, 0),
             ("17:00 WIB", 10, 0), ("20:00 WIB", 13, 0), ("23:00 WIB", 16, 0)]
    busy = busy_by_account(ACC.STUDIOSOFT_LINKEDIN)
    now = datetime.now(timezone.utc) + timedelta(minutes=15)
    now_naive = now.replace(tzinfo=None)
    today = datetime.now(timezone.utc)
    for day in range(0, 60):
        for label, uh, shift in SLOTS:
            d0 = datetime(today.year, today.month, today.day, tzinfo=timezone.utc) + timedelta(days=day + shift)
            cand = "%sT%02d:00:00" % (d0.strftime("%Y-%m-%d"), uh)
            if datetime.fromisoformat(cand) <= now_naive:
                continue
            if cand not in busy:
                lab = (datetime.fromisoformat(cand) + timedelta(hours=7)).strftime("%d %b %Y %H:%M WIB")
                return cand + ".000Z", lab
    return None, None


def next_free_fb():
    busy = busy_by_account(ACC.STUDIOSOFT_FB)
    now = datetime.now(timezone.utc) + timedelta(minutes=30)
    now_naive = now.replace(tzinfo=None)
    today = datetime.now(timezone.utc)
    for day in range(0, 21):
        d0 = datetime(today.year, today.month, today.day, tzinfo=timezone.utc) + timedelta(days=day)
        cand = "%sT12:30:00" % d0.strftime("%Y-%m-%d")
        if datetime.fromisoformat(cand) <= now_naive:
            continue
        if cand not in busy:
            lab = (datetime.fromisoformat(cand) + timedelta(hours=7)).strftime("%d %b %Y 19:30 WIB")
            return cand + ".000Z", lab
    return None, None


def prep_media():
    ok = True
    for png in sorted(os.listdir(SRC_PNG)):
        if not (png.endswith(".png") and png.startswith("ss_hc41_analytics_")):
            continue
        jpg = png.replace(".png", ".jpg")
        src = Image.open(os.path.join(SRC_PNG, png))
        if src.mode != "RGB":
            src = src.convert("RGB")
        src.save(os.path.join(IMG_DIR, jpg), "JPEG", quality=90)
        print("  \u2705 %s -> %s %s" % (png, jpg, src.size))
    for f in MEDIA_FILES:
        try:
            im = Image.open(os.path.join(IMG_DIR, f))
            im.verify()
            im = Image.open(os.path.join(IMG_DIR, f))
            print("  \u2705 PIL %s %s %s %d bytes" % (f, im.size, im.mode,
                                                      os.path.getsize(os.path.join(IMG_DIR, f))))
        except Exception as e:
            print("  \u274c PIL %s: %s" % (f, e))
            ok = False
    return ok


def verify_urls():
    ok = True
    for u in MEDIA_URLS:
        for ip in INGRESS:
            r = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "30",
                                "--resolve", "mantapserver.tail3947bb.ts.net:443:%s" % ip, u],
                               capture_output=True, text=True)
            code = r.stdout.strip()
            print("  %s @%s: %s" % (u.split("/")[-1], ip, code))
            if code != "200":
                ok = False
    return ok


def verify_link(url):
    r = subprocess.run(["curl", "-sIL", "--http1.1", "--max-time", "200",
                        "-A", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", url,
                        "-o", "/dev/null", "-w", "%{http_code}"], capture_output=True, text=True)
    code = (r.stdout or "").strip()
    print("  langsung %s: %s" % (url, code))
    if code != "200":
        r2 = subprocess.run(["curl", "-s", "--max-time", "120", "-o", "/dev/null", "-w", "%{http_code}",
                             "https://r.jina.ai/" + url], capture_output=True, text=True)
        code = (r2.stdout or "").strip()
        print("  via r.jina.ai: %s" % code)
    return code == "200"


def main():
    wib = datetime.now(timezone(timedelta(hours=7)))
    print("Time WIB: %s" % wib.strftime("%Y-%m-%d %H:%M"))

    print("\n1) SLOT:")
    SLOT_UTC, SLOT_LABEL = next_free_09()
    if not SLOT_UTC:
        print("  \u274c tidak ada slot 09:00 WIB bebas \u2014 batal.")
        return False
    print("  \u2705 carousel Threads: %s (%s)" % (SLOT_LABEL, SLOT_UTC))
    LI_SLOT_UTC, LI_SLOT_LABEL = next_free_li()
    FB_SLOT_UTC, FB_SLOT_LABEL = next_free_fb()
    print("  \u2705 LinkedIn text: %s (%s)" % (LI_SLOT_LABEL, LI_SLOT_UTC))
    print("  \u2705 Facebook text (tanpa media, replies=[]): %s (%s)" % (FB_SLOT_LABEL, FB_SLOT_UTC))
    if not (LI_SLOT_UTC and FB_SLOT_UTC):
        print("  \u274c slot LI/FB tidak ditemukan \u2014 batal.")
        return False

    print("\n2) CAPTION:")
    ok = True
    ok &= check_caption("Threads", THREADS, 500)
    ok &= check_caption("LinkedIn", LI)
    ok &= check_caption("Facebook", FB)
    ok &= check_caption("Reply1", REPLY1, require_cta=False)
    ok &= check_caption("Reply2", REPLY2, require_cta=False)
    if not ok:
        print("\n\u274c caption gagal aturan \u2014 batal.")
        return False

    print("\n3) IDEMPOTENSI (media hc41 sudah pernah dijadwalkan?):")
    hits = scan_media(("pending", "scheduled", "draft", "success"))
    if hits:
        print("  \u26a0\ufe0f sudah ada jadwal dgn media hc41 -> SKIP create:")
        for h in hits:
            print("   ", h)
        return True
    print("  \u2705 belum ada")

    print("\n4) MEDIA: PNG standar baru -> JPEG openclaw-img + PIL:")
    if not prep_media():
        print("  \u274c media rusak \u2014 batal.")
        return False

    print("\n5) MEDIA JALUR INTERNET (2 ingress):")
    if not verify_urls():
        print("  \u274c ada media tidak 200 \u2014 batal.")
        return False

    print("\n6) LINK ARTIKEL:")
    for u in (ARTICLE_URL, LI_URL, FB_URL):
        if not verify_link(u):
            print("  \u274c link tidak 200 \u2014 batal.")
            return False

    if DRY:
        print("\n[DRY RUN] semua cek lolos; tidak ada create.")
        return True

    base_dt = datetime.fromisoformat(SLOT_UTC.replace("Z", "+00:00"))
    r2 = (base_dt + timedelta(minutes=1)).strftime("%Y-%m-%dT%H:%M:%S.000Z")
    r3 = (base_dt + timedelta(minutes=2)).strftime("%Y-%m-%dT%H:%M:%S.000Z")
    medias = [c.make_media(u, "image", "slide%d" % (i + 1)) for i, u in enumerate(MEDIA_URLS)]

    results = {}
    print("\n7) CREATE \u2014 THREADS (album + 2 replies):")
    try:
        alb = c.schedule_album(ACC.STUDIOSOFT_THREADS, THREADS, SLOT_UTC, medias, title=TITLE)
        pid = alb.get("scheduleId") or alb.get("_id") or alb.get("id")
        print("  album -> %s" % pid)
        c.create_schedule(ACC.STUDIOSOFT_THREADS, REPLY1, r2, type_="text", replies=[pid])
        c.create_schedule(ACC.STUDIOSOFT_THREADS, REPLY2, r3, type_="text", replies=[pid])
        print("  2 replies OK")
        results["threads"] = pid
    except Exception as e:
        print("  GAGAL %s" % e)
        results["threads"] = None

    print("\n   LINKEDIN (text-only, %s):" % LI_SLOT_LABEL)
    try:
        res = c.create_schedule(ACC.STUDIOSOFT_LINKEDIN, LI, LI_SLOT_UTC, title=LI_TITLE,
                                type_="text", medias=[], replies=[])
        print("  text -> %s" % (res.get("scheduleId") or res.get("_id") or res.get("id")))
        results["linkedin"] = res.get("scheduleId") or res.get("_id") or res.get("id")
    except Exception as e:
        print("  GAGAL %s" % e)
        results["linkedin"] = None

    print("\n   FACEBOOK (TEXT saja, TANPA media, replies=[], %s):" % FB_SLOT_LABEL)
    try:
        res = c.create_schedule(ACC.STUDIOSOFT_FB, FB, FB_SLOT_UTC, title=FB_TITLE,
                                type_="text", medias=[], replies=[])
        print("  text -> %s" % (res.get("scheduleId") or res.get("_id") or res.get("id")))
        results["facebook"] = res.get("scheduleId") or res.get("_id") or res.get("id")
    except Exception as e:
        print("  GAGAL %s" % e)
        results["facebook"] = None

    print("\n" + "=" * 55)
    ok = all(results.values())
    print("SEMUA PLATFORM TERJADWAL" if ok else "GAGAL: %s" % [k for k, v in results.items() if not v])

    if ok:
        print("\n8) VERIFIKASI (fetch ULANG dari API per jadwal):")
        for name, sid in results.items():
            d = c.get("/public/schedule/%s" % sid)
            doc = d.get("doc") or d.get("data") or d
            print("  %s: status=%s at=%s type=%s medias=%d replies=%d" % (
                name, doc.get("status"), doc.get("scheduleAt"), doc.get("type"),
                len(doc.get("medias") or []), len(doc.get("replies") or [])))
        for h in scan_media(("pending",)):
            print("   ", h)

        st = json.loads(open(WS + "/studiosoft_healthcare_state.json").read())
        st["index"] = st.get("index", 36) + 1
        st["topic"] = TITLE
        st["last_post"] = wib.strftime("%Y-%m-%d %H:%M")
        json.dump(st, open(WS + "/studiosoft_healthcare_state.json", "w"))
        with open(WS + "/studiosoft_healthcare_log.txt", "a") as f:
            f.write("\n[%s WIB] hc41 (RE-AIR topik data analytics \u2014 terakhir tayang Threads "
                    "23 Agu 2026, slide BARU) | %s | %s | Threads album %s (%s) + 2 replies | "
                    "LinkedIn TEXT \"%s\" %s (%s) | FB TEXT \"%s\" (tanpa media, replies=[]) %s | "
                    "catatan: stok artikel healthcare baru masih HABIS (WP REST 19 Sep: artikel "
                    "terbaru 19 Sep `creative-agency-surabaya-solusi-kreatif-brand-berbeda` = "
                    "non-healthcare); kandidat Threads belum-tayang HABIS -> re-air data-analytics "
                    "dgn nama file BARU ss_hc41_analytics_* + engine ss_carousel_lib (bg #122B63); "
                    "LinkedIn pakai artikel panduan-seo-klinik-rumah-sakit (di LI hanya pernah "
                    "ERROR 20 Agu, tidak ada di antrean LI); FB pakai artikel "
                    "klinik-kecil-butuh-digital-marketing (FREE di FB SS). state index -> %s.\n"
                    % (wib.strftime("%Y-%m-%d %H:%M"), TITLE, ARTICLE_URL, SLOT_LABEL,
                       SLOT_UTC[11:16] + "Z", LI_TITLE, LI_SLOT_LABEL, LI_SLOT_UTC,
                       FB_TITLE, FB_SLOT_LABEL, st["index"]))
        print("Log + state ditulis.")
    return ok


if __name__ == "__main__":
    sys.exit(0 if main() else 1)
