Metin2
Overview
Reward your Metin2 private server players for voting on topgservers. This guide uses Python quest/event scripts to poll the vote-check API and a lightweight Python sidecar for real-time webhook rewards. Players receive yang, items, or EXP automatically after voting.
Prerequisites
- A Metin2 private server with Python quest support (most source releases)
- Python 2.7 or 3.x available on the server machine
- A topgservers API key — generate one in My Servers → API
- Your webhook secret (optional, for real-time rewards)
- Port 443 outbound access from your server for HTTPS calls
Installation
1 Create the quest script
Metin2 private servers use Python-based quest scripts. Place this in your `quest/` directory or load it through your server's quest system.
share/locale/quest/
├── topg_vote.quest
└── topg_vote_check.py 2 Write the quest definition
The quest definition triggers on login and periodically checks vote status for the player.
quest topg_vote begin
state start begin
when login begin
timer("topg_check", 60)
end
when timer("topg_check") begin
local result = topg_vote_check(pc.get_name())
if result == 1 then
-- Grant rewards
pc.give_item2(27001, 5) -- Red Potion (L) x5
pc.changegold(500000) -- 500k yang
syschat("Thanks for voting on topgservers! Rewards granted.")
end
timer("topg_check", 60)
end
end
end 3 Create the vote check script
This Python script is called from the quest to check vote status via the API.
"""
Vote check helper for Metin2 quest system.
Called from topg_vote.quest to verify player votes.
"""
import urllib2 # Python 2 (common in Metin2 sources)
import json
API_KEY = "tgs_your_api_key_here"
API_URL = "https://topgservers.net/api/v1/vote-check"
def topg_vote_check(player_name):
"""Returns 1 if player voted today, 0 otherwise."""
try:
req = urllib2.Request(
API_URL + "?username=" + player_name,
headers={"Authorization": "Bearer " + API_KEY}
)
response = urllib2.urlopen(req, timeout=5)
data = json.loads(response.read())
return 1 if data.get("voted") else 0
except Exception:
return 0 4 Alternative: Python 3 vote checker (standalone)
If your server supports Python 3 or you prefer a standalone cron-based approach, use this script to write vote results to a database table.
#!/usr/bin/env python3
"""
Standalone vote checker for Metin2 servers.
Run via cron: * * * * * python3 /path/to/topg_cron_check.py
"""
import requests
import mysql.connector
API_KEY = "tgs_your_api_key_here"
DB_CONFIG = {
"host": "127.0.0.1",
"user": "metin2",
"password": "password",
"database": "player"
}
def check_online_players():
db = mysql.connector.connect(**DB_CONFIG)
cursor = db.cursor()
# Get online players
cursor.execute("SELECT name FROM player WHERE online = 1")
players = [row[0] for row in cursor.fetchall()]
for name in players:
try:
resp = requests.get(
f"https://topgservers.net/api/v1/vote-check?username={name}",
headers={"Authorization": f"Bearer {API_KEY}"},
timeout=5
)
if resp.status_code == 200 and resp.json().get("voted"):
cursor.execute(
"INSERT IGNORE INTO topg_votes (name, date) "
"VALUES (%s, CURDATE())",
(name,)
)
except Exception:
continue
db.commit()
db.close()
if __name__ == "__main__":
check_online_players() Configuration
# Configuration
API_KEY = "tgs_your_api_key_here"
WEBHOOK_SECRET = "whsec_your_secret_here"
API_URL = "https://topgservers.net/api/v1/vote-check"
# Rewards
REWARD_ITEM = 27001 # Red Potion (L)
REWARD_COUNT = 5
REWARD_YANG = 500000 # 500k yang
REWARD_EXP = 0 # bonus EXP (0 = disabled)
# Cooldown
CHECK_INTERVAL = 60 # seconds between checks Vote Check
Call the /api/v1/vote-check endpoint to determine if a player has voted today.
import requests
resp = requests.get(
"https://topgservers.net/api/v1/vote-check?username=" + player_name,
headers={"Authorization": "Bearer " + API_KEY},
timeout=5
)
if resp.status_code == 200 and resp.json().get("voted"):
# Player voted today — grant reward
pass Webhook Receiver
Verify the X-TopG-Signature header using HMAC-SHA256 to ensure webhook payloads are authentic.
"""
Webhook receiver for Metin2 servers.
Run alongside your game server. Inserts into topg_votes table
so the quest script can pick up rewards on next check.
"""
import hmac, hashlib, json, mysql.connector
from http.server import HTTPServer, BaseHTTPRequestHandler
WEBHOOK_SECRET = "whsec_your_secret_here"
DB_CONFIG = {
"host": "127.0.0.1",
"user": "metin2",
"password": "password",
"database": "player"
}
class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(length).decode()
sig = self.headers.get('X-TopG-Signature', '')
if not verify_signature(body, sig):
self.send_response(401)
self.end_headers()
return
data = json.loads(body)
username = data.get('username', '')
if username:
db = mysql.connector.connect(**DB_CONFIG)
cursor = db.cursor()
cursor.execute(
"INSERT IGNORE INTO topg_votes (name, date) "
"VALUES (%s, CURDATE())",
(username,)
)
db.commit()
db.close()
self.send_response(200)
self.end_headers()
def verify_signature(body, sig_header):
parts = dict(p.split('=', 1) for p in sig_header.split(',') if '=' in p)
timestamp = parts.get('t', '')
received = parts.get('v1', '')
expected = hmac.new(
WEBHOOK_SECRET.encode(),
f"{timestamp}.{body}".encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, received)
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 8090), WebhookHandler)
print("Webhook receiver listening on :8090")
server.serve_forever() Reward Examples
# Items — give item by VNUM
pc.give_item2(27001, 5) # Red Potion (L) x5
pc.give_item2(50300, 1) # Skill Reset Book
# Yang (gold)
pc.changegold(500000) # +500k yang
# EXP
pc.give_exp2(100000) # +100k EXP
# Buff / status
affect.add(apply.ATT_SPEED, 30, 3600) # +30% attack speed for 1hr
# Custom announcement
notice_all("Player " .. pc.get_name() .. " voted on topgservers!") Notes & Tips
Most Metin2 private servers run Python 2.7 in the quest engine. The standalone cron approach (Python 3 + requests) is recommended for reliability. Create the votes table with: `CREATE TABLE topg_votes (name VARCHAR(50), date DATE, rewarded TINYINT DEFAULT 0, PRIMARY KEY(name, date));` The quest then reads this table on login and timer events.