Hatta biraz daha gelişmişi şöyle :
Python kodu (app.py) :
import os, csv, time, random, sqlite3, threading
from datetime import datetime, date
from flask import Flask, request, redirect, Response
import requests
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
TOKEN = os.getenv("WHATSAPP_TOKEN")
PHONE_NUMBER_ID = os.getenv("PHONE_NUMBER_ID")
DAILY_LIMIT = int(os.getenv("DAILY_LIMIT", 40))
MIN_DELAY = int(os.getenv("MIN_DELAY", 90))
MAX_DELAY = int(os.getenv("MAX_DELAY", 240))
DB = "whatsapp_crm.db"
def db():
conn = sqlite3.connect(DB)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = db()
conn.executescript("""
CREATE TABLE IF NOT EXISTS customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
phone TEXT UNIQUE,
opt_in INTEGER DEFAULT 1,
blocked INTEGER DEFAULT 0,
created_at TEXT
);
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
phone TEXT,
message TEXT,
status TEXT,
response TEXT,
created_at TEXT
);
""")
conn.commit()
conn.close()
def today_sent_count():
conn = db()
today = date.today().isoformat()
count = conn.execute(
"SELECT COUNT(*) FROM messages WHERE status='sent' AND DATE(created_at)=?",
(today,)
).fetchone()[0]
conn.close()
return count
def already_sent_recent(phone):
conn = db()
row = conn.execute(
"""
SELECT id FROM messages
WHERE phone=? AND status='sent'
AND datetime(created_at) >= datetime('now', '-7 days')
LIMIT 1
""",
(phone,)
).fetchone()
conn.close()
return row is not None
def send_whatsapp(phone, message):
url = f"https://graph.facebook.com/v20.0/{PHONE_NUMBER_ID}/messages"
payload = {
"messaging_product": "whatsapp",
"to": phone,
"type": "text",
"text": {
"preview_url": False,
"body": message
}
}
headers = {
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json"
}
r = requests.post(url, json=payload, headers=headers, timeout=30)
return r.status_code, r.text
def log_message(phone, message, status, response):
conn = db()
conn.execute(
"""
INSERT INTO messages(phone, message, status, response, created_at)
VALUES (?, ?, ?, ?, ?)
""",
(phone, message, status, response, datetime.now().isoformat())
)
conn.commit()
conn.close()
def campaign_worker(message_template):
conn = db()
customers = conn.execute(
"""
SELECT * FROM customers
WHERE opt_in=1 AND blocked=0
ORDER BY id ASC
"""
).fetchall()
conn.close()
for c in customers:
if today_sent_count() >= DAILY_LIMIT:
break
phone = c["phone"]
name = c["name"] or ""
if already_sent_recent(phone):
continue
message = message_template.replace("{name}", name).strip()
message += "\n\nMesaj almak istemiyorsanız DUR yazabilirsiniz."
code, resp = send_whatsapp(phone, message)
if 200 <= code < 300:
log_message(phone, message, "sent", resp)
else:
log_message(phone, message, "failed", resp)
time.sleep(random.randint(MIN_DELAY, MAX_DELAY))
@app.route("/")
def index():
conn = db()
customers = conn.execute("SELECT * FROM customers ORDER BY id DESC LIMIT 100").fetchall()
sent = today_sent_count()
conn.close()
rows = "".join(
f"<tr><td>{c['id']}</td><td>{c['name']}</td><td>{c['phone']}</td><td>{c['opt_in']}</td><td>{c['blocked']}</td></tr>"
for c in customers
)
return f"""
<h2>WhatsApp CRM Panel</h2>
<p>Bugün gönderilen: <b>{sent}/{DAILY_LIMIT}</b></p>
<h3>Tek müşteri ekle</h3>
<form method="post" action="/add">
<input name="name" placeholder="İsim">
<input name="phone" placeholder="905xxxxxxxxx" required>
<button>Ekle</button>
</form>
<h3>CSV Import</h3>
<p>CSV kolonları: name, phone, opt_in</p>
<form method="post" action="/import" enctype="multipart/form-data">
<input type="file" name="file" required>
<button>İçe Aktar</button>
</form>
<h3>Kampanya gönder</h3>
<form method="post" action="/campaign">
<textarea name="message" rows="6" cols="70" placeholder="Merhaba {{name}}, kampanya mesajınız..." required></textarea><br>
<button>Yavaş Gönderimi Başlat</button>
</form>
<p>
<a href="/export/customers">Müşterileri CSV indir</a> |
<a href="/export/messages">Mesaj loglarını CSV indir</a>
</p>
<h3>Son müşteriler</h3>
<table border="1" cellpadding="6">
<tr><th>ID</th><th>İsim</th><th>Telefon</th><th>Opt-in</th><th>Blocked</th></tr>
{rows}
</table>
"""
@app.route("/add", methods=["POST"])
def add():
name = request.form.get("name", "").strip()
phone = request.form.get("phone", "").strip()
conn = db()
conn.execute(
"""
INSERT OR IGNORE INTO customers(name, phone, opt_in, blocked, created_at)
VALUES (?, ?, 1, 0, ?)
""",
(name, phone, datetime.now().isoformat())
)
conn.commit()
conn.close()
return redirect("/")
@app.route("/import", methods=["POST"])
def import_csv():
file = request.files["file"]
content = file.stream.read().decode("utf-8-sig").splitlines()
reader = csv.DictReader(content)
conn = db()
for row in reader:
name = row.get("name", "").strip()
phone = row.get("phone", "").strip()
opt_in = 1 if row.get("opt_in", "yes").lower() in ["yes", "1", "true", "evet"] else 0
if phone:
conn.execute(
"""
INSERT OR IGNORE INTO customers(name, phone, opt_in, blocked, created_at)
VALUES (?, ?, ?, 0, ?)
""",
(name, phone, opt_in, datetime.now().isoformat())
)
conn.commit()
conn.close()
return redirect("/")
@app.route("/campaign", methods=["POST"])
def campaign():
message = request.form["message"]
t = threading.Thread(target=campaign_worker, args=(message,))
t.daemon = True
t.start()
return "Kampanya arka planda başladı. Panelden logları export edebilirsin. <a href='/'>Geri dön</a>"
@app.route("/export/customers")
def export_customers():
conn = db()
rows = conn.execute("SELECT name, phone, opt_in, blocked, created_at FROM customers").fetchall()
conn.close()
def generate():
yield "name,phone,opt_in,blocked,created_at\n"
for r in rows:
yield f"{r['name']},{r['phone']},{r['opt_in']},{r['blocked']},{r['created_at']}\n"
return Response(generate(), mimetype="text/csv",
headers={"Content-Disposition": "attachment; filename=customers.csv"})
@app.route("/export/messages")
def export_messages():
conn = db()
rows = conn.execute("SELECT phone, message, status, response, created_at FROM messages").fetchall()
conn.close()
def generate():
yield "phone,message,status,response,created_at\n"
for r in rows:
msg = str(r["message"]).replace('"', '""')
resp = str(r["response"]).replace('"', '""')
yield f'{r["phone"]},"{msg}",{r["status"]},"{resp}",{r["created_at"]}\n'
return Response(generate(), mimetype="text/csv",
headers={"Content-Disposition": "attachment; filename=messages.csv"})
@app.route("/webhook", methods=["GET", "POST"])
def webhook():
if request.method == "GET":
verify_token = os.getenv("VERIFY_TOKEN")
if request.args.get("hub.verify_token") == verify_token:
return request.args.get("hub.challenge")
return "Invalid verify token", 403
data = request.json or {}
try:
message = data["entry"][0]["changes"][0]["value"]["messages"][0]
phone = message["from"]
text = message.get("text", {}).get("body", "").strip().lower()
if text in ["dur", "stop", "iptal", "çık", "cik"]:
conn = db()
conn.execute("UPDATE customers SET blocked=1, opt_in=0 WHERE phone=?", (phone,))
conn.commit()
conn.close()
send_whatsapp(phone, "Talebiniz alınmıştır. Bu numaradan artık bilgilendirme mesajı almayacaksınız.")
except Exception:
pass
return "ok"
if __name__ == "__main__":
init_db()
app.run(host="0.0.0.0", port=5000, debug=True)
pip:
pip install flask requests python-dotenv
.env:
WHATSAPP_TOKEN=EAAG...
PHONE_NUMBER_ID=123456789
VERIFY_TOKEN=benim_verify_tokenim
DAILY_LIMIT=40
MIN_DELAY=90
MAX_DELAY=240
Çalıştır:
python app.py
Panel:
http://localhost:5000
bu bizim aktif kullandığımız.