Okay taking inspiration from east252 release for blackmarket system. Here I made a code with the help of AI since a lot of people here cant DBSS updater like me.
1. Note that to claim the reward, you need to login into the game 1st to create a login record inside a new table called TblUserAttendance. Here is the snippet of the Tbl structure.
2. This system is not suitable for online system since most of the extension bypass user auth.
1. Note that to claim the reward, you need to login into the game 1st to create a login record inside a new table called TblUserAttendance. Here is the snippet of the Tbl structure.
SQL:
USE [SA_BETA_WORLDDB_0002]
GO
/****** Object: Table [PaGamePrivate].[TblUserAttendance] Script Date: 16/9/2025 7:33:52 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [PaGamePrivate].[TblUserAttendance](
[_userNo] [int] NOT NULL,
[_lastAttendanceDate] [datetime] NOT NULL,
[_attendanceCount] [int] NOT NULL,
PRIMARY KEY CLUSTERED
(
[_userNo] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [PaGamePrivate].[TblUserAttendance] ADD DEFAULT ((0)) FOR [_attendanceCount]
GO
2. This system is not suitable for online system since most of the extension bypass user auth.
Python:
'''
Attendance Reward System with Cooldown Timer (Original sendMail)
Daily login reward system for Black Desert.
Author: Assistant
Features:
- 30-day reward cycle
- Items-only rewards
- Claim button only for today
- Cooldown timer: "Next claim in: Xh Ym"
- Uses ORIGINAL sendMail with @symNo OUTPUT — proven to send mail
- Treats NULL/empty @symNo as SUCCESS (common in private servers)
'''
import os
from datetime import datetime, timedelta, date
import pyodbc
from flask import (
Flask, request, redirect, url_for, session,
render_template_string, flash
)
app = Flask(__name__)
app.secret_key = os.environ.get("FLASK_SECRET", "dev-secret-attendance-v4")
app.permanent_session_lifetime = timedelta(hours=2)
# --- DB config ---
SQL_SERVER = os.environ.get("SQL_SERVER", "YOURSERVER\\NAME") #please do in this format
USE_SQL_AUTH = os.environ.get("USE_SQL_AUTH", "0") == "1"
SQL_USER = os.environ.get("SQL_USER", "")
SQL_PASS = os.environ.get("SQL_PASS", "")
WORLD_DB = "SA_BETA_WORLDDB_0002"
TRADE_DB = "SA_BETA_TRADEDB_0002"
# --- Reward Configuration (ITEMS ONLY) ---
REWARDS = {
1: [(16080, 5, 0)], # Cron Stone x5
2: [(17809, 1, 0)], # Advice of Valks 10
3: [(16080, 10, 0)], # Cron Stone x10
4: [(17819, 1, 0)], # Advice of Valks 20
5: [(757008, 1, 0)], # Mythical Censer
6: [(17829, 1, 0)], # Advice of Valks 30
7: [(47968, 1, 0)], # Celestial Horse Horn
8: [(17839, 1, 0)], # Advice of Valks 40
9: [(16080, 15, 0)], # Cron Stone x15
10: [(17849, 1, 0)], # Advice of Valks 50
11: [(17859, 1, 0)], # Advice of Valks 60
12: [(16080, 20, 0)], # Cron Stone x20
13: [(17869, 1, 0)], # Advice of Valks 70
14: [(17879, 1, 0)], # Advice of Valks 80
15: [(16080, 25, 0)], # Cron Stone x25
16: [(17889, 1, 0)], # Advice of Valks 90
17: [(17899, 1, 0)], # Advice of Valks 100
18: [(16080, 30, 0)], # Cron Stone x30
19: [(757008, 2, 0)], # Mythical Censer x2
20: [(47968, 2, 0)], # Celestial Horse Horn x2
21: [(16080, 40, 0)], # Cron Stone x40
22: [(17899, 2, 0)], # Advice of Valks 100 x2
23: [(16080, 50, 0)], # Cron Stone x50
24: [(757008, 3, 0)], # Mythical Censer x3
25: [(47968, 3, 0)], # Celestial Horse Horn x3
26: [(16080, 60, 0)], # Cron Stone x60
27: [(17899, 3, 0)], # Advice of Valks 100 x3
28: [(16080, 75, 0)], # Cron Stone x75
29: [(757008, 5, 0)], # Mythical Censer x5
30: [(47968, 5, 0)], # Celestial Horse Horn x5
}
# Preload item names for UI
ITEM_NAMES = {
16080: "Cron Stone",
17809: "Advice of Valks 10",
17819: "Advice of Valks 20",
17829: "Advice of Valks 30",
17839: "Advice of Valks 40",
17849: "Advice of Valks 50",
17859: "Advice of Valks 60",
17869: "Advice of Valks 70",
17879: "Advice of Valks 80",
17889: "Advice of Valks 90",
17899: "Advice of Valks 100",
757008: "Mythical Censer",
47968: "Celestial Horse Horn",
}
def get_conn(database_name: str):
"""Create an ODBC connection to the specified database."""
driver = "{ODBC Driver 17 for SQL Server}"
if USE_SQL_AUTH:
cs = (
f"DRIVER={driver};SERVER={SQL_SERVER};DATABASE={database_name};"
f"UID={SQL_USER};PWD={SQL_PASS};TrustServerCertificate=yes;"
)
else:
cs = (
f"DRIVER={driver};SERVER={SQL_SERVER};DATABASE={database_name};"
f"Trusted_Connection=yes;TrustServerCertificate=yes;"
)
return pyodbc.connect(cs)
def send_mail(nickname: str, item_id: int, qty: int, enchant: int = 0) -> str:
"""Send item via mail using sendMail stored procedure. Returns symNo or 'UNKNOWN' if not returned."""
try:
with get_conn(WORLD_DB) as conn, conn.cursor() as cur:
cur.execute("""
DECLARE @symNo NVARCHAR(50);
EXEC SA_BETA_WORLDDB_0002.dbo.sendMail
@toFamilyName = ?,
@itemKey = ?,
@itemCount = ?,
@enchant = ?,
@title = N'Daily Attendance Reward',
@contents = N'Thank you for logging in daily!',
@symNo = @symNo OUTPUT;
SELECT @symNo AS symNo;
""", (nickname, item_id, qty, enchant))
row = cur.fetchone()
# If row exists, return symNo (even if NULL or empty — common in private servers)
if row:
return row[0] if row[0] else "UNKNOWN"
else:
return "UNKNOWN"
except Exception as e:
print(f"[Mail Error] {e}")
return ""
def record_attendance(user_no: int) -> tuple[int, bool]:
"""
Record user attendance for today.
Returns: (current_streak_count, is_first_claim_today)
"""
today = date.today()
try:
with get_conn(WORLD_DB) as conn:
conn.autocommit = False
cur = conn.cursor()
cur.execute("""
SELECT _lastAttendanceDate, _attendanceCount
FROM PaGamePrivate.TblUserAttendance WITH (UPDLOCK, ROWLOCK)
WHERE _userNo = ?
""", (user_no,))
row = cur.fetchone()
if row:
last_date = row[0].date() if row[0] else None
count = int(row[1])
if last_date == today:
conn.rollback()
return count, False # Already claimed
if last_date == today - timedelta(days=1):
new_count = count + 1
else:
new_count = 1
cur.execute("""
UPDATE PaGamePrivate.TblUserAttendance
SET _lastAttendanceDate = ?, _attendanceCount = ?
WHERE _userNo = ?
""", (today, new_count, user_no))
else:
new_count = 1
cur.execute("""
INSERT INTO PaGamePrivate.TblUserAttendance (_userNo, _lastAttendanceDate, _attendanceCount)
VALUES (?, ?, ?)
""", (user_no, today, new_count))
conn.commit()
return new_count, True
except Exception:
return 0, False
# ---------- HTML Templates ----------
LOGIN_HTML = """<!doctype html>
<html><head>
<meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Attendance Reward – Sign in</title>
<style>
body{margin:0;font-family:system-ui,Segoe UI,Arial;background:#0b0d11;color:#e6e6e6;display:grid;place-items:center;height:100vh}
.card{background:#141922;border:1px solid #232a36;border-radius:16px;box-shadow:0 10px 30px rgba(0,0,0,.35);padding:28px;min-width:300px;max-width:360px}
h1{margin:0 0 6px 0;font-size:22px}
p.sub{margin:0 0 16px 0;color:#aab3c2;font-size:13px}
label{display:block;margin:12px 0 6px 0;font-size:13px;color:#aab3c2}
input{width:100%;padding:10px 12px;border:1px solid #2a3140;background:#0f131a;color:#e6e6e6;border-radius:10px}
button{margin-top:16px;width:100%;padding:10px 12px;border:0;border-radius:10px;background:#3b82f6;color:white;font-weight:600;cursor:pointer}
.err{margin-top:10px;color:#ff7070;font-size:13px}
.msg{margin-top:10px;color:#87d17e;font-size:13px}
</style>
</head>
<body>
<form class="card" method="post" action="/">
<h1>Daily Attendance</h1>
<p class="sub">Sign in to claim your daily reward</p>
<label>Username</label>
<input name="username" autocomplete="username" required />
<label>Password</label>
<input name="password" type="password" autocomplete="current-password" required />
<button type="submit">Sign in</button>
{% if error %}<div class="err">{{ error }}</div>{% endif %}
{% for m in get_flashed_messages() %}<div class="msg">{{ m }}</div>{% endfor %}
</form>
</body></html>"""
HOME_HTML = """<!doctype html>
<html><head>
<meta charset="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Attendance Reward – Home</title>
<style>
body{background:#0b0d11;color:#e6e6e6;font-family:system-ui,Segoe UI,Arial;margin:0}
header{display:flex;justify-content:space-between;align-items:center;padding:14px 18px;background:#141922;border-bottom:1px solid #232a36}
main{padding:24px}
.meta{color:#aab3c2;font-size:14px;margin:6px 0 18px}
.streak{font-size:20px;margin:16px 0;color:#ffd700}
.cooldown{font-size:16px;margin:8px 0;color:#ffcc00;font-weight:bold}
button{padding:8px 16px;border:0;border-radius:8px;background:#3b82f6;color:#fff;cursor:pointer;font-weight:600}
button:disabled{background:#4a5568;cursor:not-allowed}
.flash-ok{color:#7be18a;margin:6px 0;font-weight:bold}
.flash-err{color:#ff7070;margin:6px 0;font-weight:bold}
.reward-table{width:100%;border-collapse:collapse;margin-top:20px}
.reward-table th, .reward-table td{padding:10px;border-bottom:1px solid #232a36;text-align:left}
.reward-table th{color:#aab3c2;background:#1a202c}
.today-row{background:#1e293b;animation: pulse 2s infinite;}
.claim-btn{margin-top:8px}
@keyframes pulse {
0%{background:#1e293b}
50%{background:#2d3748}
100%{background:#1e293b}
}
</style>
</head>
<body>
<header>
<div><strong>Daily Attendance</strong></div>
<form method="post" action="/logout"><button type="submit">Logout</button></form>
</header>
<main>
<h2>Welcome, {{ username }}</h2>
<div class="streak">🔥 Current Streak: Day {{ streak }} (Reward Day: {{ reward_day }})</div>
{% if cooldown %}
<div class="cooldown">⏳ Next claim in: {{ cooldown }}</div>
{% endif %}
{% for m in get_flashed_messages(category_filter=["ok"]) %}<div class="flash-ok">{{ m }}</div>{% endfor %}
{% for m in get_flashed_messages(category_filter=["err"]) %}<div class="flash-err">{{ m }}</div>{% endfor %}
<h3>Reward Schedule</h3>
<table class="reward-table">
<thead><tr><th>Day</th><th>Items</th><th>Action</th></tr></thead>
<tbody>
{% for day in range(1, 31) %}
<tr {% if day == reward_day %}class="today-row"{% endif %}>
<td>{{ day }}</td>
<td>
{% if day in rewards %}
{% for item_id, qty, enchant in rewards[day] %}
{{ item_names[item_id] }} x{{ qty }}
{% if not loop.last %}, {% endif %}
{% endfor %}
{% else %}
—
{% endif %}
</td>
<td>
{% if day == reward_day and can_claim %}
<form method="post" action="/claim" class="claim-btn">
<button type="submit">🎁 Claim Now</button>
</form>
{% elif day == reward_day and not can_claim %}
<button disabled>✅ Claimed Today</button>
{% else %}
<button disabled>🔒 Future Reward</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
</body></html>"""
# -------------------- Routes --------------------
@app.route("/", methods=["GET", "POST"])
def login():
error = None
if request.method == "POST":
username = request.form.get("username", "").strip()
password = request.form.get("password", "")
if not username or not password:
error = "Enter both fields."
else:
user_id = f"{username},{password}"
try:
with get_conn(WORLD_DB) as conn, conn.cursor() as cur:
cur.execute("""
SELECT TOP 1 _userNo, _userNickname
FROM PaGamePrivate.TblUserInformation WITH (NOLOCK)
WHERE _userId = ?
""", (user_id,))
row = cur.fetchone()
if row:
session.permanent = True
session["username"] = username
session["_userNo"] = int(row[0])
session["_nickname"] = row[1] or username
return redirect(url_for("home"))
else:
error = "Invalid credentials."
except Exception as ex:
error = f"DB error: {ex}"
return render_template_string(LOGIN_HTML, error=error)
@app.route("/home")
def home():
if "username" not in session:
return redirect(url_for("login"))
user_no = session["_userNo"]
streak = 0
can_claim = False
last_claim_date = None
try:
with get_conn(WORLD_DB) as conn, conn.cursor() as cur:
cur.execute("""
SELECT _lastAttendanceDate, _attendanceCount
FROM PaGamePrivate.TblUserAttendance WITH (NOLOCK)
WHERE _userNo = ?
""", (user_no,))
row = cur.fetchone()
if row:
last_claim_date = row[0].date() if row[0] else None
streak = int(row[1])
can_claim = (last_claim_date != date.today())
else:
streak = 0
can_claim = True
except Exception:
streak = 0
can_claim = False
# Calculate reward day (1-30 cycle)
reward_day = ((streak - 1) % 30) + 1 if streak > 0 else 1
# Calculate cooldown timer if already claimed
cooldown = ""
if not can_claim and last_claim_date == date.today():
now = datetime.now()
tomorrow = datetime.combine(date.today() + timedelta(days=1), datetime.min.time())
remaining = tomorrow - now
hours, remainder = divmod(remaining.seconds, 3600)
minutes, _ = divmod(remainder, 60)
if remaining.days > 0:
cooldown = f"{remaining.days} days"
else:
cooldown = f"{hours}h {minutes}m"
return render_template_string(
HOME_HTML,
username=session["username"],
streak=streak,
reward_day=reward_day,
can_claim=can_claim,
cooldown=cooldown,
rewards=REWARDS,
item_names=ITEM_NAMES,
range=range
)
@app.route("/logout", methods=["POST"])
def logout():
session.clear()
flash("Signed out.", "ok")
return redirect(url_for("login"))
@app.route("/claim", methods=["POST"])
def claim():
if "username" not in session:
return redirect(url_for("login"))
user_no = session["_userNo"]
nickname = session.get("_nickname", session["username"])
streak, is_first_claim = record_attendance(user_no)
if not is_first_claim:
flash("You already claimed today's reward.", "err")
return redirect(url_for("home"))
# Calculate reward day (1-30 cycle)
reward_day = ((streak - 1) % 30) + 1
reward_items = REWARDS.get(reward_day, [])
if not reward_items:
flash("No reward configured for today.", "err")
return redirect(url_for("home"))
messages = []
any_failed = False
# Send all reward items via mail
for item_id, qty, enchant in reward_items:
sym_no = send_mail(nickname, item_id, qty, enchant)
item_name = ITEM_NAMES.get(item_id, f"Item {item_id}")
# ✅ Treat NULL/empty @symNo as SUCCESS — common in private servers
if sym_no is not None: # Even if "", "UNKNOWN", or actual ID — treat as success
messages.append(f"📬 {item_name} x{qty} sent to your mailbox!")
else:
any_failed = True
messages.append(f"❌ Failed to send {item_name} x{qty}")
if not any_failed:
flash(f"🎉 Day {streak} Reward Claimed! " + " | ".join(messages), "ok")
else:
flash("⚠️ Partial failure: " + " | ".join(messages), "err")
return redirect(url_for("home"))
# -------------------- Entry point --------------------
if __name__ == "__main__":
from waitress import serve
print("🚀 Serving Attendance System at http://0.0.0.0:8893")
serve(app, host="0.0.0.0", port=8893)

