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

Topik Threads : "Google Business Profile untuk Klinik: Cara Biar Pasien Gampang Nemu Lokasi Anda"
                (artikel https://studiosoft.id/google-business-profile-klinik/ — RE-AIR:
                 audit 2.654 jadwal Repliz 23 Sep (URL-presisi) -> terakhir tayang di Threads
                 29 Agu 2026, kandidat re-air paling tua yang tersisa; nama file slide BARU
                 ss_hc44_gbp_*).
LinkedIn      : TEXT-only, artikel `social-media-rumah-sakit-efektif` (LI SS: sukses terakhir
                6 Sep 2026, TIDAK ada di antrean pending -> re-air terverifikasi).
Facebook      : TEXT-only tanpa media, replies=[], artikel `konten-edukasi-kesehatan-
                kepercayaan-pasien` (di FB SS hanya pernah ERROR 12 Sep sbg album ->
                belum pernah tayang; sekarang dikirim sebagai teks).

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"
_DOCS = None
c = ReplizClient()

TITLE = "Google Business Profile untuk Klinik: Cara Biar Pasien Gampang Nemu Lokasi Anda"
ARTICLE_URL = "https://studiosoft.id/google-business-profile-klinik/"
LI_TITLE = "Social Media Marketing untuk Rumah Sakit: Platform Mana yang Paling Efektif?"
LI_URL = "https://studiosoft.id/social-media-rumah-sakit-efektif/"
FB_TITLE = "Pentingnya Konten Edukasi Kesehatan untuk Meningkatkan Kepercayaan Pasien"
FB_URL = "https://studiosoft.id/konten-edukasi-kesehatan-kepercayaan-pasien/"
HASHTAG_LINE = "#studiosoft #seolokal #klinik #googlemaps #digitalmarketingklinik"

MEDIA_FILES = [
    "ss_hc44_gbp_01_cover.jpg",
    "ss_hc44_gbp_02_kenapa.jpg",
    "ss_hc44_gbp_03_langkah.jpg",
    "ss_hc44_gbp_04_review.jpg",
    "ss_hc44_gbp_05_cta.jpg",
]
SRC_PNG = os.path.join(WS, "studiosoft_hc44_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 = """Pasien buka Google Maps, ketik \u201cklinik gigi terdekat\u201d. Yang muncul: profil klinik lain, lengkap dengan rating, jam buka, dan foto.

Google Business Profile itu gratis dan efeknya langsung terasa. Kuncinya: data lengkap, minimal 20 foto, posting rutin, dan kelola review maksimal 24 jam.

""" + ARTICLE_URL

LI_BASE = """Hampir setiap RS dan klinik sekarang punya akun di semua platform sosial media. Tapi kontennya itu-itu saja: foto dokter, banner promo, ucapan hari besar. Hasilnya engagement mati dan pasien baru tidak datang.

Masalahnya bukan di platformnya, tapi di strategi konten yang tidak disesuaikan dengan karakter tiap kanal.

Instagram paling efektif untuk membangun awareness: carousel edukasi, reels penjelasan dokter, dan story interaktif. Pasang link booking di bio, posting empat sampai lima konten per minggu dengan komposisi edukasi 70 persen, promosi 20 persen, behind the scene 10 persen.

Facebook masih kuat untuk demografi 35 tahun ke atas \u2014 yang justru segmen pasien utama RS. Pakai untuk artikel edukasi panjang, event kesehatan, dan grup diskusi.

YouTube paling sering dilupakan. Padahal video edukasi dokter bisa muncul di pencarian Google dan YouTube sekaligus. Buat series tetap: Tanya Dokter, Mitos vs Fakta, Virtual Tour RS.

TikTok menjangkau Gen Z dan milenial lewat tips cepat, fakta unik, dan myth busting. Sedangkan LinkedIn efektif untuk branding institusi, publikasi riset, dan rekrutmen tenaga medis.

Kuncinya: tidak perlu aktif di semua platform sekaligus. Pilih dua sampai tiga yang paling relevan dengan target pasien Anda, lalu konsisten.

Bacaan lengkapnya: """ + LI_URL

FB_BASE = """Ketika sebuah RS rajin membagikan konten edukasi kesehatan \u2014 bukan promosi \u2014 kepercayaan pasien tumbuh dengan sendirinya.

Coba bandingkan dua unggahan ini. \u201cPromo medical checkup diskon 50 persen!\u201d versus \u201cKenali 5 tanda awal diabetes yang sering diabaikan\u201d. Mana yang lebih mungkin dibagikan orang dan diingat?

Konten edukasi membangun otoritas RS, menaikkan engagement organik tanpa budget iklan besar, dan bisa tersebar ulang oleh pasien. Artikelnya juga tahan lama \u2014 bisa muncul di Google bertahun-tahun, dan tidak melanggar etika promosi kesehatan.

Beberapa ide yang bisa langsung dipakai: mitos vs fakta, tips kesehatan musiman, penjelasan penyakit, behind the scene dokter, FAQ pasien, dan infografis alur layanan.

Edukasi adalah marketing paling halus, dan paling dipercaya. 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). Bauran run ini: Threads opini + LinkedIn netral + FB netral.
CURATED_T = {
    "opini": "Bold statement: klinik dengan profil Google yang lengkap akan menang dari klinik yang layanannya bagus tapi tidak terlihat online. Setuju atau tidak?",
    "netral": "Menurut Anda, review atau foto yang lebih menentukan pasien memilih klinik? Yuk diskusi di komentar.",
    "sanjungan": "Buat klinik yang profil Google-nya sudah rapi dan rutin diulas pasien, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_LI = {
    "netral": "Menurut Anda, platform mana yang paling efektif untuk RS di Indonesia: Instagram, Facebook, YouTube, atau LinkedIn? Yuk diskusi di komentar.",
    "opini": "Bold statement: bagi RS, dua platform yang dikelola serius lebih baik daripada enam akun yang isinya foto dokter. Setuju atau tidak?",
    "sanjungan": "Buat tim RS yang kontennya sudah disesuaikan per platform, Anda selangkah lebih maju! \U0001f44f",
}
CURATED_FB = {
    "netral": "Menurut Anda, konten edukasi jenis apa yang paling dipercaya pasien? Yuk diskusi di komentar.",
    "opini": "Bold statement: konten edukasi lebih efektif menarik pasien daripada iklan promo. Setuju atau tidak?",
    "sanjungan": "Buat yang kontennya lebih banyak mengedukasi daripada berjualan, Anda selangkah lebih maju! \U0001f44f",
}


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):
    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", ["netral", "opini"], ban=BAN_RE, curated=CURATED_FB)

REPLY1 = ("Baca selengkapnya: %s\n\nButuh partner digital marketing untuk RS atau 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 "..." in txt:
        print("  \u274c %s: ada elipsis" % name)
        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 get_retry(ep, tries=6):
    import time
    last = None
    for i in range(tries):
        try:
            return c.get(ep)
        except Exception as e:
            last = e
            time.sleep(3 + 3 * i)
    raise last


def all_docs():
    """Ambil semua jadwal SEKALI (cache) — dump berulang bikin run lambat/timeout."""
    global _DOCS
    if _DOCS is not None:
        return _DOCS
    docs, page = [], 1
    while True:
        d = get_retry("/public/schedule?page=%d&limit=100" % page)
        docs.extend(d.get("docs", []))
        if page >= (d.get("totalPages") or 1):
            break
        page += 1
    _DOCS = docs
    print("   (dump jadwal: %d docs)" % len(docs))
    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_hc44_gbp_")):
            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=85, optimize=True)
        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(tries=4):
    """Ingress 103.84.155.217 kadang lambat (21-30s) -> timeout 60s + 4 percobaan."""
    ok = True
    for u in MEDIA_URLS:
        for ip in INGRESS:
            code = "000"
            for _ in range(tries):
                r = subprocess.run(["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "60",
                                    "--resolve", "mantapserver.tail3947bb.ts.net:443:%s" % ip, u],
                                   capture_output=True, text=True)
                code = r.stdout.strip()
                if code == "200":
                    break
            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 hc44 sudah pernah dijadwalkan?):")
    hits = scan_media(("pending", "scheduled", "draft", "success"))
    if hits:
        print("  \u26a0\ufe0f sudah ada jadwal dgn media hc44 -> 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 = get_retry("/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", 39) + 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] hc44 (RE-AIR Threads: Google Business Profile untuk klinik "
                    "\u2014 terakhir tayang teks 29 Agu 2026, kandidat re-air paling tua yang "
                    "tersisa; slide BARU ss_hc44_gbp_*) | %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 22 Sep: 300 post, "
                    "terbaru 20 Sep `jasa-branding-surabaya-membangun-identitas-brand-kuat` = "
                    "non-healthcare; kategori healthcare-digital terakhir 13 Sep = "
                    "jasa-digital-marketing-klinik, sudah dipakai hc36); audit 2.654 jadwal Repliz "
                    "URL-presisi -> Threads: GBP klinik terakhir 29 Agu 2026 (hc43 memakai "
                    "rs-klinik-digital-marketing-2026/27 Agu); LinkedIn: social-media-rumah-sakit-"
                    "efektif sukses terakhir 6 Sep & TIDAK ada di antrean pending (semua artikel "
                    "healthcare lain sudah terisi slot LI Okt 2026); FB: konten-edukasi-kesehatan-"
                    "kepercayaan-pasien hanya pernah ERROR 12 Sep sbg album -> belum pernah tayang, "
                    "kini dikirim teks. 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)
