Ilgili kategorinin olmamasindan dolayi konuyu buraya aciyorum. Belki daha sonra tasinabilir.
Bu botu normal bnc'den cok nickim o sunucuda online gozuksun diye kodlamistim. Botun islevi ayarladiginiz ana sunucu ve kanala baglanir, !ekle komutu ile eklediginiz sunucuya ayarladiginiz nicki sokar (eger ayarlarsaniz kayit eder nicki). DC yeme durumunda vs tekrar baglanir. !durum komutu ile botu takip edebilir ozeline gelen son 5 mesaji gorebilirsiniz. Dilerseniz kodlari inceleyip kendinize gore daha da gelistirebilirsiniz.
Kod:
import socket
import time
import threading
import logging
import json
import os
import re
import collections
# ─── ANA BOT AYARLARI (BncBot - irc.zevklisohbet.net) ────────────────────────
MASTER_SERVER = "irc.sohbetix.net"
MASTER_PORT = 6667
MASTER_NICK = "BncBot"
MASTER_PASSWORD = ""
MASTER_CHANNEL = "#bnc"
MASTER_IDENT = "bncbot"
MASTER_REALNAME = "BncBot Controller"
# ─── OPER BİLGİLERİ â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
OPER_NAME = "Eggdrop"
OPER_PASSWORD = ""
# ─── SLAVE BOT AYARLARI ───────────────────────────────────────────────────────
SLAVE_NICK = "ArsivDK"
SLAVE_PASSWORD = ""
SLAVE_CHANNEL = "#Sohbet"
SLAVE_IDENT = "Arsivci"
SLAVE_REALNAME = "www.Arsiv.dk"
SLAVE_PORT = 6667
SLAVE_EMAIL = ""
# ─── GENEL AYARLAR ────────────────────────────────────────────────────────────
RECONNECT_DELAY = 10
PING_TIMEOUT = 300
BOT_OWNER = ""
DB_FILE = "servers.json"
LOG_DIR = "logs" # Her sunucu için ayrı log klasörü
DAILY_REPORT_HOUR = 9 # Günlük özet saati (sunucu saati)
SLAVE_PING_INTERVAL = 60 # Slave'e PING gönderme aralığı (sn)
SLAVE_PING_TIMEOUT = 120 # Bu kadar sn içinde PONG gelmezse reconnect
# ──────────────────────────────────────────────────────────────────────────────
os.makedirs(LOG_DIR, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("IRCBot")
slave_bots: dict = {}
slave_lock = threading.Lock()
watch_keywords: list = [] # [(keyword, ekleyen_nick), ...]
watch_lock = threading.Lock()
master_ref = None # MasterBot instance (bildirim için)
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
# YARDIMCI FONKSİYONLAR
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
def is_nickserv(prefix: str) -> bool:
return prefix.lstrip(":").split("!")[0].lower() == "nickserv"
def notify_master(msg: str):
"""#bnc kanalına bildirim gönder."""
global master_ref
if master_ref:
try:
master_ref.send(f"PRIVMSG {MASTER_CHANNEL} :{msg}")
except Exception:
pass
def get_server_logger(server: str) -> logging.Logger:
"""Her sunucu için ayrı dosya logger'ı döndürür."""
name = server.replace(".", "_").replace(":", "_")
logger = logging.getLogger(f"srv.{name}")
if not logger.handlers:
fh = logging.FileHandler(
os.path.join(LOG_DIR, f"{name}.log"),
encoding="utf-8"
)
fh.setFormatter(logging.Formatter(
"%(asctime)s %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
))
logger.addHandler(fh)
logger.setLevel(logging.INFO)
logger.propagate = False
return logger
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
# KAYIT SİSTEMİ — servers.json
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
def db_load() -> list:
if not os.path.exists(DB_FILE):
return []
try:
with open(DB_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
log.info(f"[DB] {len(data)} sunucu yüklendi.")
return data
except Exception as e:
log.error(f"[DB] Okuma hatası: {e}")
return []
def db_save():
with slave_lock:
entries = [{"host": bot.server, "port": bot.port} for bot in slave_bots.values()]
try:
with open(DB_FILE, "w", encoding="utf-8") as f:
json.dump(entries, f, indent=2)
except Exception as e:
log.error(f"[DB] Yazma hatası: {e}")
def db_remove(host: str, port: int):
entries = db_load()
entries = [e for e in entries if not (e["host"] == host and e["port"] == port)]
try:
with open(DB_FILE, "w", encoding="utf-8") as f:
json.dump(entries, f, indent=2)
except Exception as e:
log.error(f"[DB] Silme hatası: {e}")
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
# TEMEL IRC BAĞLANTISI
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
class IRCConnection:
def __init__(self, server, port, nick, password, channel, ident, realname, label="Bot"):
self.server = server
self.port = port
self.nick = nick
self.password = password
self.channel = channel
self.ident = ident
self.realname = realname
self.label = label
self.sock = None
self.running = True
self._joined = False
self._buffer = ""
def connect(self):
log.info(f"[{self.label}] Bağlanılıyor: {self.server}:{self.port}")
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(PING_TIMEOUT)
self.sock.connect((self.server, self.port))
self._joined = False
self._buffer = ""
log.info(f"[{self.label}] TCP bağlantısı kuruldu.")
def disconnect(self):
try:
self.sock.close()
except Exception:
pass
self.sock = None
def send(self, msg: str):
raw = (msg + "\r\n").encode("utf-8", errors="replace")
log.debug(f"[{self.label}] >> {msg}")
self.sock.sendall(raw)
def register(self):
self.send(f"PASS {self.password}")
self.send(f"NICK {self.nick}")
self.send(f"USER {self.ident} 0 * :{self.realname}")
def read_lines(self):
chunk = self.sock.recv(4096).decode("utf-8", errors="replace")
if not chunk:
raise ConnectionResetError("Sunucu bağlantıyı kapattı.")
self._buffer += chunk
lines = []
while "\r\n" in self._buffer:
line, self._buffer = self._buffer.split("\r\n", 1)
if line:
lines.append(line)
return lines
def stop(self):
self.running = False
try:
self.send("QUIT :Bot kaldırıldı.")
except Exception:
pass
time.sleep(0.5)
self.disconnect()
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
# SLAVE BOT
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
class SlaveBot(IRCConnection):
def __init__(self, server, port=SLAVE_PORT):
label = f"Slave:{server}"
super().__init__(server, port, SLAVE_NICK, SLAVE_PASSWORD,
SLAVE_CHANNEL, SLAVE_IDENT, SLAVE_REALNAME, label)
# ── Bağlantı durumu ──────────────────────────────────────────────
self.status = "başlatılıyor"
self.connected_at = None
self.reconnects = 0
self.last_error = ""
self.last_activity = time.time()
# ── Kanal takibi ─────────────────────────────────────────────────
self.channels: set = set()
# ── PM takibi (son 20) ────────────────────────────────────────────
self.pm_log: list = []
self.pm_lock = threading.Lock()
# ── Nick / NickServ durumu ────────────────────────────────────────
self.nick_status = "unknown"
self._nick_registered = False
self._register_retry_at = None
self._register_retry_sec = 0
# ── Restart sinyali ──────────────────────────────────────────────
self._restart_flag = False
# ── İstatistikler ─────────────────────────────────────────────────
self.msg_count = 0 # bu oturumda görülen kanal mesajı
self.pm_count_total = 0 # toplam PM sayısı
self.connect_history = collections.deque(maxlen=5) # son 5 bağlanma/kopma
# ── PING/PONG takibi ─────────────────────────────────────────────
self._last_pong = time.time()
self._ping_sent_at = None
# ── Bağlantı kopma bildirimi ──────────────────────────────────────
self._notified_disconnect = False
# ── Günlük rapor ──────────────────────────────────────────────────
self._last_daily_report = None # datetime.date
# ── Sunucu logger ─────────────────────────────────────────────────
self.srv_log = get_server_logger(server)
# ── Yardımcılar ───────────────────────────────────────────────────────
def _set_status(self, s: str, err: str = ""):
self.status = s
self.last_error = err
def _touch(self):
self.last_activity = time.time()
def _fmt_duration(self, seconds: int) -> str:
if seconds < 60:
return f"{seconds}sn"
dk, sn = divmod(seconds, 60)
if dk < 60:
return f"{dk}dk {sn}sn"
sa, dk = divmod(dk, 60)
return f"{sa}sa {dk}dk {sn}sn"
def _idle_str(self) -> str:
return self._fmt_duration(int(time.time() - self.last_activity))
def _uptime_str(self) -> str:
if not self.connected_at:
return "-"
return self._fmt_duration(int(time.time() - self.connected_at))
# ── Sunucu logu ───────────────────────────────────────────────────────
def _slog(self, msg: str):
self.srv_log.info(msg)
# ── GHOST — nick alınmışsa ─────────────────────────────────────────────
def _try_ghost(self):
"""Asıl nick başkası tarafından alınmışsa GHOST ile geri al."""
log.info(f"[{self.label}] GHOST deneniyor: {SLAVE_NICK}")
self.send(f"PRIVMSG NickServ :GHOST {SLAVE_NICK} {self.password}")
time.sleep(2)
self.send(f"NICK {SLAVE_NICK}")
# ── NickServ mesajını işle ────────────────────────────────────────────
def _handle_nickserv(self, body: str):
# ── Identify başarılı ─────────────────────────────────────────────
if any(k in body for k in (
"sifre kabul edildi", "zaten identify", "login oldunuz",
"you are now identified", "password accepted",
"identified successfully", "now recognized",
"you are already identified", "you are already logged in",
)):
self.nick_status = "identified"
log.info(f"[{self.label}] NickServ: identify başarılı.")
return
# ── Nick kayıtlı değil → register et ─────────────────────────────
if any(k in body for k in (
"kayitli degil", "kayitli de", "kayit altina almak",
"is not registered", "isn't registered",
"no such nick", "not registered",
)):
self.nick_status = "not_registered"
if not self._nick_registered:
self._nick_registered = True
log.info(f"[{self.label}] NickServ: kayıtlı değil, register deneniyor.")
self.send(f"PRIVMSG NickServ :REGISTER {self.password} {SLAVE_EMAIL}")
return
# ── Register bekleme süresi ───────────────────────────────────────
m = re.search(r"(\d+)\s*saniye\s*bekledikten", body)
if m and self.nick_status == "not_registered":
sn = int(m.group(1)) + 2
self._register_retry_at = time.time() + sn
self._register_retry_sec = sn
self._nick_registered = False
log.info(f"[{self.label}] NickServ: register için {sn}sn bekleniyor.")
def _delayed_register(bot=self, delay=sn):
time.sleep(delay)
if bot.running and bot.nick_status == "not_registered" and not bot._nick_registered:
bot._nick_registered = True
log.info(f"[{bot.label}] NickServ: bekleme bitti, register yeniden deneniyor.")
try:
bot.send(f"PRIVMSG NickServ :REGISTER {bot.password} {SLAVE_EMAIL}")
except Exception:
pass
threading.Thread(target=_delayed_register, daemon=True).start()
return
# ── Yanlış şifre ─────────────────────────────────────────────────
if any(k in body for k in (
"password incorrect", "invalid password",
"authentication failed", "password does not match",
"yanlis sifre", "hatali sifre",
"yanlış şifre", "hatalı şifre",
)):
self.nick_status = "wrong_password"
log.warning(f"[{self.label}] NickServ: yanlış şifre!")
return
# ── Register onayı ────────────────────────────────────────────────
if any(k in body for k in (
"has been registered", "registration successful",
"your nick has been registered",
"kayit edildi", "tescil edildi",
)):
self.nick_status = "registered"
self._register_retry_at = None
self._register_retry_sec = 0
log.info(f"[{self.label}] NickServ: nick register edildi.")
return
# ── Genel identify isteği ─────────────────────────────────────────
if any(k in body for k in (
"identify sifreniz", "msg nickserv identify", "/msg nickserv",
)):
if self.nick_status not in ("identified", "registered"):
self.send(f"PRIVMSG NickServ :IDENTIFY {self.password}")
# ── Keyword izleme ────────────────────────────────────────────────────
def _check_watch(self, channel: str, sender: str, message: str):
with watch_lock:
kws = list(watch_keywords)
msg_lower = message.lower()
for kw, ekleyen in kws:
if kw.lower() in msg_lower:
notify_master(
f"[İZLEME] [{self.server}] [{channel}] "
f"{sender}: {message} (keyword: {kw})"
)
self._slog(f"[WATCH:{kw}] {channel} <{sender}> {message}")
# ── Satır işleme ──────────────────────────────────────────────────────
def handle_line(self, line: str):
self._touch()
parts = line.split()
cmd = parts[1] if len(parts) > 1 else ""
# ── PING ──────────────────────────────────────────────────────────
if line.startswith("PING"):
token = line.split(":", 1)[1] if ":" in line else parts[1]
self.send(f"PONG :{token}")
return
# ── PONG — kendi gönderdiğimiz PING'e cevap ───────────────────────
if cmd == "PONG" or line.startswith("PONG"):
self._last_pong = time.time()
self._ping_sent_at = None
return
# ── 001 Kayıt tamamlandı ──────────────────────────────────────────
if cmd == "001":
log.info(f"[{self.label}] Kayıt olundu.")
self._slog(f"[CONNECT] {self.server}:{self.port}")
if self.connected_at is None:
self.connected_at = time.time()
self.connect_history.appendleft(
("bağlandı", time.strftime("%H:%M:%S"))
)
self._set_status("bağlı")
self._nick_registered = False
self.nick_status = "unknown"
self._register_retry_at = None
self._register_retry_sec = 0
self._notified_disconnect = False
self.channels.clear()
self.msg_count = 0
self.send(f"PRIVMSG NickServ :IDENTIFY {self.password}")
time.sleep(1)
self.send(f"JOIN {self.channel}")
return
# ── JOIN ──────────────────────────────────────────────────────────
if cmd == "JOIN":
joiner = parts[0].split("!")[0].lstrip(":")
joined_ch = (parts[2].lstrip(":") if len(parts) > 2 else "").strip()
if joiner.lower() == self.nick.lower():
self.channels.add(joined_ch)
log.info(f"[{self.label}] Kanala katıldım: {joined_ch}")
return
# ── PART ──────────────────────────────────────────────────────────
if cmd == "PART":
leaver = parts[0].split("!")[0].lstrip(":")
ch = parts[2].lstrip(":") if len(parts) > 2 else ""
if leaver.lower() == self.nick.lower():
self.channels.discard(ch)
return
# ── KICK ──────────────────────────────────────────────────────────
if cmd == "KICK":
channel = parts[2] if len(parts) > 2 else ""
kicked = parts[3] if len(parts) > 3 else ""
if kicked.lower() == self.nick.lower():
self.channels.discard(channel)
notify_master(f"âš* [{self.server}] Kanaldan atıldım: {channel}")
self._slog(f"[KICKED] {channel}")
if channel.lower() == self.channel.lower():
log.warning(f"[{self.label}] Ana kanaldan atıldım! 5 sn sonra yeniden katılıyorum.")
time.sleep(5)
self.send(f"JOIN {self.channel}")
return
# ── 433 Nick kullanımda ───────────────────────────────────────────
if cmd == "433":
log.warning(f"[{self.label}] Nick kullanımda, GHOST deneniyor.")
self._try_ghost()
return
# ── NICK değişikliği takibi ────────────────────────────────────────
if cmd == "NICK":
changer = parts[0].split("!")[0].lstrip(":")
new_nick = parts[2].lstrip(":") if len(parts) > 2 else ""
# Birisi SLAVE_NICK'i bıraktıysa al
if new_nick.lower() == SLAVE_NICK.lower() and changer.lower() != self.nick.lower():
log.info(f"[{self.label}] {SLAVE_NICK} serbest kaldı, alınıyor.")
self.send(f"NICK {SLAVE_NICK}")
self.nick = SLAVE_NICK
return
# ── PRIVMSG ───────────────────────────────────────────────────────
if cmd == "PRIVMSG":
if len(parts) < 4:
return
prefix = parts[0]
sender = prefix.split("!")[0].lstrip(":")
target = parts[2]
message = " ".join(parts[3:]).lstrip(":")
if target.lower() == self.nick.lower():
# NickServ PRIVMSG
if is_nickserv(prefix):
self._handle_nickserv(message.lower())
return
# Gerçek PM
with self.pm_lock:
self.pm_log.append((time.time(), sender, message))
if len(self.pm_log) > 20:
self.pm_log = self.pm_log[-20:]
self.pm_count_total += 1
log.info(f"[{self.label}] PM alındı — {sender}: {message}")
self._slog(f"[PM] <{sender}> {message}")
notify_master(f"�� [{self.server}] PM — {sender}: {message}")
else:
# Kanal mesajı
self.msg_count += 1
self._slog(f"[{target}] <{sender}> {message}")
self._check_watch(target, sender, message)
return
# ── NOTICE ────────────────────────────────────────────────────────
if cmd == "NOTICE":
if len(parts) < 4:
return
prefix = parts[0]
body = " ".join(parts[3:]).lstrip(":").lower()
if is_nickserv(prefix):
self._handle_nickserv(body)
return
# ── Aktif PING gönder ─────────────────────────────────────────────────
def _ping_loop(self):
"""Arka planda PING gönder, PONG gelmezse reconnect yap."""
while self.running:
time.sleep(SLAVE_PING_INTERVAL)
if not self.running:
break
if self.status != "bağlı":
continue
# PONG bekliyorsak ve süre aştıysa
if self._ping_sent_at:
if time.time() - self._ping_sent_at > SLAVE_PING_TIMEOUT:
log.warning(f"[{self.label}] PONG alınamadı, yeniden bağlanılıyor.")
self._set_status("yeniden bağlanıyor", "pong timeout")
self.disconnect()
return
else:
try:
self._ping_sent_at = time.time()
self.send(f"PING :{self.server}")
except Exception:
pass
# ── Günlük rapor ─────────────────────────────────────────────────────
def _check_daily_report(self):
import datetime
now = datetime.datetime.now()
today = now.date()
if now.hour == DAILY_REPORT_HOUR:
if self._last_daily_report != today:
self._last_daily_report = today
with self.pm_lock:
pm_c = len(self.pm_log)
notify_master(
f"�� Günlük Rapor [{self.server}] — "
f"Uptime: {self._uptime_str()} | "
f"Mesaj: {self.msg_count} | "
f"PM: {self.pm_count_total} | "
f"Kanallar: {len(self.channels)} | "
f"Reconnect: {self.reconnects}x | "
f"NickServ: {self.nick_status}"
)
self.msg_count = 0 # Günlük sayacı sıfırla
# ── Detaylı durum raporu ──────────────────────────────────────────────
def durum_detay(self) -> list:
lines = []
lines.append(f"┌─── {self.server}:{self.port} ───────────────────────")
lines.append(
f"│ Durum : {self.status}"
f" │ Uptime: {self._uptime_str()}"
f" │ Idle: {self._idle_str()}"
)
if self.reconnects or self.last_error:
hata = f" │ Son hata: {self.last_error}" if self.last_error and self.status != "bağlı" else ""
lines.append(f"│ Reconnect: {self.reconnects}x{hata}")
# Bağlantı geçmişi
if self.connect_history:
gecmis = " → ".join(f"{ev}@{zaman}" for ev, zaman in self.connect_history)
lines.append(f"│ Geçmiş : {gecmis}")
# Nick / NickServ
retry_str = ""
if self._register_retry_at and self.nick_status == "not_registered":
kalan = int(self._register_retry_at - time.time())
if kalan > 0:
retry_str = f" (retry: {kalan}sn kaldı)"
nick_map = {
"identified" : "✔ Identify edildi",
"registered" : "✔ Register edildi (bu oturumda)",
"not_registered": "✘ Kayıtlı değil → register deneniyor",
"wrong_password": "✘ Yanlış şifre",
"unknown" : "? Yanıt alınamadı",
}
lines.append(
f"│ Nick : {self.nick}"
f" │ NickServ: {nick_map.get(self.nick_status, self.nick_status)}{retry_str}"
)
# İstatistikler
lines.append(
f"│ Mesaj : {self.msg_count} (bugün)"
f" │ PM toplam: {self.pm_count_total}"
)
# Kanallar
if self.channels:
lines.append(f"│ Kanallar: {', '.join(sorted(self.channels))}")
else:
lines.append("│ Kanallar: (henüz hiçbir kanalda değil)")
# PM'ler
with self.pm_lock:
pm_copy = list(self.pm_log)
if pm_copy:
lines.append(f"│ PM ({len(pm_copy)} adet — son 5):")
for ts, nick, msg in pm_copy[-5:]:
zaman = time.strftime("%H:%M:%S", time.localtime(ts))
lines.append(f"│ [{zaman}] {nick}: {msg}")
else:
lines.append("│ PM: yok")
lines.append("└────────────────────────────────────────────────")
return lines
# ── Restart ───────────────────────────────────────────────────────────
def restart(self):
log.info(f"[{self.label}] Restart istendi.")
self._restart_flag = True
try:
self.send("QUIT :Yeniden bağlanılıyor...")
except Exception:
pass
time.sleep(0.3)
self.disconnect()
# ── Ana döngü ─────────────────────────────────────────────────────────
def run(self):
key = f"{self.server}:{self.port}"
log.info(f"[{self.label}] Slave bot başlatıldı.")
# PING loop thread
threading.Thread(target=self._ping_loop, name=f"ping-{key}", daemon=True).start()
while self.running:
self._restart_flag = False
try:
self._set_status("bağlanıyor")
self.connect()
self.register()
while self.running:
try:
for line in self.read_lines():
self.handle_line(line)
self._check_daily_report()
if self._restart_flag:
break
except socket.timeout:
log.warning(f"[{self.label}] Ping zaman aşımı.")
self._set_status("yeniden bağlanıyor", "ping timeout")
break
except (ConnectionResetError, OSError) as e:
log.error(f"[{self.label}] Bağlantı hatası: {e}")
self._set_status("yeniden bağlanıyor", str(e))
break
except Exception as e:
log.error(f"[{self.label}] Beklenmeyen hata: {e}")
self._set_status("yeniden bağlanıyor", str(e))
finally:
self.disconnect()
self.channels.clear()
if self.running and not self._notified_disconnect:
self._notified_disconnect = True
self.connect_history.appendleft(
("koptu", time.strftime("%H:%M:%S"))
)
notify_master(
f"âš* [{self.server}:{self.port}] Bağlantı koptu! "
f"{RECONNECT_DELAY}sn sonra yeniden bağlanılacak."
)
self._slog(f"[DISCONNECT] yeniden bağlanılıyor...")
if self.running:
self.reconnects += 1
log.info(f"[{self.label}] {RECONNECT_DELAY} sn sonra yeniden bağlanılacak...")
time.sleep(RECONNECT_DELAY)
self._set_status("durduruldu")
with slave_lock:
slave_bots.pop(key, None)
log.info(f"[{self.label}] Slave bot durduruldu.")
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
# ANA BOT (BncBot)
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
class MasterBot(IRCConnection):
def __init__(self):
super().__init__(
MASTER_SERVER, MASTER_PORT,
MASTER_NICK, MASTER_PASSWORD,
MASTER_CHANNEL, MASTER_IDENT, MASTER_REALNAME,
label="Master:BncBot"
)
self._oper_done = False
def do_oper(self):
if not self._oper_done:
log.info(f"[Master] OPER girişi: {OPER_NAME}")
self.send(f"OPER {OPER_NAME} {OPER_PASSWORD}")
def _start_slave(self, host: str, port: int):
bot = SlaveBot(host, port)
key = f"{host}:{port}"
with slave_lock:
slave_bots[key] = bot
threading.Thread(target=bot.run, name=f"slave-{key}", daemon=True).start()
return bot
def handle_line(self, line: str):
parts = line.split()
cmd = parts[1] if len(parts) > 1 else ""
if line.startswith("PING"):
token = line.split(":", 1)[1] if ":" in line else parts[1]
self.send(f"PONG :{token}")
return
if cmd == "001":
log.info("[Master] Kayıt olundu.")
self.send(f"PRIVMSG NickServ :IDENTIFY {self.password}")
time.sleep(1)
self.do_oper()
time.sleep(1)
self.send(f"JOIN {self.channel}")
self._restore_servers()
return
if cmd == "JOIN":
joiner = parts[0].split("!")[0].lstrip(":")
joined_ch = parts[2].lstrip(":") if len(parts) > 2 else ""
if joiner.lower() == self.nick.lower():
if joined_ch.lower() == self.channel.lower():
if not self._joined:
self._joined = True
log.info(f"[Master] '{self.channel}' kanalına katıldım.")
else:
self.send(f"PART {joined_ch}")
return
if cmd == "381":
self._oper_done = True
log.info("[Master] OPER girişi başarılı! (Eggdrop)")
return
if cmd == "491":
log.warning("[Master] OPER başarısız! (491)")
return
if cmd == "433":
self.send(f"NICK {self.nick}_")
return
if cmd == "KICK":
channel = parts[2] if len(parts) > 2 else ""
kicked = parts[3] if len(parts) > 3 else ""
if kicked.lower() == self.nick.lower() and channel.lower() == self.channel.lower():
time.sleep(5)
self.send(f"JOIN {self.channel}")
return
if cmd == "NOTICE":
body = " ".join(parts[3:]).lower()
sender = parts[0].split("!")[0].lstrip(":").lower()
if sender == "nickserv" and "identify" in body:
self.send(f"PRIVMSG NickServ :IDENTIFY {self.password}")
return
if cmd == "PRIVMSG":
if len(parts) < 4:
return
sender = parts[0].split("!")[0].lstrip(":")
target = parts[2]
message = " ".join(parts[3:]).lstrip(":")
reply = target if target.startswith("#") else sender
msg_lower = message.strip().lower()
if msg_lower.startswith("!ekle "):
self._cmd_ekle(sender, reply, message.strip()[6:].strip())
elif msg_lower.startswith("!sil "):
self._cmd_sil(sender, reply, message.strip()[5:].strip())
elif msg_lower.startswith("!restart "):
self._cmd_restart(sender, reply, message.strip()[9:].strip())
elif msg_lower == "!restartall":
self._cmd_restartall(sender, reply)
elif msg_lower.startswith("!izle "):
self._cmd_izle(sender, reply, message.strip()[6:].strip())
elif msg_lower.startswith("!izlesil "):
self._cmd_izlesil(sender, reply, message.strip()[9:].strip())
elif msg_lower == "!izlelist":
self._cmd_izlelist(reply)
elif msg_lower.startswith("!pmtemizle "):
self._cmd_pmtemizle(sender, reply, message.strip()[11:].strip())
elif msg_lower.startswith("!log "):
self._cmd_log(sender, reply, message.strip()[5:].strip())
elif msg_lower == "!liste":
self._cmd_liste(reply)
elif msg_lower == "!durum":
self._cmd_durum(reply)
elif msg_lower == "!yardim":
self._cmd_yardim(reply)
# ── Komutlar ──────────────────────────────────────────────────────────
def _parse_host_port(self, arg: str):
p = arg.split(":")
host = p[0].strip()
try:
port = int(p[1]) if len(p) > 1 else SLAVE_PORT
except ValueError:
port = SLAVE_PORT
return host, port
def _yetkili(self, requester, target) -> bool:
if BOT_OWNER and requester.lower() != BOT_OWNER.lower():
self.send(f"PRIVMSG {target} :Yetkisiz işlem.")
return False
return True
def _cmd_ekle(self, requester, target, arg):
if not self._yetkili(requester, target): return
host, port = self._parse_host_port(arg)
if not host:
self.send(f"PRIVMSG {target} :Kullanım: !ekle irc.sunucu.com[:port]")
return
key = f"{host}:{port}"
with slave_lock:
if key in slave_bots:
self.send(f"PRIVMSG {target} :Zaten bağlı: {key}")
return
self._start_slave(host, port)
db_save()
self.send(f"PRIVMSG {target} :Bağlanılıyor → {key}")
def _cmd_sil(self, requester, target, arg):
if not self._yetkili(requester, target): return
host, port = self._parse_host_port(arg)
key = f"{host}:{port}"
with slave_lock:
bot = slave_bots.get(key)
if bot:
bot.stop()
db_remove(host, port)
self.send(f"PRIVMSG {target} :{key} → durduruldu ve silindi.")
else:
with slave_lock:
aktif = list(slave_bots.keys())
self.send(f"PRIVMSG {target} :Bulunamadı: {key}")
if aktif:
self.send(f"PRIVMSG {target} :Aktif: {', '.join(aktif)}")
def _cmd_restart(self, requester, target, arg):
if not self._yetkili(requester, target): return
host, port = self._parse_host_port(arg)
key = f"{host}:{port}"
with slave_lock:
bot = slave_bots.get(key)
if bot:
bot.restart()
self.send(f"PRIVMSG {target} :{key} → yeniden bağlanılıyor...")
else:
with slave_lock:
aktif = list(slave_bots.keys())
self.send(f"PRIVMSG {target} :Bulunamadı: {key}")
if aktif:
self.send(f"PRIVMSG {target} :Aktif: {', '.join(aktif)}")
def _cmd_restartall(self, requester, target):
"""Tüm slave'leri sırayla yeniden bağlat."""
if not self._yetkili(requester, target): return
with slave_lock:
bots = list(slave_bots.values())
if not bots:
self.send(f"PRIVMSG {target} :Aktif slave bot yok.")
return
self.send(f"PRIVMSG {target} :{len(bots)} slave yeniden başlatılıyor...")
def _do_restartall():
for bot in bots:
bot.restart()
time.sleep(3)
threading.Thread(target=_do_restartall, daemon=True).start()
def _cmd_izle(self, requester, target, keyword):
"""!izle <keyword> — Kanalda bu kelime geçince bildirim al."""
if not self._yetkili(requester, target): return
if not keyword:
self.send(f"PRIVMSG {target} :Kullanım: !izle <keyword>")
return
with watch_lock:
mevcut = [k for k, _ in watch_keywords]
if keyword.lower() in [k.lower() for k in mevcut]:
self.send(f"PRIVMSG {target} :Zaten izleniyor: {keyword}")
return
watch_keywords.append((keyword, requester))
self.send(f"PRIVMSG {target} :İzlemeye alındı: {keyword}")
def _cmd_izlesil(self, requester, target, keyword):
"""!izlesil <keyword> — İzleme listesinden çıkar."""
if not self._yetkili(requester, target): return
with watch_lock:
before = len(watch_keywords)
watch_keywords[:] = [(k, n) for k, n in watch_keywords
if k.lower() != keyword.lower()]
after = len(watch_keywords)
if before != after:
self.send(f"PRIVMSG {target} :İzlemeden çıkarıldı: {keyword}")
else:
self.send(f"PRIVMSG {target} :Bulunamadı: {keyword}")
def _cmd_izlelist(self, target):
"""!izlelist — Aktif keyword listesi."""
with watch_lock:
kws = list(watch_keywords)
if kws:
liste = ", ".join(f"{k} ({n})" for k, n in kws)
self.send(f"PRIVMSG {target} :İzlenen ({len(kws)}): {liste}")
else:
self.send(f"PRIVMSG {target} :İzleme listesi boş.")
def _cmd_pmtemizle(self, requester, target, arg):
"""!pmtemizle irc.sunucu.com — O sunucunun PM logunu sıfırla."""
if not self._yetkili(requester, target): return
host, port = self._parse_host_port(arg)
key = f"{host}:{port}"
with slave_lock:
bot = slave_bots.get(key)
if bot:
with bot.pm_lock:
bot.pm_log.clear()
self.send(f"PRIVMSG {target} :{key} PM logu temizlendi.")
else:
self.send(f"PRIVMSG {target} :Bulunamadı: {key}")
def _cmd_log(self, requester, target, arg):
"""!log irc.sunucu.com — Son 10 log satırını kanala yaz."""
if not self._yetkili(requester, target): return
host, port = self._parse_host_port(arg)
name = host.replace(".", "_")
log_file = os.path.join(LOG_DIR, f"{name}.log")
if not os.path.exists(log_file):
self.send(f"PRIVMSG {target} :Log dosyası bulunamadı: {host}")
return
try:
with open(log_file, "r", encoding="utf-8", errors="replace") as f:
satirlar = f.readlines()
son10 = satirlar[-10:]
self.send(f"PRIVMSG {target} :── Son 10 log [{host}] ──")
for s in son10:
self.send(f"PRIVMSG {target} :{s.rstrip()}")
time.sleep(0.05)
except Exception as e:
self.send(f"PRIVMSG {target} :Log okunamadı: {e}")
def _cmd_liste(self, target):
with slave_lock:
keys = list(slave_bots.keys())
if keys:
self.send(f"PRIVMSG {target} :Aktif ({len(keys)}): {', '.join(keys)}")
else:
self.send(f"PRIVMSG {target} :Aktif slave bot yok.")
def _cmd_durum(self, target):
with slave_lock:
bots = list(slave_bots.values())
if not bots:
self.send(f"PRIVMSG {target} :Aktif slave bot yok.")
return
for bot in bots:
for satir in bot.durum_detay():
self.send(f"PRIVMSG {target} :{satir}")
time.sleep(0.05)
def _cmd_yardim(self, target):
komutlar = [
"!ekle irc.sunucu.com[:port] — Sunucuya slave bağla",
"!sil irc.sunucu.com[:port] — Slave'i durdur ve sil",
"!restart irc.sunucu.com[:port] — Slave'i yeniden bağlat",
"!restartall — Tüm slave'leri yeniden bağlat",
"!liste — Aktif slave listesi",
"!durum — Her slave'in detaylı durumu",
"!izle <keyword> — Kanalda kelime geçince bildirim",
"!izlesil <keyword> — İzlemeyi kaldır",
"!izlelist — İzlenen keyword listesi",
"!pmtemizle irc.sunucu.com — PM logunu sıfırla",
"!log irc.sunucu.com — Son 10 log satırı",
]
for c in komutlar:
self.send(f"PRIVMSG {target} :{c}")
time.sleep(0.05)
def _restore_servers(self):
entries = db_load()
if not entries:
return
log.info(f"[Master] {len(entries)} kayıtlı sunucu yükleniyor...")
for e in entries:
host = e.get("host", "")
port = e.get("port", SLAVE_PORT)
key = f"{host}:{port}"
with slave_lock:
if key in slave_bots:
continue
self._start_slave(host, port)
log.info(f"[Master] Otomatik başlatıldı: {key}")
time.sleep(1)
def run(self):
global master_ref
master_ref = self
log.info("[Master] Ana bot başlatılıyor...")
while self.running:
self._oper_done = False
self._joined = False
try:
self.connect()
self.register()
while self.running:
try:
for line in self.read_lines():
self.handle_line(line)
except socket.timeout:
log.warning("[Master] Ping zaman aşımı.")
break
except (ConnectionResetError, OSError) as e:
log.error(f"[Master] Bağlantı hatası: {e}")
break
except Exception as e:
log.error(f"[Master] Beklenmeyen hata: {e}")
finally:
self.disconnect()
if self.running:
log.info(f"[Master] {RECONNECT_DELAY} sn sonra yeniden bağlanılacak...")
time.sleep(RECONNECT_DELAY)
# â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
if __name__ == "__main__":
master = MasterBot()
try:
master.run()
except KeyboardInterrupt:
log.info("Kapatılıyor...")
master.stop()
with slave_lock:
for bot in list(slave_bots.values()):
bot.stop()
log.info("Tüm botlar durduruldu.")