<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xml" href="https://kekavigi.xyz/rss.xslt.xml"?>
<ns0:feed xmlns:ns0="http://www.w3.org/2005/Atom">
    <ns0:link href="https://kekavigi.xyz/rss.xml" rel="self" type="application/atom+xml" />
    <ns0:link href="https://kekavigi.xyz" rel="alternate" type="text/html" />
    <ns0:id>https://kekavigi.xyz/rss.xml</ns0:id>
    <ns0:title type="html">kekavigi.xyz</ns0:title>
    <ns0:subtitle>Kumpulan tulisan pribadi.</ns0:subtitle>
    <ns0:author>
        <ns0:name>A. Keyka Vigiliant</ns0:name>
    </ns0:author>
    <ns0:updated>2026-09-01T22:54:42.647041+08:00</ns0:updated>
    <ns0:rights>© 2016-2026 A. Keyka Vigiliant CC BY-SA 4.0</ns0:rights>
    <ns0:entry>
        <ns0:id>https://kekavigi.xyz/blog/ternyata-itu-menyenangkan</ns0:id>
        <ns0:title type="html">Ternyata itu Menyenangkan</ns0:title>
        <ns0:link href="https://kekavigi.xyz/blog/ternyata-itu-menyenangkan" rel="alternate" type="text/html" title="Ternyata itu Menyenangkan" />
        <ns0:published>2026-03-15T00:00:00+08:00</ns0:published>
        <ns0:author>
            <ns0:name>A. Keyka Vigiliant</ns0:name>
        </ns0:author>
        <ns0:rights>© 2016-2026 A. Keyka Vigiliant CC BY-SA 4.0</ns0:rights>
        <ns0:summary type="html">Mengenang hal-hal kecil yang terjadi selama satu dekade terakhir.</ns0:summary>
        <ns0:content type="html" xml:base="https://kekavigi.xyz/blog/ternyata-itu-menyenangkan">&lt;p&gt;Halo semuanya.&lt;/p&gt;
&lt;p&gt;Belakangan ini saya merasa bahagia, karena beberapa hal-hal kecil yang dulu saya mulai dengan alasan &amp;quot;kenapa tidak?&amp;quot; maupun &amp;quot;tidak ada salahnya mencoba,&amp;quot; tumbuh menjadi tanaman-tanaman hias mungil yang terpajang di sudut-sudut otak saya. Saya ambil contoh usaha saya membuat &lt;a href="/blog/membuat-database-posisi-catur"&gt;database analisa posisi catur&lt;/a&gt; satu tahun lalu, menjadi suatu cara yang &amp;quot;ya, aku bisa belajar catur pakai situs ini.&amp;quot; Dalam prosesnya, saya belajar banyak hal -- dari IPC, threading dan concurrency, web server, otentikasi, database, banyak cara menulis kode agar tidak menyebabkan depresi di kemudian hari. Memang, pada akhirnya itu sesuatu &amp;quot;produk&amp;quot; yang personal, dan besar kemungkinan tidak akan pernah digunakan orang lain, tapi tidak mengubah emosi bangga yang saya rasakan. Ada juga, proyek saya merapikan data kegempaan BMKG (dan USGS) lalu &lt;a target="_blank" rel="noreferrer" href="https://www.kaggle.com/datasets/kekavigi/earthquakes-in-indonesia"&gt;mengunggahnya di Kaggle&lt;/a&gt;, sejak sekitar empat-lima tahun lalu. &amp;quot;Buat apa?,&amp;quot; kata orang; saya hanya bisa menjawab &amp;quot;karena pengen aja.&amp;quot; Syukurlah, sesekali saya bisa membantu mahasiswa menyediakan data untuk skripsi mereka -- walau mereka selalu mengira saya orang BMKG.&lt;/p&gt;
&lt;p&gt;Beberapa hal kecil lainnya yang dulu saya lakukan ternyata mengubah arah hidup saya, seperti aktif mengerjakan soal-soal ProjectEuler membuat saya tahan (dan lulus) dari ITB, atau &lt;a href="/blog/menyapa-ubuntu"&gt;keputusan saya menggunakan Linux&lt;/a&gt;... jujur saya tidak dapat membayangkan Keyka macam apa yang akan ada saat ini, jika dia dulu bersikukuh menggunakan Windows di laptopnya. Namun tidak saya pungkiri, ada juga hal-hal yang sampai saat ini tidak mengubah diri saya, seperti usaha saya selama hampir satu dekade ini, untuk menuliskan beberapa hal yang terjadi dalam hidup saya di satu sudut Internet ini (&lt;em&gt;membuat jurnal mingguan sepertinya akan efektif, tapi saya ragu punya tenaga untuk melakukannya&lt;/em&gt;). Jika saya tidak membuat blog ini, Keyka versi-itu tetap menjadi diri saya saat ini.&lt;/p&gt;
&lt;p&gt;Oh, untuk membuat suasana tulisan ini menjadi tidak terlalu positif, juga ada banyak hal-hal yang dulu saya pikir sederhana tetapi seharusnya tidak saya lakukan. Seperti, aktif di Instagram. Tempat tersebut menyenangkan dan sempat menjadi cara saya belajar dunia desain, tipografi, fotografi, animasi, dan semacamnya. Namun entah mengapa, selanjutnya saya malah menjadikan Instagram menjadi tempat hiburan dan media sosial; sesuatu yang awalnya saya hindari (karena apa untungnya?). Butuh waktu lama untuk &amp;quot;membebaskan diri&amp;quot; dari ketidakoptimalan(?) tersebut, &lt;em&gt;dan oh... Anda tidak tahu seberapa sulitnya menghapus data yang pernah Anda bagikan&lt;/em&gt;, dan sekarang saya menganggap diri saya akan lebih baik jika sembilan tahun lalu, saya tidak mendaftar di situs tersebut. (&lt;em&gt;huh... membaca kembali bagian ini, saya menulis seperti seorang yang bebas dari kecanduan&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Apa yang hendak saya sampaikan dari menceritakan itu semua? ternyata mencoba dan menekuni hal-hal kecil dapat membuat hidup menjadi tidak monoton, dan siapa tahu, sesekali suatu saat nanti, bisa membantu mengubah hidup orang lain. Untuk mereka yang lebih muda dari saya, saya ada pesan singkat: kalian pernah dengar orang-orang tua memberikan nasehat-nasehat? walau apa yang mereka katakan terkadang terdengar lawas dan tidak sesuai jaman, hal yang mereka katakan bisa jadi ada benarnya; mereka mungkin hanya tidak menyampaikannya secara runtut maupun lengkap. Dengarkan dan pertimbangkan perkataan mereka itu, karena ketika sudah beranjak dewasa, kalian akan jarang mendengarkan petuah-petuah itu, dan akan semakin sering merasakannya secara langsung.&lt;/p&gt;
&lt;p&gt;Sekarang, saya mau mencoba lebih aktif melakukan (dan melanjutkan) hal-hal kecil semacam itu (&lt;a href="/notes/koleksi-proyek-sampingan"&gt;&lt;em&gt;to-do list&lt;/em&gt; hal-hal yang saya ingin lakukan semakin menumpuk&lt;/a&gt;), juga belajar terkait perpajakan, investasi, dan asuransi. Kembali aktif menulis catatan harian/mingguan. Perlahan kembali aktif di Wikipedia Indonesia -- mengingat sejak Februari 2026 &lt;a target="_blank" rel="noreferrer" href="https://id.wikipedia.org/wiki/Wikipedia:Pernyataan_resmi_komunitas_Wikipedia_bahasa_Indonesia_terhadap_pembatasan_akses_auth.wikimedia.org_di_Indonesia"&gt;Kemkomdigi memblokir akses log masuk&lt;/a&gt;. Uh... belajar cara memperbaiki perangkat-perangkat elektronik, melanjutkan studi, dan... &lt;em&gt;uh mari lempar dadu&lt;/em&gt;... jalan-jalan ke luar negeri? Oke yang ide yang terakhir tadi terlalu di luar dugaan, penasaran apa yang Keyka satu-dekade-nanti pikirkan saat membaca tulisan ini.&lt;/p&gt;
&lt;p&gt;Sekian tulisan singkat dari saya.&lt;/p&gt;
&lt;p&gt;Salam, kekavigi.&lt;/p&gt;
</ns0:content>
    </ns0:entry>
    <ns0:entry>
        <ns0:id>https://kekavigi.xyz/blog/kabar-singkat-pertama-di-tahun-2026</ns0:id>
        <ns0:title type="html">Kabar Singkat Pertama di Tahun 2026</ns0:title>
        <ns0:link href="https://kekavigi.xyz/blog/kabar-singkat-pertama-di-tahun-2026" rel="alternate" type="text/html" title="Kabar Singkat Pertama di Tahun 2026" />
        <ns0:published>2026-02-17T00:00:00+08:00</ns0:published>
        <ns0:author>
            <ns0:name>A. Keyka Vigiliant</ns0:name>
        </ns0:author>
        <ns0:rights>© 2016-2026 A. Keyka Vigiliant CC BY-SA 4.0</ns0:rights>
        <ns0:summary type="html">Oke… ini sudah terlihat seperti drama serial kehidupan.</ns0:summary>
        <ns0:content type="html" xml:base="https://kekavigi.xyz/blog/kabar-singkat-pertama-di-tahun-2026">&lt;p&gt;Wow, tidak menyangka hampir delapan purnama tidak menerbitkan tulisan di tempat ini. Keadaan saya baik-baik saja (setidaknya sampai hari ini), dan tidak ada masalah besar dalam pekerjaan (setidaknya sampai hari ini). Hmm... kalau ditanya apa saja hal bermakna selama delapan bulan terakhir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Saya menyewa VPS. Cukup &lt;em&gt;wow&lt;/em&gt; di kantong, dan terkadang saya berpikir jika banyak tindakan yang saya lakukan setelah melakukan transaksi itu, adalah contoh-contoh klasik &lt;em&gt;sunk-cost fallacy&lt;/em&gt;. Setidaknya ketika melihat dari sudut pandang positif -- dengan kata lain, menutup mata terkait hal-hal buruk yang terjadi -- ini adalah keputusan yang membuat pikiran lega, karena saya tidak perlu menyiksa laptop dengan mengubahnya menjadi server.&lt;/li&gt;
&lt;li&gt;Melanjutkan poin sebelumnya, saya akhirnya bisa mempublikasikan proyek &lt;code&gt;chess_cache&lt;/code&gt; saya di &lt;a target="_blank" rel="noreferrer" href="https://cc.kekavigi.xyz"&gt;cc.kekavigi.xyz&lt;/a&gt; dan repo Fossil terkait di &lt;a target="_blank" rel="noreferrer" href="https://fossil.kekavigi.xyz"&gt;fossil.kekavigi.xyz&lt;/a&gt;. Saya bisa meng-&lt;em&gt;host&lt;/em&gt; Jupyter Lab sendiri, sehingga saya bisa menggunakannya ketika lagi membutuhkan, tetapi tidak ada akses ke laptop pribadi (misalnya saat di depan komputer kantor, dan mendapati suatu masalah yang jauh lebih cepat pakai Python ketimbang Excel). Saya juga kedepannya mungkin akan meng-&lt;em&gt;host&lt;/em&gt; beberapa hal lain.&lt;/li&gt;
&lt;li&gt;Untuk &lt;code&gt;chess_cache&lt;/code&gt;, saya akhirnya bisa membuat &lt;em&gt;prototipe&lt;/em&gt; cara melakukan analisa secara terdistribusi: komputer (yang selanjutnya saya sebut minion) akan meminta daftar posisi dari server, menganalisanya, lalu mengirimkan hasilnya ke server. Sederhana, jadi bingung penyebab saya butuh hampir (atau lebih dari) satu tahun untuk menulisnya. Oh... namun malangnya minion belum bisa dijalankan di Windows (tapi kalau membandingkan Windows dengan bukan-Windows, saya rasa ini sesuatu yang dapat dimaklumi) dan masih ada berbagai masalah &amp;quot;kecil&amp;quot; lainnya (yah tapi namanya juga rancangan).&lt;/li&gt;
&lt;li&gt;Melakukan satu peningkatan kualitas hidup: membuat diri agar mudah untuk dapat membaca buku. Jadi ketika saya tidak membaca buku, saya dapat mengerti dengan sepenuh jiwa, akal, dan raga, itu disebabkan karena saya malas dan bukan suatu hambatan/kesulitan. Setelah menulis kalimat ini, wow... saya ternyata sadis juga.&lt;/li&gt;
&lt;li&gt;Kantor &amp;quot;menyediakan&amp;quot; waktu untuk olahraga bersama, dan itu adalah anugerah bagi saya yang sering kali malas olahraga sendirian (karena tidak menemukan alasan SMART -- Spesific, Measureable, Achiveable, Relevant, Time-bound -- untuk melakukannya).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Saya penasaran hal-hal apa yang akan terjadi dan berhasil saya lakukan di tahun 2026 ini, karena oh boi banyak keinginan yang tidak sempat/malas saya catat di halaman daftar &lt;a href="/notes/koleksi-proyek-sampingan"&gt;koleksi proyek sampingan&lt;/a&gt;. Saya juga berharap bahwa &amp;quot;&lt;em&gt;hal-hal yang akan terjadi ... di tahun 2026 ini&lt;/em&gt;&amp;quot; itu bukanlah suatu katastrofa seperti perang dunia atau krisis moneter, kejadian luar biasa seperti negara-negara secara bersama-sama sekaligus sendiri-sendiri men-&lt;em&gt;downgrade&lt;/em&gt; Internet menjadi Intranet (&lt;em&gt;splinter-net&lt;/em&gt;?), pemerintah RI melakukan sesuatu yang ditolak/dikecam banyak pihak, maupun kejadian-kejadian diluar akal sehat saya.&lt;/p&gt;
&lt;p&gt;Yah... apapun yang nantinya terjadi, baik atau buruk, tidak ada salahnya untuk tetap berdoa dan bekerja, karena &lt;em&gt;whatever will be, will be...&lt;/em&gt; &lt;a target="_blank" rel="noreferrer" href="https://www.youtube.com/watch?v=tV0CegHzums"&gt;&lt;em&gt;que será, será&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Salam, kekavigi.&lt;/p&gt;
</ns0:content>
    </ns0:entry>
    <ns0:entry>
        <ns0:id>https://kekavigi.xyz/blog/kabar-singkat</ns0:id>
        <ns0:title type="html">Kabar Singkat</ns0:title>
        <ns0:link href="https://kekavigi.xyz/blog/kabar-singkat" rel="alternate" type="text/html" title="Kabar Singkat" />
        <ns0:published>2025-06-02T00:00:00+08:00</ns0:published>
        <ns0:updated>2025-08-20T00:00:00+08:00</ns0:updated>
        <ns0:author>
            <ns0:name>A. Keyka Vigiliant</ns0:name>
        </ns0:author>
        <ns0:rights>© 2016-2026 A. Keyka Vigiliant CC BY-SA 4.0</ns0:rights>
        <ns0:summary type="html" />
        <ns0:content type="html" xml:base="https://kekavigi.xyz/blog/kabar-singkat">&lt;p&gt;Saya mengganggap diri saya kurang emosional: menjalani aktivitas dengan pikiran, apa yang seharusnya terjadi, apa yang sebaiknya terjadi. Ketika kondisi menguntungkan, &lt;em&gt;oh oke&lt;/em&gt; dan menyesuaikan tindakan; Ketika kondisi merugikan, &lt;em&gt;oh oke&lt;/em&gt; dan menyesuaikan tindakan. Alasan perilaku itu karena saya mengamati, bahwa saya-saat-emosi tidak mengambil keputusan yang sama saat tidak-emosi; umumnya karena salah menaksir peluang/resiko suatu tindakan (lebih optimis atau pesimis dari standar ideal pribadi). Penyebab lain, karena saya tidak/sulit menilai emosi: saya tidak tahu parameter apa saja, yang menurut orang lain atau masyarakat gunakan, mengategorikan emosi menjadi &amp;quot;sedih&amp;quot;, &amp;quot;bahagia&amp;quot;, &amp;quot;marah&amp;quot;, dll. Menggunakan konsep yang berbeda (salah paham) saat berkomunikasi (atau memaknai komunikasi yang terjadi) adalah hal yang buruk bagi semua pihak yang ikut bagian. Ibaratnya seperti orang yang mengernyitkan wajah padahal Anda berkata &amp;quot;parfum ini baunya oke,&amp;quot; saya tidak ingin orang mengernyitkan wajah ketika saya berkata &amp;quot;saya sedang merasa [nama emosi].&amp;quot;&lt;/p&gt;
&lt;p&gt;Akan tetapi ada kalanya saya merasa nyaman&lt;sup class="footnote-ref" id="fnref-1"&gt;&lt;a href="#fn-1"&gt;1&lt;/a&gt;&lt;/sup&gt; untuk &amp;quot;melanggar&amp;quot; paragraf &amp;quot;prinsip pribadi&amp;quot; di atas, dan mengatakan bahwa saya merasa senang dan cemas, setidaknya untuk beberapa bulan terakhir dan yang akan datang.&lt;/p&gt;
&lt;p&gt;Ah, ada baiknya saya menjelaskan kalimat itu, dimulai dari emosi yang masyarakat anggap positif: &amp;quot;senang.&amp;quot; Saya mengganggap diri saya senang karena: ada sekelompok hal yang saya cita-citakan terjadi, saat ini sebagiannya sedang terjadi, dan saya mengganggap peluang sisanya terjadi cukup signifikan. Hal-hal ini menurut saya bisa memperbanyak kemampuan diri, pengalaman, dan/atau ilmu saya; dan menghasilkan perilaku yang sama seperti saat saya mendengarkan orang lain &amp;quot;mendapatkan tujuan hidup.&amp;quot; Apa saja hal-hal itu?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;program penyinggah mesin catur, yang detailnya bisa Anda lihat di beberapa tulisan saya sebelum ini. Jika dilihat apa adanya, ini proyek yang sederhana dan biasa saja. Namun saya menemukan banyak kesempatan belajar dan bereksperimen dalam membuat program ini: &lt;em&gt;threading&lt;/em&gt; dan &lt;em&gt;multiprocessing&lt;/em&gt;, &lt;em&gt;webserver&lt;/em&gt;, basis data, membuat dokumentasi, dsb. Hal-hal yang ingin saya lakukan/pelajari tetapi tidak &amp;quot;kesampaian&amp;quot;, saya dapat praktekkan disini.&lt;/li&gt;
&lt;li&gt;Usaha membuat program penyinggah juga memungkinkan saya belajar SCM (&lt;em&gt;source control management&lt;/em&gt;) selain Git, yakni &lt;a target="_blank" rel="noreferrer" href="https://fossil-scm.org/"&gt;Fossil&lt;/a&gt;; menggunakan &lt;a target="_blank" rel="noreferrer" href="https://neovim.io"&gt;Neovim&lt;/a&gt; (walau saya belum memasang LSP untuk mempermudah aktivitas &lt;em&gt;ngoding&lt;/em&gt;); menggunakan konsol teks (&lt;em&gt;text console&lt;/em&gt;); dan belajar perintah-perintah Linux.&lt;/li&gt;
&lt;li&gt;Kondisi Internet dan ilmu yang saya dapatkan saat &lt;a href="/blog/terpaksa-belajar-membuat-bom"&gt;membuat bom kompresi&lt;/a&gt;, membuat saya nyaman untuk membuka blog ini untuk di-&lt;em&gt;crawl&lt;/em&gt;. Saya tidak menyarankan untuk men-&lt;em&gt;scraping&lt;/em&gt; situs, tetapi jika ingin mencoba, itu urusan yang ingin mencoba. Tentu pemikiran ini berlawanan dengan hal yang saya sampaikan dalam tulisan itu, tapi tulisan waktu itu tidak menduga kondisi waktu ini.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sedangkan untuk emosi kedua, cemas, itu karena saya menduga ada hal-hal eksternal (hal-hal yang secara praktikal tidak dapat saya pengaruhi) dengan potensi untuk mengubah cara saya beraktivitas menjadi lebih buruk/sulit, dengan mekanisme yang tidak disangka; yang hanya dari dugaan itu saja, telah mengubah cara saya beraktivitas dan/atau berpikir. Malangnya, saya tidak menemukan sesuatu yang spesifik, yang tepatnya menjadi alasan saya cemas. Namun, itu berkaitan dengan teknologi/informasi di Indonesia: Pusat Data Nasional yang &lt;a target="_blank" rel="noreferrer" href="https://katadata.co.id/digital/teknologi/66862b8b7f375/kronologi-pusat-data-nasional-diretas-hingga-pejabat-kominfo-mundur"&gt;diretas&lt;/a&gt; dan &lt;a target="_blank" rel="noreferrer" href="https://www.tempo.co/hukum/kronologi-dugaan-korupsi-pusat-data-nasional-sementara-1543851"&gt;diduga dikorupsi&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://nasional.kompas.com/read/2025/05/09/07054061/duduk-perkara-korupsi-satelit-yang-seret-eks-pejabat-kemenhan"&gt;pengadaan satelit yang dikorupsi&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://katadata.co.id/berita/nasional/6461c9962725f/kronologi-korupsi-proyek-bts-kominfo-hingga-rugikan-negara-rp-8-32-t"&gt;pengadaan BTS yang dikorupsi&lt;/a&gt;, pemblokiran situs seperti &lt;a target="_blank" rel="noreferrer" href="https://tekno.kompas.com/read/2022/07/30/10500057/paypal-kena-blokir-kominfo-juga-padahal-sudah-terdaftar-pse"&gt;PayPal (tahun 2022)&lt;/a&gt; dan baru-baru ini &lt;a target="_blank" rel="noreferrer" href="https://safenet.or.id/id/2025/05/setop-langgar-hak-akses-informasi/"&gt;Internet Archive&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://www.kompas.com/tren/read/2024/07/10/103000265/dokter-forensik-ungkap-penyebab-wartawan-di-karo-meninggal-dunia"&gt;wartawan dan keluarganya dibakar&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://nasional.kompas.com/read/2025/03/22/11221261/teror-kepala-babi-untuk-tempo-ancaman-nyata-terhadap-pers-di-indonesia"&gt;paket kepala babi ke kantor Tempo&lt;/a&gt;, berbagai kebobolan data di berbagai sektor (tanya saja ke mesin pencari), dan berita-berita lainnya yang tiba-tiba muncul di TV. Kejadian-kejadian ini membuat saya cemas.&lt;/p&gt;
&lt;p&gt;Yah, sesuai ungkapan &amp;quot;namanya juga hidup, ada untung ada buntung,&amp;quot; saya merasa normal saja&lt;sup class="footnote-ref" id="fnref-2"&gt;&lt;a href="#fn-2"&gt;2&lt;/a&gt;&lt;/sup&gt; merasakan dua emosi yang berlainan itu.&lt;sup class="footnote-ref" id="fnref-3"&gt;&lt;a href="#fn-3"&gt;3&lt;/a&gt;&lt;/sup&gt;. Dan... oke, ini saja kabar singkat dari saya, sampai jumpa di tulisan berikutnya.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mengakhiri tulisan tepat setelah menulis &amp;quot;Kejadian-kejadian ini membuat saya cemas.&amp;quot; memang terlihat absurd. Namun saya memang berniat hanya untuk menyampaikan kabar singkat kondisi saya, bukan membuat analis, kesimpulan panjang lebar, maupun menyusun daftar tindakan, terkait emosi-emosi ini.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Pembaruan Agustus 2025&lt;/h2&gt;
&lt;p&gt;Ada beberapa perubahan dan hasil kecil sejak saya menulis bagian di atas.&lt;/p&gt;
&lt;p&gt;Pertama, program penyinggah analisa mesin catur sekarang lebih matang. Saya berhasil membuat semacam halaman kuis, yang menyajikan suatu posisi catur yang telah dianalisa secara acak (pada rentang skor yang ditetapkan oleh pengguna), dan selanjutnya menanyakan balasan terbaik untuk posisi tersebut. Saya menganggap &amp;quot;kuis&amp;quot; ini lebih baik dari versi halaman latihan di Chess.com dan Lichess, karena saya dapat berlatih mencari balasan terbaik pada posisi-posisi yang tidak menguntungkan (misal, posisi dengan skor -2 atau lebih buruk). Saat mengerjakan itu, saya sempat kebingungan dengan SQLite, karena &lt;a target="_blank" rel="noreferrer" href="https://sqlite.org/forum/forumpost/2425dac696507f08"&gt;dua perintah SQL yang setara memiliki selisih waktu eksekusi yang besar&lt;/a&gt;. Saya sampai saat ini belum mendapat jawaban pasti, tetapi menduga dari tampilan &lt;a target="_blank" rel="noreferrer" href="https://sqlite.org/opcode.html"&gt;opcode&lt;/a&gt;, hal tersebut adalah dampak dari keputusan saya membuat tabel (untuk menyimpan hasil analisa) dengan &lt;code&gt;WITHOUT ROWID&lt;/code&gt;. Walaupun saya terpaksa membuat perintah SQL yang lebih rumit, tabel &lt;code&gt;WITHOUT ROWID&lt;/code&gt; lebih hemat memori penyimpanan ketimbang tabel SQL pada umumnya.&lt;/p&gt;
&lt;p&gt;Omong-omong tentang tabel, program penyinggah ini dapat dibuat &amp;quot;terdistribusi.&amp;quot; Setidaknya dalam pengertian, program ini dapat dipasang di banyak tempat, tetapi hanya menggunakan satu database untuk menyimpan data analisa; jadinya hasil analisa suatu pengguna program dapat dinikmati oleh semua pengguna lainnya. Tentu SQLite bukan solusi yang tepat untuk kasus ini, melainkan database &lt;em&gt;client-server&lt;/em&gt; seperti PostgreSQL. Namun kendalanya, saya tidak tahu cara mencegah seorang pengguna jahat yang entah-dengan-cara-apa mengirimkan hasil analisa palsu dan merusak konten database… oke, saya terpikir beberapa cara, dan dengan membaca beberapa buku/algoritma/referensi, mungkin tahu beberapa alternatif lain lagi maupun untung-ruginya. Namun kecuali seseorang (mungkin saya sendiri) sudi menyiapkan waktu luang beberapa hari dan beberapa kaleng kopi Nescafe; saya tidak ingin serius memikirkan masalah rumit ini.&lt;/p&gt;
&lt;p&gt;Kedua, saya kembali aktif menjawab soal-soal Project Euler. Dalam sembilan hari terakhir, saya berhasil menjawab sebelas soal; dan saya bangga dengan solusi-solusi yang saya buat, setidaknya dibandingkan dengan saya beberapa tahun lalu (mungkin karena tidak menduga solusi soal-soal itu bisa didapatkan di dalam waktu sekian detik saja). Selagi masih ada semangat ini, saya sedang aktif mengerjakan soal-soal OSN matematika tingkat SMA, dan mencoba mempelajari kurikulum pendidikan matematika SMA saat ini. Entahlah, mungkin menjadi pengajar tidak sebegitu menakutkan. Oh… semoga keseruan Project Euler ini cukup untuk saya ingin kembali membaca &lt;a target="_blank" rel="noreferrer" href="https://archive.org/details/probability-statistics-for-engineers-scientists_compress"&gt;buku statistika-nya Walpole&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Ketiga, saya berhasil menyederhanakan situs ini; setelah membaca dan mempertimbangkan isi tulisan &lt;a target="_blank" rel="noreferrer" href="https://endtimes.dev/why-your-website-should-be-under-14kb-in-size/"&gt;&lt;em&gt;Why your website should be under 14kB in size&lt;/em&gt;&lt;/a&gt; (dan beberapa referensi terkait). Walau juga saya sampaikan, saya melihat situs ini sudah optimal (keadaan saat ini sudah terletak di &lt;a target="_blank" rel="noreferrer" href="https://en.wikipedia.org/wiki/Pareto_front"&gt;batas Pareto&lt;/a&gt;); meningkatkan kualitas suatu aspek akan mengurangi kualitas setidaknya satu aspek lainnya. Karena saya ingin membuat situs lebih cepat di-&lt;em&gt;load&lt;/em&gt;, saya terpaksa rela melakukan hal-hal berikut:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Membuat tampilan situs (CSS) lebih sulit diubah, karena menaruh langsung &lt;em&gt;styling&lt;/em&gt; di tag &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; HTML ketimbang di berkas &lt;code&gt;.css&lt;/code&gt;. Saya punya beberapa alasan melakukan ini: pembaca situs ini praktis 0 (setelah mengecualikan mesin dan bot); jika pun ada pembaca, mereka realistisnya hanya mengunjungi kurang dari empat halaman (beranda, tulisan terbaru, mungkin arsip blog, dan kalau beruntung, tentang/biografi); pembaca realistisnya hanya berkunjung sesekali setiap beberapa minggu/bulan; ukuran CSS saya kecil, sampai-sampai lebih kecil dibandingkan dengan berkas CSS yang telah dikompresi; dan saya melihat dalam beberapa tahun kedepan, tidak akan terbesit niat untuk mengubah tampilan situs.&lt;/li&gt;
&lt;li&gt;Mengubah beberapa pengaturan di Cloudflare seperti mematikan deteksi bot dan meningkatkan lama penyinggahan. Ini membuat situs banyak dikerumuni bot, dan membuat perubahan pada konten situs tidak langsung terlihat oleh pembaca, tapi saya melihat ini sebanding dengan keuntungan yang didapatkan.&lt;/li&gt;
&lt;li&gt;Menghapus beberapa bita yang tidak dibutuhkan dari berkas HTML, seperti &lt;a target="_blank" rel="noreferrer" href="https://blog.joinmastodon.org/2024/07/highlighting-journalism-on-mastodon/"&gt;&lt;code&gt;&amp;lt;meta name=&amp;quot;fediverse:creator&amp;quot;&amp;gt;&lt;/code&gt;&lt;/a&gt; yang spesifikasinya kurang jelas, dan tag &lt;em&gt;pixel tracking&lt;/em&gt; yang kurang relevan karena praktis tidak ada yang membaca situs ini.&lt;/li&gt;
&lt;li&gt;Untuk konten dengan rumus matematika, saya memilih untuk tidak menggunakan &lt;code&gt;annotation&lt;/code&gt;-nya MathML, tidak menyajikan kapabilitas menyalin teks rumus sebagai kode LaTeX, dan hanya menggunakan fon bawaan browser/sistem untuk memformat tampilan rumus; ini adalah tarik-ulur (&lt;em&gt;trade-off&lt;/em&gt;) tersulit dalam saga ini, karena rumus matematika sudah rumit, dan saya ingin tampilan yang buruk (dari sudut pandang saya) memperumit keadaan itu. Namun uh… mungkin ini sepadan dengan keuntungan yang didapat?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Masih ada beberapa hal lain yang terjadi, tetapi mereka belum bertunas; belum layak untuk dikabarkan. Jadi, cukup sekian dan sampai jumpa lain hari? Terima kasih sudah membaca.&lt;/p&gt;
&lt;h2&gt;Catatan kaki&lt;/h2&gt;
&lt;section class="footnotes"&gt;
&lt;ol&gt;
&lt;li id="fn-1"&gt;&lt;p&gt;Mungkin lebih tepatnya, saya tidak menemukan resiko, atau menganggap resiko dapat diurus tanpa usaha yang berarti, dari tindakan yang sedang/akan saya lakukan.&lt;a href="#fnref-1" class="footnote"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li id="fn-2"&gt;&lt;p&gt;Tidak merasa kewalahan sampai kesulitan melakukan rutinitas sehari-hari.&lt;a href="#fnref-2" class="footnote"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li id="fn-3"&gt;&lt;p&gt;Setidaknya saya mengganggap hahwa, masyarakat mengganggap bahwa kedua emosi tersebut berlainan. Apakah anggapan ini masuk akal? saya tidak/belum mencari tahu.&lt;a href="#fnref-3" class="footnote"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</ns0:content>
    </ns0:entry>
    <ns0:entry>
        <ns0:id>https://kekavigi.xyz/blog/terpaksa-belajar-membuat-bom</ns0:id>
        <ns0:title type="html">Terpaksa Belajar Membuat Bom</ns0:title>
        <ns0:link href="https://kekavigi.xyz/blog/terpaksa-belajar-membuat-bom" rel="alternate" type="text/html" title="Terpaksa Belajar Membuat Bom" />
        <ns0:published>2025-05-13T00:00:00+08:00</ns0:published>
        <ns0:author>
            <ns0:name>A. Keyka Vigiliant</ns0:name>
        </ns0:author>
        <ns0:rights>© 2016-2026 A. Keyka Vigiliant CC BY-SA 4.0</ns0:rights>
        <ns0:summary type="html">Ini baru gestur jari tengah, belum mengepalkan tangan, maupun mengayunkannya ke muka.</ns0:summary>
        <ns0:content type="html" xml:base="https://kekavigi.xyz/blog/terpaksa-belajar-membuat-bom">&lt;p&gt;Hmm... entah bagaimana cara memulai tulisan ini. Oke, mungkin ini lebih baik. Anda membaca blog saya; kebanyakan isinya adalah diari yang ditulis dengan kurang jelas. Ya, saya mengatakan itu dengan ukuran standar saya. Walau saya berusaha menulis sebaik mungkin, dengan argumen-argumen yang masuk akal, dan tidak ada pernyataan yang salah, sebagian dari isi pikiran saya menganggap ini adalah sekadar hobi; tidak perlu serius seperti dulu saya menulis skripsi. Akibatnya, saya tidak mengharapkan tulisan di blog ini (mau) dibaca orang. Bahkan sejak tahun 2016 lalu. Jika ternyata ada yang (mau) membaca, terima kasih! Saya tidak menyangka hal itu, dan... maaf jika gaya menulis saya agak berbeda-beda antar postingan.&lt;/p&gt;
&lt;p&gt;Blog ini adalah pojok terpencil di Internet, tempat saya menulis, sunyi, dan itu semua terasa nyaman bagi saya. Namun Negara Api datang menyerang.&lt;/p&gt;
&lt;p&gt;Sekitar setahun lalu, saya pindah ke dari Github Pages ke Cloudflare untuk mengurus blog ini. Ada banyak pro-kontra di pikiran saya memilih (dan tetap menggunakan) Cloudflare, tapi yah... itu adalah opsi yang cocok untuk saya saat itu (dan sekarang). Sialnya, fitur di Cloudflare yang saya kurang sukai adalah penyebab saya mengetahui seberapa gilanya kejadian yang menimpa blog ini. Begitu gilanya, sampai saya merasa perlu berterima kasih pada Cloudflare (yang selanjutnya saya singkat CF) punya fitur ini. Untuk bisa tidak menyukai sekaligus berterima kasih? Wow, itu olahraga mental yang melelahkan.&lt;/p&gt;
&lt;p&gt;Mari saya jelaskan maksud saya tadi. Saya kurang menyukai aktivitas &lt;em&gt;logging&lt;/em&gt; (pencatatan) kunjungan situs. Saya saat ini bahkan merasa bimbang, jikalau alasan saya melakukan &lt;em&gt;logging&lt;/em&gt; sebenarnya cuma untuk mengejar suatu metrik yang tak berarti (dalam konteks tujuan saya menulis di blog ini). Tapi CF? CF terasa seperti mencatat semuanya; saya cuma tidak bisa mengakses semuanya itu karena akun saya ada di versi gratisan. Awalnya, yang tercatat di log adalah semua kunjungan yang saya lakukan. Namun entah sejak kapan, CF selalu mencatat ada lebih dari dua ribu pengunjung unik blog ini setiap bulannya. Ini omong kosong, lebih kosong daripada apa yang fisika kuantum perbolehkan. Siapa saya, sampai ada dua ribu manusia berbeda sudi mengunjungi situs saya?&lt;/p&gt;
&lt;p&gt;Di halaman &lt;em&gt;Analytics&lt;/em&gt; di menu &lt;em&gt;Security&lt;/em&gt;, Cloudflare menampilkan maksimal seratus sampel log kunjungan situs. Walaupun &lt;a target="_blank" rel="noreferrer" href="https://developers.cloudflare.com/analytics/graphql-api/sampling/"&gt;hanya sampel&lt;/a&gt;, dan hanya seratus log yang bisa saya lihat, saya tidak dapat menahan diri, untuk tidak berprasangka siapa biang kerok kegilaan ini. Sebagian besar adalah &lt;em&gt;crawler&lt;/em&gt;, yang menguji semua jalur URL WordPress yang mungkin memiliki celah keamanan (contohnya &lt;code&gt;/xmlrpc.php&lt;/code&gt; dan yang berawalan &lt;code&gt;/wp-content/plugins/&lt;/code&gt;). Sisanya adalah &lt;em&gt;scraper&lt;/em&gt; (pengais) yang secara presisi membaca semua tulisan saya. Oh itu jelas bot: tidak ada ada manusia normal yang bisa membuka semua halaman di situs saya dalam waktu kurang dua detik. Jikalau ada, untuk apa? Saya ingin berkata saya merasa risih, tapi dua ribu kunjungan per bulan? siapapun mereka, tindakan itu terlalu beringas.&lt;/p&gt;
&lt;p&gt;Saya sudah mencoba solusi-solusi yang ditawarkan CF, seperti &lt;a target="_blank" rel="noreferrer" href="https://developers.cloudflare.com/bots/get-started/bot-fight-mode/#enable-bot-fight-mode"&gt;&lt;em&gt;Bot Fight Mode&lt;/em&gt;&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://developers.cloudflare.com/bots/get-started/bot-fight-mode/#block-ai-bots"&gt;&lt;em&gt;Block AI Bot&lt;/em&gt;&lt;/a&gt;, dan beragam kombinasi Rules yang bisa saya pikirkan. Namun saya melihat itu tidak efektif. Bot tetap membabi buta, dan (tergantung &lt;em&gt;rule&lt;/em&gt; yang dibuat), saya malah dianggap bot oleh CF (dan butuh waktu lama untuk CF menyadari saya manusia).&lt;/p&gt;
&lt;p&gt;Ya, mungkin mustahil mencegah bot mengunjungi situs, tetapi tidak ada salahnya mengacungkan jari tengah selagi mereka melakukannya.&lt;/p&gt;
&lt;p&gt;Saat ini ada banyak proyek untuk menjawab masalah bot (khususnya yang mengais konten), beberapa diantaranya adalah &lt;a target="_blank" rel="noreferrer" href="https://iocaine.madhouse-project.org/"&gt;iocaine&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://zadzmo.org/code/nepenthes/"&gt;Nephentes&lt;/a&gt;, dan &lt;a target="_blank" rel="noreferrer" href="https://marcusb.org/hacks/quixotic.html"&gt;Quixotic&lt;/a&gt; (yang mungkin cocok untuk situs statis seperti blog saya). Itu keren, tapi saya masih ragu untuk menyertakan tulisan yang secara matematis lebih buruk daripada tulisan saya, ke blog ini. Lalu saya bertemu dengan tulisan &lt;a target="_blank" rel="noreferrer" href="https://idiallo.com/blog/zipbomb-protection"&gt;&lt;em&gt;I use Zip Bombs to Protect my Server&lt;/em&gt;&lt;/a&gt; oleh Ibrahim Diallo. Idenya sederhana: Anda mungkin pernah mengompres (beberapa) berkas dokumen, atau gambar, atau apapun itu menjadi satu berkas &lt;code&gt;.zip&lt;/code&gt; (atau &lt;code&gt;.rar&lt;/code&gt;, &lt;code&gt;.7z&lt;/code&gt;, dst.) yang ukurannya lebih kecil. Nah, kita juga bisa merancang berkas &lt;code&gt;.zip&lt;/code&gt; khusus yang ukurannya kecil, disebut &lt;a target="_blank" rel="noreferrer" href="https://en.wikipedia.org/wiki/Zip_bomb"&gt;&lt;em&gt;compression bomb&lt;/em&gt;&lt;/a&gt;, yang ketika di-dekompres akan menghasilkan sesuatu yang sangat besar; program apapun yang mencoba men-dekompres-nya akan melakukan &lt;em&gt;seppuku&lt;/em&gt; digital. Lebih tepatnya program yang tidak menyangka ada yang tega memberi mereka &lt;em&gt;compression bomb&lt;/em&gt;. Oke, ini terasa seperti ide yang keren untuk dicoba: membuat bom yang tidak disangka oleh para pengais.&lt;/p&gt;
&lt;p&gt;Hal yang selanjutnya saya tulis disini adalah iterasi pertama bom ini. Saya belum mengecek dampaknya pada perangkat (sistem? program?) pengais seperti &lt;a target="_blank" rel="noreferrer" href="https://crawlee.dev/"&gt;crawlee&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://scrapy.org/"&gt;scrapy&lt;/a&gt;, dan sebagainya. Saya hanya bisa melihat bom ini bisa membuat peramban (&lt;em&gt;browser&lt;/em&gt;) menjadi tidak responsif.&lt;/p&gt;
&lt;p&gt;Kode yang digunakan oleh Ibrahim untuk membuat dan menyajikan bom keren. Namun saya melihat beberapa hal yang membuat bom sulit diendus. Pertama, menambahkan metadata &lt;a target="_blank" rel="noreferrer" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Type"&gt;&lt;code&gt;Content-Type&lt;/code&gt;&lt;/a&gt; dengan nilai &lt;code&gt;text/html; charset=utf-8&lt;/code&gt;. Lalu, ada baiknya kita menjaga agar ukuran bom kurang dari satu-dua megabita (berkas HTML berukuran lebih dari itu tidak umum dijumpai). Selain itu, tidak ada salahnya membuat bom dengan teknik kompresi lainnya yang umum digunakan untuk mengompres berkas HTML, seperti &lt;a target="_blank" rel="noreferrer" href="https://en.wikipedia.org/wiki/Brotli"&gt;&lt;code&gt;brotli&lt;/code&gt;&lt;/a&gt; dan &lt;a target="_blank" rel="noreferrer" href="https://en.wikipedia.org/wiki/Zstd"&gt;&lt;code&gt;zstd&lt;/code&gt;&lt;/a&gt;. Karena saya tidak yakin efektivitas bom yang dibuat dengan menggunakan &lt;code&gt;dd if=/dev/zero&lt;/code&gt;, saya mengubah cara membuat bom menjadi berikut&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;4&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tr&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\n&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;bs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1k&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;notrunc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;iflag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fullblock&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gzip&lt;span class="w"&gt; &lt;/span&gt;--best&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-w0&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bomb.gz.base64&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(untuk &lt;code&gt;brotli&lt;/code&gt; dan &lt;code&gt;zstd&lt;/code&gt; yang memiliki kemampuan kompresi yang lebih baik, saya meningkatkan &lt;code&gt;count&lt;/code&gt; menjadi &lt;code&gt;40*1024*1024&lt;/code&gt; dan &lt;code&gt;10*1024*1024&lt;/code&gt;). Isi dari berkas &lt;code&gt;bomb.gz.base64&lt;/code&gt; (dan sejenisnya untuk teknik kompresi lainnya), saya salin ke kode Javascript berikut:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;onRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;encHeader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;accept-encoding&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;zstd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serveCompressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zstdDataBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;zstd&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;br&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serveCompressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;brotliDataBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;br&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serveCompressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gzipDataBase64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;gzip&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Raw version not available&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;406&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serveCompressed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binaryData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;atob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;base64Data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binaryData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;encodeBody&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;manual&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Type&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;text/html; charset=utf-8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Content-Encoding&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Cache-Control&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;no-transform&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// oh... juga pastikan tidak ada rule di pengaturan CF situs Anda,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// yang mengompres dan/atau menyinggah halaman hasil function ini &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zstdDataBase64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;KLUv/QRYVAAAEDQ0AQD7/znAAgIAEDQCABA0AgAQNAIAEDQCABA0AgAQN...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;brotliDataBase64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;z///f/glaOKxQCD3/p/////wSwDEYQGA7v0/////4ZcAiMMiAN37f/7...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gzipDataBase64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;H4sIAAAAAAACA+zBgQAAAACAIKn92RapCgAAAAAAAAAAAAAAAAAAAAAAA...&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Berkas Javascript ini selanjutnya saya simpan di &lt;code&gt;/functions/hello.js&lt;/code&gt;, seperti dokumentasi &lt;a target="_blank" rel="noreferrer" href="https://developers.cloudflare.com/pages/functions/"&gt;Functions-nya Couldflare Pages&lt;/a&gt;. Hasil dari fungsi ini dapat dilihat di &lt;a href="https://kekavigi.xyz/hello"&gt;kekavigi.xyz/hello&lt;/a&gt;. Setelah bom dibuat, saya membuat &lt;a target="_blank" rel="noreferrer" href="https://developers.cloudflare.com/rules/transform/url-rewrite/"&gt;Rewrite Rule&lt;/a&gt; yang memetakan semua URL yang berakhiran &lt;code&gt;.php&lt;/code&gt;, atau yang mengandung kata &lt;code&gt;wp&lt;/code&gt;, ke halaman bom. &lt;em&gt;Ada juga opsi untuk memetakan berdasarkan AS Number, Hostname, User Agent, dll., tapi saya ragu tingkat efektivitasnya; mengingat sifat pengais seperti virus/bakteri, akan selalu berubah mengakali kondisi lingkungan&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Satu pengalaman yang saya ingin bagikan, Cloudflare sulit ditebak. Terkadang metadata &lt;code&gt;Content-Length&lt;/code&gt; akan disertakan di &lt;em&gt;header&lt;/em&gt; respon HTTP, tanpa perlu ditulis di kode JS. Sebaliknya, terkadang metadata tersebut dihapus walau secara eksplisit ditulis di kode. Begitu pula dengan metadata &lt;code&gt;Content-Encoding&lt;/code&gt;. Baiklah, selama mengunduh halaman menggunakan &lt;code&gt;curl&lt;/code&gt; atau mengakses halaman lewat peramban akan &amp;quot;mengaktifkan&amp;quot; bom, terserah kamu Cloudflare. &lt;em&gt;Saya belum menutup kemungkinan penyebab respon yang flaky ini adalah skill-issue kemampuan Javascript diri saya&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Nantinya, saya akan &amp;quot;menanam&amp;quot; pranala-pranala ke halaman bom di keseluruhan blog ini. Ditanam sedemikian sehingga tidak terlihat -- menggunakan CSS -- oleh pembaca; hanya oleh pengais yang rakus. Apakah bom ini efektif? Jika suatu saat saya kembali menulis tentang mempertahankan blog dari pengais, kemungkinan besar jawabannya tidak. Ketika itu terjadi, saya akan teringat foto suatu bendera, bertuliskan &lt;em&gt;We do this not because it is easy, but because we thought it would be easy&lt;/em&gt;.&lt;/p&gt;
</ns0:content>
    </ns0:entry>
    <ns0:entry>
        <ns0:id>https://kekavigi.xyz/blog/membuat-penyinggah-mesin-catur</ns0:id>
        <ns0:title type="html">Membuat Penyinggah untuk Mesin Catur</ns0:title>
        <ns0:link href="https://kekavigi.xyz/blog/membuat-penyinggah-mesin-catur" rel="alternate" type="text/html" title="Membuat Penyinggah untuk Mesin Catur" />
        <ns0:published>2025-03-23T00:00:00+08:00</ns0:published>
        <ns0:updated>2025-03-29T00:00:00+08:00</ns0:updated>
        <ns0:author>
            <ns0:name>A. Keyka Vigiliant</ns0:name>
        </ns0:author>
        <ns0:rights>© 2016-2026 A. Keyka Vigiliant CC BY-SA 4.0</ns0:rights>
        <ns0:summary type="html">Ini adalah tulisan ketiga dalam upaya saya membuat pangkalan data analisis catur. Saya membuat teks ini dapat berdiri sendiri, jadi Anda tidak perlu membaca dua kisah sebelumnya.</ns0:summary>
        <ns0:content type="html" xml:base="https://kekavigi.xyz/blog/membuat-penyinggah-mesin-catur">&lt;p&gt;Mesin catur (&lt;em&gt;chess engine&lt;/em&gt;) adalah program yang memberikan analisis posisi catur, contohnya berupa skor evaluasi (perkiraan besar keuntungan/kerugian bagi pemain yang bersangkutan) dan langkah balasan terbaik untuk posisi itu. Contoh mesin ini adalah &lt;a target="_blank" rel="noreferrer" href="https://stockfishchess.org/"&gt;Stockfish&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://lczero.org/"&gt;Leela&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://komodochess.com/"&gt;Komodo&lt;/a&gt;, dan (mungkin sudah ada puluh) ribuan mesin lainnya. Sebagian besar, atau mungkin lebih tepatnya hampir semua, mesin-mesin ini adalah program berbasis teks, dan menggunakan &lt;a target="_blank" rel="noreferrer" href="https://www.wbec-ridderkerk.nl/html/UCIProtocol.html"&gt;protokol UCI&lt;/a&gt; untuk berkomunikasi. Sebagai contoh bentuk komunikasi, berikut (salah satu) cara menyuruh mesin menganalisis posisi yang dihasilkan dari &lt;code&gt;1.d4 d5 2.Nf3&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uci
ucinewgame
position startpos moves d2d4 d7d5 g1f3
go infinite
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setelah menuliskan itu, mesin akan memberikan analisisnya dalam baris-baris. Salah satu baris tersebut mungkin bertuliskan berikut&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;info multipv 1 depth 5 score cp -16 nodes 7825068 time 9914 nps 789294
hashfull 999 tbhits 0 pv e7e6 c2c4 g8f6 c4d5 e6d5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Terakhir ketika analisis dianggap sudah memuaskan, kita mengirim perintah &lt;code&gt;stop&lt;/code&gt;, dan mesin akan menyampaikan teks yang kurang lebih seperti &lt;code&gt;bestmove e7e6 ponder c2c4&lt;/code&gt;. Protokol UCI diterapkan untuk menyeragamkan format masukan/keluaran mesin-mesin catur, agar mereka lebih mudah digunakan dan dibandingkan satu sama lain.&lt;/p&gt;
&lt;p&gt;Namun jujur, mengetik semua perintah-perintah itu dan mencoba membaca baris-baris analisis yang mengalir deras di layar, bukanlah pengalaman pengguna (UX, &lt;em&gt;user experience&lt;/em&gt;) yang menyenangkan. Oleh karena itu, selanjutnya muncul antarmuka-antarmuka grafis catur (&lt;em&gt;chess GUI&lt;/em&gt;) yang memperbaiki hal itu; membuat tampilan menjadi seperti apa yang diharapkan oleh pemain catur pada umumnya. Anda cukup menggeser-geser atau mengeklik-klik, dan analisis posisi akan tertampil rapi. Ada juga antarmuka dengan fitur-fitur tambahan, seperti bertanding melawan mesin, menonton mesin lawan mesin, melihat statistik permainan secara daring, membuat berkas analisis yang disertai teks komentar (misal, menjelaskan alasan suatu balasan dianggap cerdas), dan lain sebagainya. Contoh antarmuka ini adalah &lt;a target="_blank" rel="noreferrer" href="https://encroissant.org/"&gt;En Croissant&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="https://github.com/rooklift/nibbler"&gt;Nibbler&lt;/a&gt;, dan banyak lainnya (saya hanya menyertakan antarmuka yang sering saya pakai saja).&lt;/p&gt;
&lt;p&gt;Malangnya, ada satu hal yang saya anggap sebagai kekurangan (setidaknya dari aspek pengalaman pengguna) di &lt;em&gt;kebanyakan&lt;/em&gt; mesin maupun antarmuka catur: kemampuan untuk mengingat analisis posisi yang sudah dikerjakan. Ini maksud saya: misalkan Anda menganalisis posisi P dan membiarkan komputer bekerja setengah jam untuk menghasilkan analisis A yang memuaskan. Lalu Anda (secara tidak sengaja karena &lt;em&gt;mouse-slip&lt;/em&gt;) memulai menganalisis posisi baru. Ini akan menyebabkan data analisis A hilang, dan Anda harus menganalisis P selama setengah jam lagi(!) untuk mendapatkan analisis A kembali. Anda juga harus melakukan hal yang sama jika Anda membuat sesi baru di mesin/antarmuka. Antarmuka dengan fasilitas &amp;quot;Simpan&amp;quot; juga tidak menyimpan informasi ini, atau menyimpannya dalam bentuk yang secara praktis sama saja dengan tidak disimpan. Tentu kekurangan ini tidak berlaku di semua mesin dan antarmuka; walau saya tidak bisa memastikannya, ada mesin (contohnya &lt;a target="_blank" rel="noreferrer" href="https://www.hiarcs.com/"&gt;HIARCS&lt;/a&gt; dan &lt;a target="_blank" rel="noreferrer" href="https://github.com/amchess/BrainLearn"&gt;BrainLearn&lt;/a&gt;, &lt;a target="_blank" rel="noreferrer" href="http://www.rybkachess.com"&gt;Rybka&lt;/a&gt;) dan antarmuka (&lt;a target="_blank" rel="noreferrer" href="https://shop.chessok.com/index.php?main_page=product_info&amp;amp;products_id=984"&gt;Aquarium&lt;/a&gt;) yang menjawab masalah itu. Sialnya, solusi-solusi tersebut &lt;em&gt;ad-hoc&lt;/em&gt;, dan tidak menyelesaikan keadaan ketika, contoh, saya ingin antarmuka Nibbler dapat menyimpan analisis oleh mesin Stockfish, dan simpanan itu dipakai di antarmuka En Croissant.&lt;/p&gt;
&lt;p&gt;Ah, keadaan ini menyedihkan, dan itu memacu saya untuk membuat penyinggah (&lt;em&gt;caching&lt;/em&gt;) untuk menyimpan analisis mesin. Secara lebih mendetail, penyinggah akan menjadi perantara komunikasi dengan mesin. Ketika mesin menganalisis posisi P, hasil analisisnya akan dibandingkan dengan hasil di pangkalan data, lalu penyinggah akan menyajikan data yang lebih berkualitas (misal, dengan nilai &lt;code&gt;depth&lt;/code&gt; atau &lt;code&gt;nodes&lt;/code&gt; yang lebih besar) dan memperbarui data di pangkalan (kalau diperlukan). Dari seig prakteknya, saya nantinya tidak perlu takut kehilangan hasil analisis karena &lt;em&gt;mouse-slip&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Dari ide itu saya membuat prototipe program menggunakan Python, pangkalan data dengan SQLite, dan &lt;a target="_blank" rel="noreferrer" href="https://nuitka.net/"&gt;Nuitka&lt;/a&gt; untuk kompilasi kode menjadi &lt;em&gt;executable&lt;/em&gt;. Gambaran umum pembuatan program kurang lebih seperti berikut. Pertama, menulis program yang bertindak sebagai perantara (&lt;em&gt;man-in-the-middle&lt;/em&gt;) antara mesin dan apapun yang mencoba berkomunikasi dengannya. Lalu kita membuat cara memroses (&lt;em&gt;parsing&lt;/em&gt;) teks yang dikirim dari/ke mesin. Selanjutnya kita tinggal membentuk pangkalan data, cara menyinggah hasil analisis, dan menampilkan versi analisis yang lebih baik (antara mesin dan pangkalan data). Saya pikir kode saya sama rapinya dengan kebanyakan kode-kode mesin catur di Internet; dengan kata lain, bisa dipahami tetapi hati akan sedikit memanas. Jika Anda penasaran cara yang saya lakukan, kode sumber utuh dapat diakses di &lt;a href="/etc/share/chess_cache.py"&gt;&lt;code&gt;/etc/share/chess_cache.py&lt;/code&gt;&lt;/a&gt;. Terakhir, kita bisa menggunakan Nuitka untuk mengompilasi kode menjadi berkas &lt;em&gt;executable&lt;/em&gt;; berkas yang dihasilkan sekitar 50-60% lebih kecil daripada ketika menggunakan &lt;a target="_blank" rel="noreferrer" href="https://pyinstaller.org/en/stable/index.html"&gt;PyInstaller&lt;/a&gt;. Perintah yang saya gunakan adalah&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;nuitka&lt;span class="w"&gt; &lt;/span&gt;--standalone&lt;span class="w"&gt; &lt;/span&gt;--onefile&lt;span class="w"&gt; &lt;/span&gt;--lto&lt;span class="o"&gt;=&lt;/span&gt;yes&lt;span class="se"&gt;\&lt;/span&gt;
--python-flag&lt;span class="o"&gt;=&lt;/span&gt;unbuffered&lt;span class="w"&gt; &lt;/span&gt;--python-flag&lt;span class="o"&gt;=&lt;/span&gt;no_asserts&lt;span class="se"&gt;\&lt;/span&gt;
--python-flag&lt;span class="o"&gt;=&lt;/span&gt;no_docstrings&lt;span class="w"&gt; &lt;/span&gt;--python-flag&lt;span class="o"&gt;=&lt;/span&gt;static_hashes&lt;span class="se"&gt;\&lt;/span&gt;
--output-filename&lt;span class="o"&gt;=&lt;/span&gt;chess_cache&lt;span class="w"&gt; &lt;/span&gt;chess_cache.py&lt;span class="sb"&gt;`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Untuk melihat program ini beraksi, Anda terlebih dahulu perlu membuat berkas &lt;code&gt;settings.json&lt;/code&gt; di direktori yang sama dengan program &lt;code&gt;chess_cache&lt;/code&gt;. Berkas JSON ini berisi letak mesin yang analisisnya ingin Anda singgah, dan nama berkas untuk menyimpan data singgahan. Sebagai contoh:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;engine_path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/user/playground/stockfish&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;database_path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/user/playground/chess.sqlite&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lalu di Nibbler atau En Croissant (atau antarmuka yang Anda pakai), Anda tinggal menambahkan mesin baru dengan berkas &lt;em&gt;binary/executable&lt;/em&gt; &lt;code&gt;chess_cache&lt;/code&gt;. Antarmuka akan mengganggap mesin yang ditambahkan adalah mesin Stockfish (entah versi Stockfish apa yang Anda tetapkan di &lt;code&gt;settings.json&lt;/code&gt;), dan Anda dapat berinteraksi seperti biasa. Yeay!&lt;/p&gt;
&lt;p&gt;Tentu ini bukan hasil akhir. Untuk pengembangan selanjutnya, saya memikirkan cara-cara untuk: mengefisienkan penyimpanan data (info apa saja yang penting disinggah? apakah menyimpan &lt;code&gt;bestmove&lt;/code&gt; sebagai &lt;code&gt;INTEGER&lt;/code&gt; lebih efisien?), menyimpan metadata (mencegah pengguna tidak sengaja menggunakan pangkalan data yang sama untuk dua mesin berbeda), dan menggabungkan singgahan (sehingga pengguna dapat menikmati hasil singgahan yang dikumpulkan oleh pengguna-pengguna lainnya). Yeay! Ini menyenangkan, dan semoga saya bisa mengabarkan perkembangan selanjutnya.&lt;/p&gt;
&lt;p&gt;Sampai jumpa!&lt;/p&gt;
</ns0:content>
    </ns0:entry>
    <ns0:entry>
        <ns0:id>https://kekavigi.xyz/blog/database-catur-yang-minimalis</ns0:id>
        <ns0:title type="html">Database Catur yang Minimalis</ns0:title>
        <ns0:link href="https://kekavigi.xyz/blog/database-catur-yang-minimalis" rel="alternate" type="text/html" title="Database Catur yang Minimalis" />
        <ns0:published>2025-03-16T00:00:00+08:00</ns0:published>
        <ns0:author>
            <ns0:name>A. Keyka Vigiliant</ns0:name>
        </ns0:author>
        <ns0:rights>© 2016-2026 A. Keyka Vigiliant CC BY-SA 4.0</ns0:rights>
        <ns0:summary type="html">Ketimbang menceritakan keberhasilan, tulisan ini berisi tindakan-tindakan yang saya anggap gagal, dalam membuat database catur.</ns0:summary>
        <ns0:content type="html" xml:base="https://kekavigi.xyz/blog/database-catur-yang-minimalis">&lt;p&gt;Pada &lt;a href="/blog/membuat-database-posisi-catur"&gt;tulisan pertama terkait proyek ini&lt;/a&gt;, saya ingin membuat basis data posisi catur yang juga mencatat hal-hal seperti nilai WDL (&lt;em&gt;Win/Draw/Lose&lt;/em&gt;) dari pemain catur, dikelompokkan berdasarkan peringkat Elo mereka. Saya pikir ini akan mudah, karena beberapa situs terkait catur seperti Lichess menyediakan data ini lewat API mereka. Saya membuat implementasinya untuk setiap data yang saya anggap perlu ada di basis data nantinya. Butuh sekitar dua-tiga pekan sebelum saya menyadari, saya hanya mengerjakan hal-hal tersier; dan selama rentang waktu itu, saya tidak menemukan solusi mantap dalam mengumpulkan data terpenting: bagaimana cara mendapatkan balasan terbaik suatu posisi?&lt;/p&gt;
&lt;p&gt;Dalam beberapa pekan berikutnya, saya fokus mencoba menjawab masalah tersebut:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ide termudah adalah meng-kueri API Lichess. Namun jika saya diam dan merenungkan sejenak, ada tak hingga (secara praktikal) posisi catur permainan standar yang mungkin; hanya seperkian dari seperkian dari semua posisi catur itu, yang evaluasinya dicatat oleh Lichess. Lagipula menge-spam &lt;em&gt;request&lt;/em&gt; ke Lichess bukanlah hal yang pantas, setidaknya menurut saya.&lt;/li&gt;
&lt;li&gt;Oke, bagaimana dengan melakukan evaluasi Stockfish secara lokal, dan mencatat hasilnya ke basis data? Oh wow, kipas laptop saya mengerang seperti pesawat jet yang lepas landas; saya rasa ini bukan ide yang bagus.&lt;/li&gt;
&lt;li&gt;Oke… jadi… bagaimana jika membuat &lt;em&gt;chess engine&lt;/em&gt; baru, yang menggunakan diska dan bukan RAM sebagai penyimpanan memorinya? Mesin ini akan sangat lambat ketimbang pada dasarnya semua mesin catur. Namun harapannya, dengan menyimpan data secara &lt;em&gt;persistent&lt;/em&gt;, &lt;em&gt;eventually&lt;/em&gt; (entah kapan) mesin akan menghasilkan evaluasi yang sama baiknya dengan Stockfish. Setelah menerapkan Negamax dengan Stockfish sebagai fungsi evaluasi statis (karena saya tidak mau menghabiskan waktu belajar teori catur), saya baru menyadari hasil evaluasi Negamax suatu posisi belum tentu &lt;em&gt;back-propagate&lt;/em&gt; ke posisi induk. Sial. Saya selanjutnya membuat mesin yang menerapkan Monte Carlo Tree Search, yang secara desain menjawab masalah ini. Sialnya, tidak ada cara mudah membuat fungsi &lt;em&gt;policy head&lt;/em&gt; (ah iya, &lt;em&gt;no free-lunch theorem&lt;/em&gt;). Saya menghabiskan beberapa hari mengimplementasikan MCTS dengan heuristik AMAF (&lt;em&gt;All-Move As First&lt;/em&gt;) dan menyadari… ini hanya Minimax yang diglorifikasi.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Kegagalan di tiga (walau sebenarnya saya anggap empat) cara itu sempat menghancurkan semangat; saya beralih pada proyek lain. Namun satu pertanyaan tetap menghantui saya: data-data apa yang penting dan harus ada agar saya bisa mengganggap ini sebagai basis data catur? Saya duduk, dan merumuskan ini:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Posisi catur&lt;/strong&gt;, pastinya. Ini bisa disimpan dalam notasi FEN, tetapi agar hemat memori dan mudah digunakan (di SQLite), saya menggunakan &lt;a target="_blank" rel="noreferrer" href="https://github.com/official-stockfish/nnue-pytorch/blob/master/lib/nnue_training_data_formats.h#L4615"&gt;solusi oleh Tomasz Sobczyk&lt;/a&gt; (hanya menggunakan 24 bita) yang diubah menjadi teks dengan enkoding Z85.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;skor evaluasi&lt;/strong&gt;, menggunakan unit centipawn.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kedalaman evaluasi&lt;/strong&gt;, besar &lt;em&gt;ply&lt;/em&gt; yang dianalisis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;balasan terbaik&lt;/strong&gt;, menggunakan notasi UCI (e.g. &lt;code&gt;d2d4&lt;/code&gt;, atau angka seperti 731).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Setelah menjabarkan itu, saya mendapat ide, saya bisa membuat penyinggah untuk mesin catur!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Penyinggah akan menyimpan hasil analisis posisi oleh mesin catur&lt;/li&gt;
&lt;li&gt;Jika hasil analisis posisi yang ada di basis data lebih dalam (dari nilai &lt;code&gt;depth&lt;/code&gt;) ketimbang yang dihasilkan oleh mesin, gunakan hasil di basis data.&lt;/li&gt;
&lt;li&gt;Untuk mendapatkan &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="katex-base"&gt;&lt;span class="katex-strut" style="height:0.6833em;"&gt;&lt;/span&gt;&lt;span class="mord mathnormal" style="margin-right:0.109em;"&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; MultiPV, penyinggah akan membuat daftar semua balasan yang mungkin, mengueri basis data, mengurutkan hasil yang didapatkan berdasarkan skor dan/atau kedalaman, lalu menampilkan &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;math xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;N&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;&lt;/span&gt;&lt;span class="katex-html" aria-hidden="true"&gt;&lt;span class="katex-base"&gt;&lt;span class="katex-strut" style="height:0.6833em;"&gt;&lt;/span&gt;&lt;span class="mord mathnormal" style="margin-right:0.109em;"&gt;N&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; solusi terbaik.&lt;/li&gt;
&lt;li&gt;Semua perintah ke penyinggah akan diteruskan ke mesin catur dan hasil dari mesin catur ke pengguna.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ini adalah cara terbaik yang bisa saya pikirkan saat ini, karena pada dasarnya ini sama seperti menggunakan mesin catur; perbedaannya, penyinggah akan menampilkan solusi terbaik yang mesin pernah kerjakan, selagi mesin mencari solusi yang lebih baik (solusi dengan nilai &lt;em&gt;depth&lt;/em&gt; yang lebih besar).&lt;/p&gt;
&lt;p&gt;Malangnya, saya belum mengimplementasikan ide ini. Setidaknya ide ini memmbuat saya semangat belajar tentang IPC (*inter-process communication). Tunggu perkembangan saya (semoga) bulan depan ya!&lt;/p&gt;
</ns0:content>
    </ns0:entry>
    <ns0:entry>
        <ns0:id>https://kekavigi.xyz/blog/membuat-proksi-penyinggah</ns0:id>
        <ns0:title type="html">Membuat Proksi Penyinggah</ns0:title>
        <ns0:link href="https://kekavigi.xyz/blog/membuat-proksi-penyinggah" rel="alternate" type="text/html" title="Membuat Proksi Penyinggah" />
        <ns0:published>2025-02-09T00:00:00+08:00</ns0:published>
        <ns0:updated>2025-02-26T00:00:00+08:00</ns0:updated>
        <ns0:author>
            <ns0:name>A. Keyka Vigiliant</ns0:name>
        </ns0:author>
        <ns0:rights>© 2016-2026 A. Keyka Vigiliant CC BY-SA 4.0</ns0:rights>
        <ns0:summary type="html">Karena saya ingin bisa membaca kembali halaman di Internet tanpa koneksi Internet</ns0:summary>
        <ns0:content type="html" xml:base="https://kekavigi.xyz/blog/membuat-proksi-penyinggah">&lt;p&gt;Mungkin karena saya tumbuh bersama laptop yang sederhana, saya selalu mencari cara mengoptimasi penggunaan RAM. Contohnya di peramban (&lt;em&gt;browser&lt;/em&gt;), saya jarang membuka lebih dari lima-enam tab sekaligus; karena saya pernah mengalami rasanya putus asa, ketika laptop kala itu mendadak tidak responsif, dan suara kipas internalnya semakin nyaring seperti pesawat mau lepas landas. Kebiasaan itu terbawa sampai saat ini. Ketika &lt;em&gt;debugging&lt;/em&gt;, saya suka membaca dokumentasi, tulisan blog orang, atau sekadar mampir ke forum StackOverflow. Namun ketimbang membuka semua pranala di tab-tab baru, saya terbiasa untuk membaca tuntas konten di tab tersebut; menutupnya lalu membuka pranala baru; dan mengulangi alur tersebut. Terkadang saya beberapa kali menutup-membuka suatu halaman yang sama, karena ada detail yang saya lupakan (contoh, ketika membaca dokumentasi SQLAlchemy dan rclone). Malangnya, ini malah memperbesar total penggunaan Internet. Selain itu, saya tinggal di daerah Indihome dan PLN sering bermasalah; ketika koneksi Internet terputus, saya tidak bisa membaca kembali halaman yang baru saja ditutup.&lt;/p&gt;
&lt;p&gt;Jadi sederhananya, saya butuh cara untuk menyimpan semua halaman yang (pernah) saya akses, dan menyajikan halaman versi simpanan ketika koneksi Internet terputus. Terdengar sederhana, tetapi sebenarnya tidak; karena jika memang mudah, tidak ada yang menghentikan negara, perusahaan tempat Anda bekerja, penyedia jasa internet, dan bahkan alat-alat yang memungkinkan Anda mengakses Internet (seperti router WiFi dan &lt;em&gt;smartphone&lt;/em&gt;), untuk diam-diam menyimpan semua halaman yang Anda akses. Ada satu cara menjawab masalah itu, yakni dengan menerapkan &lt;a target="_blank" rel="noreferrer" href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack"&gt;&lt;em&gt;man-in-the-middle&lt;/em&gt; (MITM) &lt;em&gt;attack&lt;/em&gt;&lt;/a&gt;; menyusup dan menjadi perantara peramban (&lt;em&gt;browser&lt;/em&gt;) dengan Internet, seperti tukang pos yang diam-diam membuka surat dari seseorang, membaca (atau juga mengubah) isinya, sebelum mengirimkannya ke tujuan. Melakukan MITM sendiri dari nol adalah hal yang sulit, karena semua pihak yang berperan dalam mengelola keamanan di Internet, selalu mencari cara untuk menghindari itu bisa terjadi. Untungnya khusus untuk masalah saya, ada modul(?) &lt;a target="_blank" rel="noreferrer" href="https://mitmproxy.org/"&gt;&lt;code&gt;mitmproxy&lt;/code&gt;&lt;/a&gt; yang bertindak sebagai proksi dan menyediakan abstraksi dalam melakukan MITM.&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" rel="noreferrer" href="https://docs.mitmproxy.org/stable/overview-installation/"&gt;Memasang (&lt;em&gt;installing&lt;/em&gt;)&lt;/a&gt; dan menggunakan &lt;code&gt;mitmproxy&lt;/code&gt; cukup mudah. Namun tergantung bagaimana Anda mengatur sistem operasi di laptop dan peramban yang Anda pakai, mungkin ada beberapa langkah yang perlu ditambah/diganti. Misal kalau Anda menggunakan Linux dengan peramban Librewolf (Firefox dengan pengaturan privasi dan keamanam yang lebih baik), Anda perlu meng-import sertifikat mitmproxy ke &lt;em&gt;Certificate Manager&lt;/em&gt; dan mengubah pengaturan &lt;a target="_blank" rel="noreferrer" href="https://librewolf.net/docs/faq/#im-getting-mozilla_pkix_error_key_pinning_failure-what-can-i-do"&gt;&lt;code&gt;security.cert_pinning.enforcement_level&lt;/code&gt;&lt;/a&gt;, agar Librewolf tidak mengganggap &lt;code&gt;mitmproxy&lt;/code&gt; berbahaya, dan mau terhubung ke Internet.&lt;/p&gt;
&lt;p&gt;Baik, sekarang mari membahas &lt;code&gt;mitmproxy&lt;/code&gt; itu sendiri. Modul &lt;code&gt;mitmproxy&lt;/code&gt; memungkinkan kita membuat &lt;em&gt;addons&lt;/em&gt; (dengan bahasa Python) untuk menambahkan hal yang bisa dilakukan proksi. Malangnya, dokumentasi modul ini tidak lengkap, dan terkadang kita perlu menelusuri isu Github untuk mengetahui jawabannya. &lt;code&gt;mitmproxy&lt;/code&gt; menyimpan semua informasi terkait suatu transaksi HTTP dalam objek &lt;code&gt;HTTPFlow&lt;/code&gt;, yang selanjutnya saya sebut &lt;code&gt;flow&lt;/code&gt;. Dari banyak &lt;em&gt;event hooks&lt;/em&gt; yang diabstraksi &lt;code&gt;mitmproxy&lt;/code&gt;, ada tiga &lt;em&gt;hooks&lt;/em&gt; yang kita perlukan:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;request(flow)&lt;/code&gt; yang dipanggil ketika ada &lt;em&gt;HTTP request&lt;/em&gt; yang dikirimkan peramban ke proksi,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response(flow)&lt;/code&gt; ketika ada &lt;em&gt;HTTP response&lt;/em&gt; dari server ke proksi, dan&lt;/li&gt;
&lt;li&gt;&lt;code&gt;error(flow)&lt;/code&gt; ketika ada terjadi &lt;em&gt;HTTP error&lt;/em&gt;, contohnya koneksi Internet laptop yang terputus.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dalam kondisi ideal, proksi kita hanya perlu menyinggah semua &lt;code&gt;flow&lt;/code&gt; saat &lt;code&gt;response()&lt;/code&gt; dijalankan, misal ke database SQLite. Ketika koneksi Internet terputus, &lt;em&gt;hook&lt;/em&gt; &lt;code&gt;error()&lt;/code&gt; akan terpanggil, dan kita menampilkan &lt;code&gt;flow&lt;/code&gt; yang tersimpan di database, jika URL dari &lt;em&gt;request&lt;/em&gt; kita singgah. Sialnya, kita &lt;em&gt;tidak bisa&lt;/em&gt; mengubah konten galat HTML ketika &lt;em&gt;hook&lt;/em&gt; &lt;code&gt;error()&lt;/code&gt; terjadi, kecuali dengan &lt;em&gt;monkey-patching&lt;/em&gt; serius, yang mungkin bisa saja rusak setiap ada versi baru dari &lt;code&gt;mitmproxy&lt;/code&gt;. Saya tidak mau repot, dan mengambil solusi jalan pintas berikut:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dalam kondisi normal, &lt;code&gt;response()&lt;/code&gt; akan juga menyimpan &lt;code&gt;flow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Jika koneksi Internet terputus, saya akan membuat file &lt;code&gt;use_stale&lt;/code&gt; di direktori yang sama dengan &lt;em&gt;addons&lt;/em&gt;. Tindakan ini akan membuat &lt;code&gt;request()&lt;/code&gt; langsung mengisi konten HTML jika URL tersinggah, sehingga tidak ada &lt;em&gt;HTTP request&lt;/em&gt; yang terjadi dari proksi ke server (dan &lt;code&gt;error()&lt;/code&gt; tidak terpanggil).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ini &lt;em&gt;script&lt;/em&gt; yang menyelesaikan masalah saya:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;listdir&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;CachingProxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Database menggunakan aiosqlite, flow disimpan&lt;/span&gt;
        &lt;span class="c1"&gt;# sebagai pickle yang dikompresi dengan zlib&lt;/span&gt;

        &lt;span class="c1"&gt;# CREATE TABLE IF NOT EXISTS dict(&lt;/span&gt;
        &lt;span class="c1"&gt;#     host         TEXT NOT NULL,&lt;/span&gt;
        &lt;span class="c1"&gt;#     path         TEXT NOT NULL,&lt;/span&gt;
        &lt;span class="c1"&gt;#     content-type TEXT NOT NULL,&lt;/span&gt;
        &lt;span class="c1"&gt;#     timestamp    REAL NOT NULL,&lt;/span&gt;
        &lt;span class="c1"&gt;#     response     BLOB);&lt;/span&gt;
        &lt;span class="c1"&gt;# CREATE UNIQUE INDEX IF NOT EXISTS url ON dict(host,path);&lt;/span&gt;
        &lt;span class="c1"&gt;# PRAGMA foreign_keys = ON;&lt;/span&gt;
        &lt;span class="c1"&gt;# PRAGMA journal_mode = wal;&lt;/span&gt;
        &lt;span class="c1"&gt;# PRAGMA wal_autocheckpoint;&lt;/span&gt;
        &lt;span class="c1"&gt;# PRAGMA synchronous = normal;&lt;/span&gt;
        &lt;span class="c1"&gt;# PRAGMA temp_store = memory;&lt;/span&gt;
        &lt;span class="c1"&gt;# PRAGMA mmap_size = 30000000000;&lt;/span&gt;
        &lt;span class="c1"&gt;# PRAGMA busy_timeout = 5000;&lt;/span&gt;
        &lt;span class="c1"&gt;# PRAGMA optimize&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Database&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;history.sqlite&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPFlow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;use_stale&amp;quot;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;listdir&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPFlow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# coba singgah HTTP response yang didapat; ini menghasilkan&lt;/span&gt;
        &lt;span class="c1"&gt;# False jika flow: isinya kosong, dari host/path yang ada&lt;/span&gt;
        &lt;span class="c1"&gt;# di blocklist (situs iklan, path CAPTCHA, dst.) atau berisi&lt;/span&gt;
        &lt;span class="c1"&gt;# content-type yang ada di blocklist (contoh, saya tidak&lt;/span&gt;
        &lt;span class="c1"&gt;# ingin menyinggah konten video, berkas PDF, dst.)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

            &lt;span class="c1"&gt;# Agar pengalaman ber-Internet terasa cepat, commit&lt;/span&gt;
            &lt;span class="c1"&gt;# ke database hanya pada saat tertentu saja, misal&lt;/span&gt;
            &lt;span class="c1"&gt;# setelah ada 64 URL baru yang disinggah.&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Untuk mencoba &lt;em&gt;addons&lt;/em&gt; proksi itu, Anda perlu menjalankan&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mitmdump&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8888&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;cache.py&lt;span class="w"&gt; &lt;/span&gt;--quiet&lt;span class="w"&gt; &lt;/span&gt;--set&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;connection_strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;lazy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;dan mengatur peramban agar menggunakan proksi di alamat &lt;code&gt;localhost:8888&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Proksi ini sederhana, dan saya tidak ingin &lt;code&gt;over-engineering it&lt;/code&gt;. Jika Anda ingin meningkatkannya, Anda dapat mengembangkan: &lt;em&gt;dashboard&lt;/em&gt; agar pengguna bisa mengatur/menghapus singgahan yang tersimpan, maupun metode hemat-memori untuk menyimpan URL dengan kueri di path-nya. Setidaknya sekarang, saya bisa membaca kembali sejarah kunjungan di peramban saya tanpa perlu koneksi Internet.&lt;/p&gt;
</ns0:content>
    </ns0:entry>
</ns0:feed>
