# ╔══════════════════════════════════════════════════════════════════╗ # ║ KISALT — KabaLang ile bağlantı kısaltıcı ║ # ║ ║ # ║ · Sayfalar sunucuda KabaLang ile üretilir (şablon motoru yok) ║ # ║ · /k/{kod} → gerçek HTTP 302 yönlendirmesi + tıklama kaydı ║ # ║ · İstatistik sayfasındaki grafik: sunucuda üretilen SVG ║ # ╚══════════════════════════════════════════════════════════════════╝ # # Çalıştır: kabalang calistir web/kisalt/kisalt.kbl # Aç: http://127.0.0.1:8472 kullan kaba.web kullan kaba.json kullan kaba.veritabani kullan kaba.sistem # Karıştırılabilecek harfler (0/o, 1/l/i) bilerek yok. ALFABE = "abcdefghjkmnpqrstuvwxyz23456789" KOD_UZUNLUGU = 6 PORT = 8472 # Barındıran ortam (ör. Online IDE) portu PORT ortam değişkeniyle verir. port_ortam = cevre.al("PORT") eger port_ortam != "": PORT = sayi(port_ortam) db = cevre.al("KISALT_DB") eger db == "": db = "kisalt.db" # ── Stil: tüm sayfalar aynı görünümü paylaşır ─────────────────────── STIL = """ :root { --zemin:#0a0d15; --yuzey:#121826; --yuzey2:#182033; --cizgi:#232c42; --metin:#e6ebf5; --soluk:#8a94ab; --mavi:#377ef4; --cam:#22d3ee; --yesil:#34d399; } * { box-sizing: border-box; } body { margin:0; min-height:100vh; color:var(--metin); font:15px/1.55 Inter, system-ui, -apple-system, "Segoe UI", sans-serif; background: radial-gradient(900px 480px at 50% -12%, #377ef433, transparent 65%), radial-gradient(600px 360px at 95% 10%, #22d3ee18, transparent 60%), var(--zemin); } a { color: var(--cam); text-decoration: none; } a:hover { text-decoration: underline; } .kap { max-width: 980px; margin: 0 auto; padding: 0 24px 56px; } nav { display:flex; align-items:center; gap:12px; padding: 22px 0; } .logo { width:36px; height:36px; border-radius:11px; display:grid; place-items:center; font-weight:800; background:linear-gradient(135deg,var(--mavi),var(--cam)); box-shadow:0 6px 22px #377ef455; } nav b { font-size: 18px; letter-spacing: -.01em; } nav span { margin-left:auto; color:var(--soluk); font-size:13px; } .kahraman { text-align:center; padding: 46px 0 30px; } .kahraman h1 { font-size: clamp(30px, 5vw, 48px); line-height:1.1; margin:0 0 12px; letter-spacing:-.03em; } .kahraman h1 em { font-style: normal; background: linear-gradient(90deg, var(--mavi), var(--cam)); -webkit-background-clip: text; background-clip: text; color: transparent; } .kahraman p { color: var(--soluk); margin: 0 0 28px; } form { display:flex; gap:10px; max-width: 720px; margin: 0 auto; background: var(--yuzey); border:1px solid var(--cizgi); border-radius: 16px; padding: 8px; box-shadow: 0 20px 60px #0007; } input { flex:1; min-width:0; font:inherit; color:var(--metin); background:transparent; border:0; padding: 12px 14px; outline:none; } button { font:inherit; font-weight:600; color:#fff; border:0; border-radius: 11px; padding: 12px 22px; cursor:pointer; background: linear-gradient(135deg, var(--mavi), #2563eb); box-shadow: 0 6px 18px #377ef450; } #sonuc { max-width:720px; margin: 14px auto 0; min-height: 24px; font-size: 14px; color: var(--soluk); } #sonuc .kisa { color: var(--yesil); font-weight: 600; font-family: ui-monospace, monospace; font-size: 15px; } .kutular { display:grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin: 34px 0 18px; } .kutu { background: var(--yuzey); border:1px solid var(--cizgi); border-radius: 16px; padding: 16px 18px; } .kutu small { color: var(--soluk); font-size: 12px; text-transform: uppercase; letter-spacing: .07em; } .kutu div { font-size: 26px; font-weight: 700; letter-spacing: -.02em; } table { width:100%; border-collapse: collapse; background: var(--yuzey); border:1px solid var(--cizgi); border-radius: 16px; overflow:hidden; } th, td { text-align:left; padding: 12px 16px; border-bottom: 1px solid var(--cizgi); font-size: 14px; } th { color: var(--soluk); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .07em; background: var(--yuzey2); } tr:last-child td { border-bottom: 0; } td.hedef { color: var(--soluk); max-width: 360px; overflow:hidden; text-overflow: ellipsis; white-space: nowrap; } td.kod { font-family: ui-monospace, monospace; } .sayac { font-weight: 700; color: var(--metin); } .grafik { background: var(--yuzey); border:1px solid var(--cizgi); border-radius: 16px; padding: 18px; margin-top: 14px; } .grafik svg { width: 100%; height: auto; display:block; } .adres { word-break: break-all; color: var(--soluk); } footer { text-align:center; color: var(--soluk); font-size: 12px; margin-top: 30px; } @media (max-width: 680px) { .kap { padding: 0 16px 40px; } form { flex-direction: column; } .kutular { grid-template-columns: 1fr 1fr; } .kutular .kutu:last-child { grid-column: 1 / -1; } td.hedef, th.hedef { display: none; } } """ fonksiyon sayfa(baslik, govde): don "" + web.kacir(baslik) + "
" + govde + "
" # ── Veritabanı ────────────────────────────────────────────────────── fonksiyon hazirla(db): veritabani.sqlite(db, "CREATE TABLE IF NOT EXISTS baglanti(kod TEXT PRIMARY KEY, adres TEXT NOT NULL, olusturma TEXT, tiklama INTEGER DEFAULT 0); CREATE TABLE IF NOT EXISTS tiklama(kod TEXT, zaman TEXT);") # Satırları `|` ile ayrılmış sütunlara böler. Adres son sütundur ve # kendi içinde `|` taşıyabilir → kalan parçalar geri birleştirilir. fonksiyon satirlar(ham): sonuc = [] eger ham == "": don sonuc dongu s icinde ham.bol("\n"): sonuc.ekle(s) don sonuc fonksiyon son_sutun(parcalar, bas): don parcalar.dilim(bas, parcalar.uzunluk() - bas).birlestir("|") # ── Kısa kod ──────────────────────────────────────────────────────── fonksiyon yeni_kod(): kod = "" dongu _ icinde 1..KOD_UZUNLUGU: kod = kod + ALFABE.dilim(rastgele.sayi(0, ALFABE.uzunluk() - 1), 1) don kod fonksiyon kod_gecerli(k): eger k.uzunluk() != KOD_UZUNLUGU: don yanlis dongu i icinde 0..k.uzunluk() - 1: eger degil ALFABE.icerir(k.dilim(i, 1)): don yanlis don dogru # Hedef adres yalnız http(s) olabilir: `javascript:` ya da `file:` ile # başlayan bir "bağlantı" kısaltıcıyı saldırı aracına çevirirdi. fonksiyon adres_dogrula(a): eger a.uzunluk() > 2000: don "adres çok uzun" eger degil (a.ile_baslar("https://") veya a.ile_baslar("http://")): don "adres http:// ya da https:// ile başlamalı" eger a.icerir(" ") veya a.icerir("\n") veya a.icerir("\r") veya a.icerir("\t"): don "adres boşluk içeremez" eger a.uzunluk() < 12: don "adres eksik görünüyor" don "" fonksiyon kaydet(db, adres): kod = yeni_kod() # Çakışma olasılığı çok düşük ama sıfır değil: boş kod bulana dek dene. iken veritabani.sqlite(db, "SELECT count(*) FROM baglanti WHERE kod = ?", [kod]) != "0": kod = yeni_kod() veritabani.sqlite(db, "INSERT INTO baglanti(kod, adres, olusturma) VALUES (?, ?, ?)", [kod, adres, zaman.simdi()]) don kod # Tanıtım verisi: ilk açılışta birkaç bağlantı ve geçmiş tıklamalar. fonksiyon ornek_veri(db): eger veritabani.sqlite(db, "SELECT count(*) FROM baglanti") != "0": don 0 adresler = ["https://kabaforce.com", "https://dev.kabaforce.com/kabalang/", "https://forge.kabaforce.com/KabaLang/ornekler", "https://maps.kabaforce.com/?q=Galata+Kulesi", "https://dev.kabaforce.com/kabalang/kilavuz/12-web-uygulamasi"] agirlik = [7, 11, 5, 3, 2] dongu i icinde 0..adresler.uzunluk() - 1: kod = kaydet(db, adresler[i]) veritabani.sqlite(db, "UPDATE baglanti SET olusturma = datetime('now', 'localtime', ?) WHERE kod = ?", ["-" + metin(7 + i) + " days", kod]) # Son yedi güne yayılmış tıklamalar. dongu gun icinde 0..6: adet = rastgele.sayi(0, agirlik[i]) dongu _ icinde 1..adet: veritabani.sqlite(db, "INSERT INTO tiklama(kod, zaman) VALUES (?, datetime('now', 'localtime', ?))", [kod, "-" + metin(gun) + " days"]) veritabani.sqlite(db, "UPDATE baglanti SET tiklama = (SELECT count(*) FROM tiklama WHERE tiklama.kod = baglanti.kod) WHERE kod = ?", [kod]) don 1 hazirla(db) ornek_veri(db) # ── Sayfalar ──────────────────────────────────────────────────────── fonksiyon ana_sayfa(_istek): toplam = veritabani.sqlite(db, "SELECT count(*) FROM baglanti") tik = veritabani.sqlite(db, "SELECT coalesce(sum(tiklama), 0) FROM baglanti") bugun = veritabani.sqlite(db, "SELECT count(*) FROM tiklama WHERE date(zaman) = date('now', 'localtime')") g = "

Uzun bağlantılar,
kısa ve ölçülebilir.

" g = g + "

Bir adres yapıştırın; KabaLang ona kısa bir kod versin ve her tıklamayı saysın.

" g = g + "
" g = g + "
" g = g + "
" g = g + "
Bağlantı
" + toplam + "
" g = g + "
Toplam tıklama
" + tik + "
" g = g + "
Bugün
" + bugun + "
" g = g + "
" g = g + "" dongu s icinde satirlar(veritabani.sqlite(db, "SELECT kod, tiklama, adres FROM baglanti ORDER BY olusturma DESC LIMIT 12")): p = s.bol("|") kod = p[0] # Kod yalnız ALFABE harflerinden oluşur → href içinde güvenli. # Hedef adres kullanıcıdan geldi → yalnız METİN olarak, kaçırılarak basılır. g = g + "" g = g + "" g = g + "" g = g + "" g = g + "
Kısa kodHedefTıklama
/k/" + kod + "" + web.kacir(son_sutun(p, 2)) + "" + p[1] + "istatistik →
" # Küçük bir istemci betiği: formu JSON API'ye gönderir. g = g + "" don web.html(sayfa("Kısalt · KabaLang", g)) fonksiyon kisalt_api(istek): govde = istek.govde() eger degil json.gecerli_mi(govde): don web.json(json.uret({"hata": "gövde geçerli JSON değil"}), 400) adres = json.al(govde, "adres").kirp() sorun = adres_dogrula(adres) eger sorun != "": don web.json(json.uret({"hata": sorun}), 422) kod = kaydet(db, adres) don web.json(json.uret({"kod": kod, "kisa": "/k/" + kod, "adres": adres}), 201) fonksiyon bulunamadi_sayfasi(): g = "

Bu kod bulunamadı

Bağlantı silinmiş ya da hiç var olmamış olabilir.

← ana sayfa

" don web.html(sayfa("Bulunamadı · Kısalt", g), 404) fonksiyon yonlendir(istek): kod = istek.parametre("kod") eger degil kod_gecerli(kod): don bulunamadi_sayfasi() adres = veritabani.sqlite(db, "SELECT adres FROM baglanti WHERE kod = ?", [kod]) eger adres == "": don bulunamadi_sayfasi() veritabani.sqlite(db, "INSERT INTO tiklama(kod, zaman) VALUES (?, datetime('now', 'localtime'))", [kod]) veritabani.sqlite(db, "UPDATE baglanti SET tiklama = tiklama + 1 WHERE kod = ?", [kod]) # Gerçek HTTP yönlendirmesi: 302 + Location başlığı. don web.yanit("", 302).baslik("Location", adres) # Son 7 günün tıklamalarını SVG çubuk grafiğe çevirir — tamamen sunucuda. fonksiyon yedi_gun_svg(db, kod): gunler = [] adetler = [] en_cok = 1 dongu i icinde 0..6: geri = 6 - i gun = veritabani.sqlite(db, "SELECT strftime('%d.%m', 'now', 'localtime', ?)", ["-" + metin(geri) + " days"]) adet = sayi(veritabani.sqlite(db, "SELECT count(*) FROM tiklama WHERE kod = ? AND date(zaman) = date('now', 'localtime', ?)", [kod, "-" + metin(geri) + " days"])) gunler.ekle(gun) adetler.ekle(adet) eger adet > en_cok: en_cok = adet gen = 700 yuk = 220 cubuk = 64 aralik = 36 svg = "" svg = svg + "" dongu i icinde 0..6: h = adetler[i] * (yuk - 30) // en_cok eger h < 3: h = 3 x = 30 + i * (cubuk + aralik) y = yuk - h svg = svg + "" svg = svg + "" + metin(adetler[i]) + "" svg = svg + "" + gunler[i] + "" don svg + "" fonksiyon istatistik(istek): kod = istek.parametre("kod") eger degil kod_gecerli(kod): don bulunamadi_sayfasi() satir = veritabani.sqlite(db, "SELECT tiklama, olusturma, adres FROM baglanti WHERE kod = ?", [kod]) eger satir == "": don bulunamadi_sayfasi() p = satir.bol("|") son = veritabani.sqlite(db, "SELECT coalesce(max(zaman), '—') FROM tiklama WHERE kod = ?", [kod]) g = "

/k/" + kod + "

" g = g + "

→ " + web.kacir(son_sutun(p, 2)) + "

" g = g + "
" g = g + "
Toplam tıklama
" + p[0] + "
" g = g + "
Oluşturuldu
" + p[1] + "
" g = g + "
Son tıklama
" + son + "
" g = g + "
" g = g + "
Son 7 gün" + yedi_gun_svg(db, kod) + "
" g = g + "

← tüm bağlantılar

" don web.html(sayfa("/k/" + kod + " · istatistik", g)) # Tek iş parçacığı: tıklama sayacı yarış koşuluna girmesin. sunucu = web.sunucu(PORT, "127.0.0.1", 1) sunucu.govde_siniri(8192) sunucu.al("/", ana_sayfa) sunucu.gonder("/api/kisalt", kisalt_api) sunucu.al("/k/{kod}", yonlendir) sunucu.al("/istatistik/{kod}", istatistik) sunucu.bulunamadi((_istek) => bulunamadi_sayfasi()) yazdir("🔗 Kısalt hazır — http://127.0.0.1:" + metin(PORT)) sunucu.basla()