pip install customtkinter openpyxl pandas python-dotenv netgsm-sms
import customtkinter as ctk
from tkinter import filedialog, messagebox, scrolledtext
import pandas as pd
import threading
import time
import re
from datetime import datetime
import os
from dotenv import load_dotenv, set_key
from netgsm import Netgsm
from netgsm.exceptions.api_exception import ApiException, NotAcceptableException
from netgsm.utils.enums import Encoding
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
ENV_FILE = ".env"
class NetgsmSMSBot:
def __init__(self):
self.window = ctk.CTk()
self.window.title("Netgsm Toplu SMS Botu")
self.window.geometry("950x750")
self.excel_file = None
self.contacts = []
self.is_sending = False
self.netgsm_client = None
load_dotenv(ENV_FILE)
self.setup_ui()
def setup_ui(self):
self.tabview = ctk.CTkTabview(self.window, width=900, height=700)
self.tabview.pack(padx=20, pady=20, fill="both", expand=True)
self.tabview.add("📨 SMS Gönder")
self.tabview.add("⚙️ Netgsm Ayarları")
self.setup_send_tab()
self.setup_settings_tab()
def setup_send_tab(self):
send_tab = self.tabview.tab("📨 SMS Gönder")
excel_frame = ctk.CTkFrame(send_tab)
excel_frame.pack(fill="x", padx=20, pady=(20,10))
self.excel_label = ctk.CTkLabel(excel_frame, text="📁 Excel dosyası seçilmedi", font=("Roboto",12))
self.excel_label.pack(side="left", padx=(0,10))
select_btn = ctk.CTkButton(excel_frame, text="Excel Seç", command=self.select_excel, width=120)
select_btn.pack(side="right")
msg_frame = ctk.CTkFrame(send_tab)
msg_frame.pack(fill="both", expand=True, padx=20, pady=10)
ctk.CTkLabel(msg_frame, text="Mesaj Şablonu:", font=("Roboto",14, "bold")).pack(anchor="w")
self.message_text = ctk.CTkTextbox(msg_frame, height=150)
self.message_text.pack(fill="both", expand=True, pady=5)
self.message_text.insert("1.0", "Merhaba ${isim},\nNetgsm ile toplu SMS gönderimi testidir.\nTeşekkürler.")
tip_label = ctk.CTkLabel(msg_frame, text="💡 İpucu: ${isim} ve ${soyisim} değişkenlerini kullanabilirsiniz",
font=("Roboto",10), text_color="gray")
tip_label.pack(anchor="w")
prog_frame = ctk.CTkFrame(send_tab)
prog_frame.pack(fill="x", padx=20, pady=10)
self.progress_bar = ctk.CTkProgressBar(prog_frame)
self.progress_bar.pack(fill="x", pady=5)
self.progress_bar.set(0)
self.progress_label = ctk.CTkLabel(prog_frame, text="Hazır", font=("Roboto",12))
self.progress_label.pack()
log_frame = ctk.CTkFrame(send_tab)
log_frame.pack(fill="both", expand=True, padx=20, pady=10)
ctk.CTkLabel(log_frame, text="İşlem Günlüğü:", font=("Roboto",14, "bold")).pack(anchor="w")
self.log_text = scrolledtext.ScrolledText(log_frame, height=12, bg="#2b2b2b", fg="white",
font=("Consolas",10))
self.log_text.pack(fill="both", expand=True, pady=5)
self.send_btn = ctk.CTkButton(send_tab, text="📨 SMS Göndermeyi Başlat",
command=self.start_sending, height=45,
font=("Roboto",14,"bold"))
self.send_btn.pack(pady=(10,20), padx=20, fill="x")
def setup_settings_tab(self):
settings_tab = self.tabview.tab("⚙️ Netgsm Ayarları")
info_frame = ctk.CTkFrame(settings_tab)
info_frame.pack(fill="x", padx=20, pady=20)
ctk.CTkLabel(info_frame, text="Netgsm Hesap Bilgileri", font=("Roboto",16,"bold")).pack(pady=5)
ctk.CTkLabel(info_frame, text="Bu bilgiler .env dosyasında saklanacaktır.",
font=("Roboto",10), text_color="gray").pack()
user_frame = ctk.CTkFrame(settings_tab)
user_frame.pack(fill="x", padx=20, pady=10)
ctk.CTkLabel(user_frame, text="Kullanıcı Adı:", width=120).pack(side="left", padx=5)
self.username_entry = ctk.CTkEntry(user_frame, width=300)
self.username_entry.pack(side="left", padx=5)
self.username_entry.insert(0, os.getenv("NETGSM_USERNAME", ""))
pass_frame = ctk.CTkFrame(settings_tab)
pass_frame.pack(fill="x", padx=20, pady=10)
ctk.CTkLabel(pass_frame, text="Şifre:", width=120).pack(side="left", padx=5)
self.password_entry = ctk.CTkEntry(pass_frame, width=300, show="*")
self.password_entry.pack(side="left", padx=5)
self.password_entry.insert(0, os.getenv("NETGSM_PASSWORD", ""))
header_frame = ctk.CTkFrame(settings_tab)
header_frame.pack(fill="x", padx=20, pady=10)
ctk.CTkLabel(header_frame, text="Gönderici Başlığı:", width=120).pack(side="left", padx=5)
self.header_entry = ctk.CTkEntry(header_frame, width=300)
self.header_entry.pack(side="left", padx=5)
self.header_entry.insert(0, os.getenv("NETGSM_MSGHEADER", ""))
test_frame = ctk.CTkFrame(settings_tab)
test_frame.pack(fill="x", padx=20, pady=10)
ctk.CTkLabel(test_frame, text="Test Numarası:", width=120).pack(side="left", padx=5)
self.testno_entry = ctk.CTkEntry(test_frame, width=300)
self.testno_entry.pack(side="left", padx=5)
self.testno_entry.insert(0, os.getenv("NETGSM_TEST_NUMBER", ""))
ctk.CTkLabel(test_frame, text="(10 haneli, 0'sız)", font=("Roboto",9), text_color="gray").pack(side="left", padx=5)
btn_frame = ctk.CTkFrame(settings_tab)
btn_frame.pack(fill="x", padx=20, pady=20)
save_btn = ctk.CTkButton(btn_frame, text="💾 Bilgileri Kaydet", command=self.save_settings, width=150)
save_btn.pack(side="left", padx=10)
test_btn = ctk.CTkButton(btn_frame, text="🔍 Test SMS Gönder", command=self.test_sms, width=150, fg_color="green")
test_btn.pack(side="left", padx=10)
self.settings_status = ctk.CTkLabel(settings_tab, text="", font=("Roboto",11))
self.settings_status.pack(pady=10)
def select_excel(self):
filename = filedialog.askopenfilename(filetypes=[("Excel files","*.xlsx *.xls")])
if filename:
self.excel_file = filename
self.excel_label.configure(text=f"📄 {os.path.basename(filename)}")
self.log(f"Excel dosyası seçildi: {filename}")
try:
self.load_contacts()
except Exception as e:
self.log(f"❌ Excel okuma hatası: {str(e)}")
def load_contacts(self):
df = pd.read_excel(self.excel_file, engine='openpyxl')
self.contacts = []
for _, row in df.iterrows():
phone = str(row.get('telefon', row.get('phone', ''))).strip()
phone = re.sub(r'\D', '', phone)
if phone.startswith('0') and len(phone) == 11:
phone = phone[1:]
if len(phone) == 10:
phone = phone
else:
self.log(f"⚠️ Geçersiz numara formatı: {phone}")
continue
self.contacts.append({
'phone': phone,
'first_name': str(row.get('isim', row.get('first_name', ''))).strip(),
'last_name': str(row.get('soyisim', row.get('last_name', ''))).strip()
})
self.log(f"✅ {len(self.contacts)} kişi yüklendi.")
def save_settings(self):
username = self.username_entry.get().strip()
password = self.password_entry.get().strip()
header = self.header_entry.get().strip()
testno = self.testno_entry.get().strip()
if not username or not password or not header:
messagebox.showerror("Hata", "Kullanıcı adı, şifre ve gönderici başlığı zorunludur.")
return
set_key(ENV_FILE, "NETGSM_USERNAME", username)
set_key(ENV_FILE, "NETGSM_PASSWORD", password)
set_key(ENV_FILE, "NETGSM_MSGHEADER", header)
if testno:
set_key(ENV_FILE, "NETGSM_TEST_NUMBER", testno)
load_dotenv(ENV_FILE, override=True)
self.settings_status.configure(text="✅ Bilgiler başarıyla kaydedildi.", text_color="green")
self.log("Netgsm ayarları kaydedildi.")
def test_sms(self):
self.save_settings()
username = os.getenv("NETGSM_USERNAME")
password = os.getenv("NETGSM_PASSWORD")
header = os.getenv("NETGSM_MSGHEADER")
testno = os.getenv("NETGSM_TEST_NUMBER")
if not testno:
messagebox.showerror("Hata", "Test numarası girilmemiş.")
return
testno = re.sub(r'\D', '', testno)
if testno.startswith('0'):
testno = testno[1:]
if len(testno) != 10:
messagebox.showerror("Hata", "Test numarası 10 haneli olmalı (başında 0 olmadan).")
return
try:
client = Netgsm(username=username, password=password)
message = {
"msg": "Bu bir test mesajıdır. Netgsm SDK çalışıyor.",
"no": testno
}
response = client.sms.send(msgheader=header, messages=[message], encoding=Encoding.TR)
self.log(f"✅ Test SMS gönderildi! JobID: {response.get('jobid')}")
messagebox.showinfo("Başarılı", f"Test SMS gönderildi.\nJobID: {response.get('jobid')}")
except NotAcceptableException as e:
self.log(f"❌ Netgsm hatası: {e.message} (Kod: {e.code})")
messagebox.showerror("Netgsm Hatası", f"{e.message}\nKod: {e.code}")
except ApiException as e:
self.log(f"❌ API hatası: {e.message}")
messagebox.showerror("API Hatası", e.message)
def send_sms_via_sdk(self, phone, message):
username = os.getenv("NETGSM_USERNAME")
password = os.getenv("NETGSM_PASSWORD")
header = os.getenv("NETGSM_MSGHEADER")
if not all([username, password, header]):
return False, "API bilgileri eksik. Ayarlar sekmesini kontrol edin."
try:
client = Netgsm(username=username, password=password)
msg_obj = {
"msg": message,
"no": phone
}
response = client.sms.send(msgheader=header, messages=[msg_obj], encoding=Encoding.TR)
if response.get('jobid'):
return True, f"JobID: {response.get('jobid')}"
else:
return False, "Bilinmeyen yanıt"
except NotAcceptableException as e:
return False, f"{e.message} (Kod: {e.code})"
except ApiException as e:
return False, e.message
except Exception as e:
return False, str(e)
def send_bulk(self, message_template):
total = len(self.contacts)
success = 0
fail = 0
for i, contact in enumerate(self.contacts):
if not self.is_sending:
break
msg = message_template
msg = msg.replace('${isim}', contact['first_name'])
msg = msg.replace('${soyisim}', contact['last_name'])
ok, info = self.send_sms_via_sdk(contact['phone'], msg)
if ok:
success += 1
self.log(f"✅ Gönderildi: {contact['phone']} - {contact['first_name']} ({info})")
else:
fail += 1
self.log(f"❌ Başarısız: {contact['phone']} - {info}")
progress = (i+1)/total
self.progress_bar.set(progress)
self.progress_label.configure(text=f"{i+1}/{total} - Başarılı: {success}, Başarısız: {fail}")
time.sleep(0.5)
self.log(f"\n{'='*50}\n📊 RAPOR\nToplam: {total}\nBaşarılı: {success}\nBaşarısız: {fail}\n{'='*50}")
messagebox.showinfo("Tamamlandı", f"Gönderim bitti.\nBaşarılı: {success}\nBaşarısız: {fail}")
def start_sending(self):
if self.is_sending:
messagebox.showwarning("Uyarı", "Zaten gönderim devam ediyor.")
return
if not self.excel_file:
messagebox.showwarning("Uyarı", "Önce bir Excel dosyası seçin.")
return
if not self.contacts:
self.load_contacts()
if not self.contacts:
messagebox.showerror("Hata", "Excel'de geçerli numara bulunamadı.")
return
msg_template = self.message_text.get("1.0", "end-1c").strip()
if not msg_template:
messagebox.showwarning("Uyarı", "Mesaj şablonu boş olamaz.")
return
if not all([os.getenv("NETGSM_USERNAME"), os.getenv("NETGSM_PASSWORD"), os.getenv("NETGSM_MSGHEADER")]):
messagebox.showwarning("Uyarı", "Netgsm API bilgileri eksik. Lütfen ayarlar sekmesinden girin.")
self.tabview.set("⚙️ Netgsm Ayarları")
return
self.is_sending = True
self.send_btn.configure(state="disabled", text="📨 Gönderiliyor...")
self.progress_bar.set(0)
thread = threading.Thread(target=self.send_bulk, args=(msg_template,))
thread.daemon = True
thread.start()
def clear_log(self):
self.log_text.delete("1.0", "end")
self.log("Log temizlendi.")
def log(self, msg):
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.insert("end", f"[{timestamp}] {msg}\n")
self.log_text.see("end")
self.window.update_idletasks()
def run(self):
self.window.mainloop()
if __name__ == "__main__":
app = NetgsmSMSBot()
app.run()