#!/usr/bin/env python3
"""StudioSoft: artikel harian 18 Okt 2026 (pilar A uang) — migrasi website tanpa kehilangan peringkat.
Pola mengikuti ss_artikel_struktur_url.py. Tahap: draf dulu (--apply), baru dijadwalkan (--jadwalkan TANGGAL).
Catatan: satu elemen blok = satu baris. Jangan melipat baris di dalam <p>/<li>, karena wpautop mengubahnya jadi <br>.
"""
import base64, json, re, sys, time, urllib.error, urllib.request

WS = "/home/mantapman/.hermes/profiles/atun/workspace"
DOM = "studiosoft.id"
AUTH = base64.b64encode(b"bambangbot:q26r RePo BVy6 pwU2 F3z7 ouyl").decode()

JUDUL = "Migrasi Website Tanpa Kehilangan Peringkat: Urutan Langkahnya"
SLUG = "migrasi-website-tanpa-kehilangan-peringkat"
FOKUS = "migrasi website"
META_JUDUL = "Migrasi Website Tanpa Kehilangan Peringkat: Urutan"
META_DESC = ("Migrasi website tanpa kehilangan peringkat dimulai dari peta alamat, pengalihan 301 yang aktif, "
             "dan pemeriksaan bertahap. Ini urutan kerja lengkapnya.")

KAT = [330]  # SEO

WA = "https://wa.me/6281935000624?text=Halo%20StudioSoft%2C%20saya%20ingin%20konsultasi%20digital%20marketing"
TOMBOL = ('<p style="text-align:center;margin:32px 0 8px"><a href="' + WA +
          '" target="_blank" rel="noopener" style="display:inline-block;background-color:#0f2557;'
          'color:#ffffff;padding:15px 34px;font-size:18px;font-weight:bold;text-decoration:none;'
          'border-radius:6px;">Konsultasi Gratis</a></p>')

L_GSC = "https://studiosoft.id/google-search-console-untuk-pemula-7-manfaat-wajib-untuk-website-bisnis-naik-peringkat-google/"
L_DOMAIN = "https://studiosoft.id/6-tips-memilih-domain-website-yang-bagus-dan-profesional/"
L_CWV = "https://studiosoft.id/core-web-vitals-kecepatan-website-2026/"
L_AUDIT = "https://studiosoft.id/10-checklist-audit-seo-gratis-yang-bikin-website-umkm-naik-peringkat-dalam-7-hari/"
L_JASA = "https://studiosoft.id/jasa-pembuatan-website-profesional-untuk-usaha-jasa-di-surabaya/"

FAQ = [
    ("Apakah peringkat langsung hilang setelah pindah domain?",
     "Tidak langsung, dan sering tidak hilang sama sekali kalau pengalihan aktif sejak awal. Yang biasa terjadi "
     "adalah naik turun selama beberapa pekan saat mesin pencari memproses alamat baru. Penurunan tajam biasanya "
     "berasal dari pengalihan yang tidak pernah aktif atau halaman yang tidak ikut dipindahkan."),
    ("Berapa lama peringkat pulih setelah migrasi?",
     "Untuk situs yang alamatnya rapi dan pengalihannya benar, tayangan biasanya kembali dalam dua sampai delapan "
     "pekan. Situs dengan banyak tautan masuk dari luar bisa lebih cepat karena mesin pencari lebih sering "
     "mengunjunginya."),
    ("Apakah semua alamat lama harus dialihkan?",
     "Tidak. Alamat yang tidak pernah menerima kunjungan dan tidak punya tautan masuk tidak perlu diselamatkan. "
     "Yang wajib dialihkan adalah halaman dengan kunjungan, halaman dengan tautan masuk, dan halaman layanan yang "
     "menjadi tujuan penjualan."),
    ("Bisakah migrasi dikerjakan sendiri tanpa bantuan?",
     "Bisa, untuk situs kecil dengan puluhan halaman. Yang dibutuhkan kesabaran mencatat alamat satu per satu dan "
     "kemauan memeriksa hasilnya selama sebulan. Untuk situs dengan ratusan halaman atau toko online, satu aturan "
     "pengalihan yang salah bisa berdampak ke transaksi."),
    ("Bolehkah mengganti tema sekaligus pindah domain?",
     "Boleh, tetapi sebaiknya tidak dalam satu waktu. Dua perubahan besar yang berjalan bersamaan membuat Anda "
     "tidak bisa mengetahui penyebab kalau ada yang memburuk. Kerjakan migrasinya dulu, pastikan datanya stabil, "
     "baru ganti tampilan sebulan kemudian."),
]

ISI = f"""
<p>Pindah domain atau mengganti struktur alamat halaman adalah pekerjaan yang paling sering ditunda pemilik situs. Alasannya masuk akal: takut kehilangan peringkat yang sudah dibangun bertahun-tahun. Kekhawatiran itu berdasar. Situs yang dipindahkan tanpa urutan kerja yang benar bisa kehilangan trafik dalam hitungan hari, dan pemulihannya sering memakan waktu berbulan-bulan.</p>

<p>Menolak pindah selamanya juga punya biaya. Domain lama yang sulit diingat, struktur alamat yang berantakan, dan platform yang sudah tidak cocok menahan pertumbuhan bisnis setiap bulan. Yang membedakan migrasi yang aman dari yang merusak bukan keberuntungan, melainkan persiapan, urutan eksekusi, dan pemeriksaan setelah situs berpindah. Berikut urutan kerja yang kami pakai saat memindahkan situs klien, termasuk bagian yang paling sering terlewat.</p>

<h2>Apa yang sebenarnya hilang ketika migrasi gagal</h2>
<p>Peringkat tidak hilang karena alamatnya berubah. Peringkat hilang karena mesin pencari tidak pernah tahu bahwa alamat lama dan alamat baru menunjuk halaman yang sama. Ketika sebuah alamat mati tanpa pengalihan, Google memperlakukannya sebagai halaman yang dihapus. Nilai tautan, riwayat klik, dan relevansi yang menempel di alamat lama berhenti di situ.</p>

<h2>Sebelum menyentuh apa pun: catat nilai halaman Anda</h2>
<p>Mulai dari data, bukan dari daftar halaman yang menurut Anda jelek. Buka Search Console dan ambil data dua belas bulan terakhir: halaman mana yang menerima klik, kueri apa yang membawanya, dan berapa posisi rata-ratanya. Data ini menjadi pembanding setelah migrasi. Tanpa pembanding, Anda tidak akan bisa membedakan dampak migrasi dari naik turun yang biasa terjadi. Cara membaca laporan ini kami bahas di artikel <a href=\"{L_GSC}\">Search Console untuk pemula</a>.</p>

<p>Setelah itu, ekspor daftar alamat yang masuk. Anda butuh tiga daftar: alamat yang punya kunjungan, alamat yang punya tautan masuk dari situs lain, dan alamat yang pernah muncul di hasil pencarian meskipun tanpa klik. Kelompok ketiga paling sering dilupakan padahal paling murah diselamatkan karena tinggal dialihkan. Kalau sekalian mengganti domain, kredibilitas alamat baru perlu diperiksa sebelum pindah, seperti dibahas di <a href="{L_DOMAIN}">tips memilih domain</a>.</p>

<h2>Peta alamat adalah pekerjaan yang menentukan</h2>
<p>Peta alamat berisi dua kolom: alamat lama dan alamat baru yang menggantikannya. Untuk situs kecil, peta ini bisa selesai dalam satu sore. Untuk situs dengan ratusan halaman, tarik semua alamat dari sitemap lalu cocokkan dengan halaman barunya satu per satu. Beberapa aturan berikut menghemat banyak waktu.</p>

<ul>
<li>Satu alamat lama hanya boleh punya satu tujuan. Dua aturan pengalihan untuk alamat yang sama membuat pengunjung dan mesin pencari menerima hasil berbeda.</li>
<li>Halaman yang isinya sudah tidak relevan tidak perlu diarahkan ke beranda. Pilih halaman terdekat yang masih membahas topik serupa, atau biarkan mati kalau memang layak dihapus.</li>
<li>Simpan peta ini sebagai berkas. Anda akan membukanya lagi saat memeriksa hasil, dan kadang saat memulihkan satu halaman yang terlewat.</li>
<li>Alamat gambar dan berkas ikut dicatat. Yang sering hilang setelah pindah bukan halamannya, tetapi gambar unggulan dan berkas unduhan.</li>
</ul>

<h2>Pengalihan 301 dan kesalahan yang membuatnya diam</h2>
<p>Pengalihan permanen memberi tahu mesin pencari bahwa halaman berpindah untuk selamanya, dan nilai halaman ikut berpindah. Yang sering terjadi di lapangan bukan pengalihan yang salah tujuan, tetapi pengalihan yang tidak pernah aktif. Di WordPress, perubahan alamat artikel biasanya diarahkan otomatis. Perubahan domain, penggabungan halaman, dan alamat yang dihapus tidak.</p>

<p>Uji pengalihan dengan cara yang tidak menipu diri sendiri. Peramban biasa mengikuti pengalihan tanpa memberi tahu Anda, sehingga alamat lama tampak hidup padahal hanya meneruskan. Perintah curl tanpa opsi mengikuti pengalihan menunjukkan kode sebenarnya: 301 untuk pengalihan permanen, 404 untuk alamat yang benar-benar mati. Periksa juga rantai pengalihannya. Alamat lama yang melewati alamat sementara sebelum sampai ke alamat baru lebih lemah daripada satu lompatan langsung.</p>

<h2>Urutan eksekusi pada hari pindah</h2>
<p>Bagian ini paling mudah dikacaukan karena semua orang ingin cepat selesai. Urutan berikut menjaga agar setiap masalah bisa dilacak.</p>

<ol>
<li>Pasang situs baru di alamat sementara dan pastikan seluruh halaman bisa dibuka tanpa galat. Halaman yang rusak sebelum pindah akan tetap rusak sesudahnya, dan Anda kehilangan kemampuan memisahkan penyebabnya.</li>
<li>Salin seluruh isi, termasuk gambar, berkas unduhan, dan formulir. Formulir adalah bagian yang paling sering berhenti bekerja karena alamat pengirimnya masih menunjuk domain lama.</li>
<li>Aktifkan peta pengalihan sebelum domain diarahkan. Pengunjung yang datang lebih awal akan langsung menemukan halaman yang benar.</li>
<li>Arahkan domain dan tunggu perubahan alamat tersebar. Ini bisa memakan beberapa jam, dan tidak ada gunanya menguji sesuatu yang belum tersebar.</li>
<li>Kerjakan satu perubahan besar dalam satu waktu. Kalau tema baru belum siap, tunda. Migrasi yang bersih lebih berharga daripada migrasi yang sekaligus mengubah tampilan.</li>
</ol>

<h2>Pemeriksaan 24 jam, 7 hari, dan 30 hari</h2>
<p>Pemeriksaan hari pertama bukan tentang peringkat, tetapi tentang akses. Pastikan beranda, halaman layanan utama, dan lima artikel dengan kunjungan tertinggi bisa dibuka, gambar tampil, dan formulir terkirim. Periksa juga robots.txt dan sitemap, dua berkas yang memberi tahu mesin pencari alamat baru.</p>

<p>Pada hari ketujuh, bandingkan data Search Console dengan pembanding yang Anda catat sebelumnya. Yang Anda cari bukan kenaikan, melainkan apakah alamat baru sudah mulai menerima tayangan. Kalau alamat baru belum muncul sama sekali, masalahnya biasanya ada di pengalihan atau di sitemap yang belum diperbarui.</p>

<p>Pada hari ketiga puluh, baru masuk ke halaman yang kehilangan trafik. Urutkan dari kerugian terbesar, lalu periksa satu per satu: apakah pengalihannya benar, apakah isinya berubah, apakah tautan internalnya masih menunjuk alamat lama. Sebagian besar temuan di tahap ini bisa diperbaiki tanpa mengulang migrasi. Kalau penurunannya disertai halaman yang terasa lambat, penyebabnya mungkin bukan migrasi sama sekali, melainkan hal yang dibahas di <a href=\"{L_CWV}\">Core Web Vitals dan kecepatan website</a>.</p>

<h2>Bagian yang paling sering terlewat</h2>
<p>Daftar berikut berasal dari pemeriksaan situs yang sudah pindah.</p>

<ul>
<li>Tautan internal di dalam artikel. Setelah alamat berubah, tautan lama masih sampai ke tujuan, tetapi lewat pengalihan. Perbarui supaya pengunjung tidak menunggu dua kali.</li>
<li>Gambar dan berkas yang diunggah ke alamat lama. Kalau domainnya berganti, alamat gambar harus ikut pindah, bukan hanya halamannya.</li>
<li>Akun analitik dan pelacakan. Properti baru sebaiknya dipasang sebelum pindah, bukan sesudah, supaya tidak ada jeda data yang membuat perbandingan mustahil.</li>
<li>Tanda tangan email, materi cetak, dan tautan di profil media sosial. Bukan urusan teknis SEO, tetapi menyumbang kunjungan dan sering terlupa.</li>
</ul>

<h2>Pertanyaan yang sering diajukan</h2>
<h3>{FAQ[0][0]}</h3>
<p>{FAQ[0][1]}</p>
<h3>{FAQ[1][0]}</h3>
<p>{FAQ[1][1]}</p>
<h3>{FAQ[2][0]}</h3>
<p>{FAQ[2][1]}</p>
<h3>{FAQ[3][0]}</h3>
<p>{FAQ[3][1]}</p>
<h3>{FAQ[4][0]}</h3>
<p>{FAQ[4][1]}</p>

<p>Migrasi bukan pekerjaan yang perlu ditakuti, tetapi juga bukan pekerjaan yang bisa dikerjakan sambil lalu. Mulailah dari data halaman yang berharga, susun peta alamat, aktifkan pengalihan sebelum domain diarahkan, lalu periksa hasilnya bertahap. Kalau Anda memerlukan pemeriksaan menyeluruh sebelum pindah, kami biasa mulai dari memetakan seluruh alamat dan tautan yang menuju situs Anda. Untuk situs baru yang sedang disiapkan, cakupan pekerjaannya ada di <a href=\"{L_JASA}\">jasa pembuatan website profesional untuk usaha jasa</a>, dan untuk pemeriksaan teknis awal Anda bisa memakai <a href=\"{L_AUDIT}\">checklist audit SEO gratis</a> yang kami pakai sendiri.</p>

""" + TOMBOL + """
<p><em>Catatan: urutan pemeriksaan di atas kami pakai saat memindahkan situs klien sendiri, termasuk situs yang sempat kehilangan trafik karena pengalihan belum aktif.</em></p>
"""

LD = {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [{"@type": "Question", "name": q,
                    "acceptedAnswer": {"@type": "Answer", "text": a}} for q, a in FAQ],
}
ISI = ISI + '\n<script type="application/ld+json">' + json.dumps(LD, ensure_ascii=False) + "</script>\n"

UA = {"User-Agent": "Mozilla/5.0"}


def rest(p, method="GET", data=None):
    h = {"Authorization": f"Basic {AUTH}", **UA}
    body = None
    if data is not None:
        body = json.dumps(data).encode()
        h["Content-Type"] = "application/json"
    r = urllib.request.Request(f"https://{DOM}{p}", data=body, headers=h, method=method)
    for _ in range(3):
        try:
            with urllib.request.urlopen(r, timeout=180) as x:
                return x.status, json.loads(x.read())
        except urllib.error.HTTPError as e:
            return e.code, e.read().decode()[:300]
        except Exception:
            time.sleep(4)
    return 0, "gagal"


tanpa_script = re.sub(r"<script.*?</script>", " ", ISI, flags=re.S)
kata_visible = len(re.sub(r"<[^>]+>", " ", tanpa_script).split())
kata_hitung = len(re.sub(r"<[^>]+>", " ", ISI).split())
patah = [x for x in re.findall(r"<(?:p|li|h2|h3)>(.*?)</(?:p|li|h2|h3)>", ISI, re.S)
         if re.search(r"<", x) is None and "\n" in x.strip() and not x.strip().endswith((".", "!", "?", ":", ";"))]
print("=== artikel baru:", JUDUL)
print(f"   panjang naskah: {kata_visible} kata terlihat · {kata_hitung} kata versi cek_pola_ai (baku 1.000-1.300)")
print(f"   meta judul: {len(META_JUDUL)} char (maks 60)")
print(f"   meta desc : {len(META_DESC)} char (140-158) | fokus di awal: {META_DESC.lower().startswith(FOKUS)}")
print(f"   tombol WA : {'ada' if WA in ISI else 'TIDAK ADA'}")
print(f"   tautan internal: {len(re.findall(r'href=.https://studiosoft.id/', ISI))}")
print(f"   h2: {len(re.findall('<h2', ISI))} | h3: {len(re.findall('<h3', ISI))} | FAQPage: {'FAQPage' in ISI}")
print(f"   patahan baris di dalam elemen: {len(patah)}")

if "--apply" not in sys.argv and "--jadwalkan" not in sys.argv:
    print("\n(dry run — pakai --apply untuk bikin draf, --jadwalkan YYYY-MM-DD untuk menjadwalkan)")
    sys.exit(0)

if "--apply" in sys.argv:
    st, d = rest(f"/wp-json/wp/v2/posts?slug={SLUG}&_fields=id,slug,status")
    if isinstance(d, list):
        for p in d:
            print(f"   ⚠ slug {SLUG} sudah dipakai: #{p['id']} [{p['status']}] — hentikan, ganti slug")
        if d:
            sys.exit(1)
    st, d = rest("/wp-json/wp/v2/posts", "POST", {
        "title": JUDUL, "slug": SLUG, "content": ISI, "status": "draft",
        "categories": KAT, "comment_status": "closed",
        "meta": {"_yoast_wpseo_title": META_JUDUL, "_yoast_wpseo_metadesc": META_DESC,
                 "_yoast_wpseo_focuskw": FOKUS}})
    if not isinstance(d, dict) or "id" not in d:
        print("   ❌ gagal bikin draf:", st, str(d)[:300])
        sys.exit(1)
    pid = d["id"]
    print(f"\n   ✅ draf #{pid} → {d.get('link')}")
    st2, d2 = rest(f"/wp-json/wp/v2/posts/{pid}?context=edit")
    raw = (d2.get("content") or {}).get("raw", "")
    meta = (d2.get("meta") or {})
    print(f"   baca balik: status={d2.get('status')} slug={d2.get('slug')} kata="
          f"{len(re.sub(r'<[^>]+>', ' ', raw).split())} kategori={d2.get('categories')}")
    print("   meta: " + json.dumps({k: v for k, v in meta.items() if "yoast" in k}, ensure_ascii=False)[:400])
    simpan = {"id": pid, "judul": JUDUL, "slug": SLUG, "tautan": d.get("link")}
    json.dump(simpan, open(f"{WS}/drafts/ss_artikel_migrasi_website.json", "w"), ensure_ascii=False, indent=1)
    print("   → id disimpan di drafts/ss_artikel_migrasi_website.json")

if "--jadwalkan" in sys.argv:
    tgl = sys.argv[sys.argv.index("--jadwalkan") + 1]
    pid = json.load(open(f"{WS}/drafts/ss_artikel_migrasi_website.json"))["id"]
    st, d = rest(f"/wp-json/wp/v2/posts/{pid}", "POST",
                 {"status": "future", "date": f"{tgl}T08:00:00"})
    if not isinstance(d, dict) or d.get("status") != "future":
        print("   ❌ gagal menjadwalkan:", st, str(d)[:300])
        sys.exit(1)
    st2, d2 = rest(f"/wp-json/wp/v2/posts/{pid}?context=edit&_fields=id,status,date,slug,content,meta")
    raw = (d2.get("content") or {}).get("raw", "")
    print(f"   ✅ dijadwalkan #{pid}: status={d2.get('status')} date={d2.get('date')} slug={d2.get('slug')}")
    print(f"   isi: {len(re.sub(r'<[^>]+>', ' ', raw).split())} kata | tombol WA: "
          f"{'ada' if 'wa.me/6281935000624' in raw else 'TIDAK ADA'} | tautan internal: "
          f"{len(re.findall(r'href=.https://studiosoft.id/', raw))}")
