184 lines
8.3 KiB
Python
184 lines
8.3 KiB
Python
|
|
import telebot
|
|||
|
|
import requests
|
|||
|
|
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
|
|||
|
|
|
|||
|
|
BOT_TOKEN = "7741492900:AAG6M_faLy5JG7g0t2gBFj-sXtBIaBXQZHk"
|
|||
|
|
ALLOWED_USERS = [165067365]
|
|||
|
|
CF_API_TOKEN = "VfeGnC3v5McJ3sJ6bsjjhG0IhvOgY-5-v6lpYgH3"
|
|||
|
|
|
|||
|
|
bot = telebot.TeleBot(BOT_TOKEN)
|
|||
|
|
ZONE_CACHE = {}
|
|||
|
|
USER_STATE = {}
|
|||
|
|
|
|||
|
|
def get_zones():
|
|||
|
|
headers = {"Authorization": f"Bearer {CF_API_TOKEN}"}
|
|||
|
|
r = requests.get("https://api.cloudflare.com/client/v4/zones", headers=headers).json()
|
|||
|
|
return r.get("result", [])
|
|||
|
|
|
|||
|
|
def get_zone_id(domain):
|
|||
|
|
root = ".".join(domain.split(".")[-2:])
|
|||
|
|
if root in ZONE_CACHE:
|
|||
|
|
return ZONE_CACHE[root]
|
|||
|
|
for z in get_zones():
|
|||
|
|
if z["name"] == root:
|
|||
|
|
ZONE_CACHE[root] = z["id"]
|
|||
|
|
return z["id"]
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def is_authorized(uid):
|
|||
|
|
return uid in ALLOWED_USERS
|
|||
|
|
|
|||
|
|
def main_menu():
|
|||
|
|
markup = InlineKeyboardMarkup(row_width=2)
|
|||
|
|
markup.add(
|
|||
|
|
InlineKeyboardButton('➕ 添加记录', callback_data='add'),
|
|||
|
|
InlineKeyboardButton('🗑 删除记录', callback_data='del'),
|
|||
|
|
InlineKeyboardButton('☁️ 小黄云', callback_data='proxy'),
|
|||
|
|
InlineKeyboardButton('📋 列出记录', callback_data='list')
|
|||
|
|
)
|
|||
|
|
return markup
|
|||
|
|
|
|||
|
|
def domain_menu(action):
|
|||
|
|
zones = get_zones()
|
|||
|
|
markup = InlineKeyboardMarkup(row_width=2)
|
|||
|
|
for z in zones[:10]:
|
|||
|
|
markup.add(InlineKeyboardButton(z['name'], callback_data=f"{action}:{z['name']}"))
|
|||
|
|
markup.add(InlineKeyboardButton('🔙 返回', callback_data='back'))
|
|||
|
|
return markup
|
|||
|
|
|
|||
|
|
@bot.message_handler(commands=['help', 'start'])
|
|||
|
|
def start_cmd(message):
|
|||
|
|
if not is_authorized(message.from_user.id): return
|
|||
|
|
bot.send_message(message.chat.id, '🌐 CF DNS 管理', reply_markup=main_menu())
|
|||
|
|
|
|||
|
|
@bot.callback_query_handler(func=lambda c: True)
|
|||
|
|
def callback_handler(call):
|
|||
|
|
uid = call.from_user.id
|
|||
|
|
if not is_authorized(uid): return
|
|||
|
|
data = call.data
|
|||
|
|
|
|||
|
|
if data == 'back':
|
|||
|
|
bot.edit_message_text('🌐 CF DNS 管理', call.message.chat.id, call.message.message_id, reply_markup=main_menu())
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if data in ['add', 'del', 'proxy', 'list']:
|
|||
|
|
bot.edit_message_text(f'选择域名:', call.message.chat.id, call.message.message_id, reply_markup=domain_menu(data))
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if data.startswith('list:'):
|
|||
|
|
domain = data.split(':')[1]
|
|||
|
|
zone_id = get_zone_id(domain)
|
|||
|
|
headers = {"Authorization": f"Bearer {CF_API_TOKEN}"}
|
|||
|
|
r = requests.get(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records", headers=headers).json()
|
|||
|
|
records = r.get("result", [])
|
|||
|
|
if not records:
|
|||
|
|
txt = f'📋 {domain}\n\n无记录'
|
|||
|
|
else:
|
|||
|
|
lines = [f"{rec['name']} -> {rec['content']} ({'☁️' if rec['proxied'] else '⚪'})" for rec in records[:15]]
|
|||
|
|
txt = f'📋 {domain}\n\n' + '\n'.join(lines)
|
|||
|
|
markup = InlineKeyboardMarkup()
|
|||
|
|
markup.add(InlineKeyboardButton('🔙 返回', callback_data='back'))
|
|||
|
|
bot.edit_message_text(txt, call.message.chat.id, call.message.message_id, reply_markup=markup)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if data.startswith('add:'):
|
|||
|
|
domain = data.split(':')[1]
|
|||
|
|
USER_STATE[uid] = {'action': 'add', 'domain': domain}
|
|||
|
|
markup = InlineKeyboardMarkup()
|
|||
|
|
markup.add(InlineKeyboardButton('🔙 取消', callback_data='back'))
|
|||
|
|
bot.edit_message_text(f'➕ 添加记录到 {domain}\n\n请输入: 子域名 IP\n例如: www 1.2.3.4', call.message.chat.id, call.message.message_id, reply_markup=markup)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if data.startswith('del:'):
|
|||
|
|
domain = data.split(':')[1]
|
|||
|
|
zone_id = get_zone_id(domain)
|
|||
|
|
headers = {"Authorization": f"Bearer {CF_API_TOKEN}"}
|
|||
|
|
r = requests.get(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records", headers=headers).json()
|
|||
|
|
records = r.get("result", [])
|
|||
|
|
markup = InlineKeyboardMarkup(row_width=1)
|
|||
|
|
for rec in records[:10]:
|
|||
|
|
short = rec['name'].replace(f'.{domain}', '') if rec['name'] != domain else '@'
|
|||
|
|
markup.add(InlineKeyboardButton(f"🗑 {short} -> {rec['content']}", callback_data=f"doit:{rec['id']}:{domain}"))
|
|||
|
|
markup.add(InlineKeyboardButton('🔙 返回', callback_data='back'))
|
|||
|
|
bot.edit_message_text(f'选择要删除的记录:', call.message.chat.id, call.message.message_id, reply_markup=markup)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if data.startswith('doit:'):
|
|||
|
|
parts = data.split(':')
|
|||
|
|
rec_id, domain = parts[1], parts[2]
|
|||
|
|
zone_id = get_zone_id(domain)
|
|||
|
|
headers = {"Authorization": f"Bearer {CF_API_TOKEN}"}
|
|||
|
|
requests.delete(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{rec_id}", headers=headers)
|
|||
|
|
bot.answer_callback_query(call.id, '✅ 已删除')
|
|||
|
|
bot.edit_message_text('🌐 CF DNS 管理', call.message.chat.id, call.message.message_id, reply_markup=main_menu())
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if data.startswith('proxy:'):
|
|||
|
|
domain = data.split(':')[1]
|
|||
|
|
zone_id = get_zone_id(domain)
|
|||
|
|
headers = {"Authorization": f"Bearer {CF_API_TOKEN}"}
|
|||
|
|
r = requests.get(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records", headers=headers).json()
|
|||
|
|
records = r.get("result", [])
|
|||
|
|
markup = InlineKeyboardMarkup(row_width=1)
|
|||
|
|
for rec in records[:10]:
|
|||
|
|
short = rec['name'].replace(f'.{domain}', '') if rec['name'] != domain else '@'
|
|||
|
|
icon = '☁️' if rec['proxied'] else '⚪'
|
|||
|
|
markup.add(InlineKeyboardButton(f"{icon} {short} -> {rec['content']}", callback_data=f"toggle:{rec['id']}:{domain}"))
|
|||
|
|
markup.add(InlineKeyboardButton('🔙 返回', callback_data='back'))
|
|||
|
|
bot.edit_message_text(f'点击切换小黄云:', call.message.chat.id, call.message.message_id, reply_markup=markup)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
if data.startswith('toggle:'):
|
|||
|
|
parts = data.split(':')
|
|||
|
|
rec_id, domain = parts[1], parts[2]
|
|||
|
|
zone_id = get_zone_id(domain)
|
|||
|
|
headers = {"Authorization": f"Bearer {CF_API_TOKEN}", "Content-Type": "application/json"}
|
|||
|
|
r = requests.get(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{rec_id}", headers=headers).json()
|
|||
|
|
rec = r.get("result", {})
|
|||
|
|
new_state = not rec.get('proxied', False)
|
|||
|
|
requests.patch(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{rec_id}", headers=headers, json={"proxied": new_state})
|
|||
|
|
bot.answer_callback_query(call.id, f"✅ 小黄云已{'开启' if new_state else '关闭'}")
|
|||
|
|
r = requests.get(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records", headers=headers).json()
|
|||
|
|
records = r.get("result", [])
|
|||
|
|
markup = InlineKeyboardMarkup(row_width=1)
|
|||
|
|
for rec in records[:10]:
|
|||
|
|
short = rec['name'].replace(f'.{domain}', '') if rec['name'] != domain else '@'
|
|||
|
|
icon = '☁️' if rec['proxied'] else '⚪'
|
|||
|
|
markup.add(InlineKeyboardButton(f"{icon} {short} -> {rec['content']}", callback_data=f"toggle:{rec['id']}:{domain}"))
|
|||
|
|
markup.add(InlineKeyboardButton('🔙 返回', callback_data='back'))
|
|||
|
|
bot.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=markup)
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
@bot.message_handler(func=lambda m: m.from_user.id in USER_STATE)
|
|||
|
|
def handle_add(message):
|
|||
|
|
uid = message.from_user.id
|
|||
|
|
if not is_authorized(uid): return
|
|||
|
|
state = USER_STATE.get(uid)
|
|||
|
|
if not state or state['action'] != 'add': return
|
|||
|
|
|
|||
|
|
parts = message.text.strip().split()
|
|||
|
|
if len(parts) < 2:
|
|||
|
|
bot.reply_to(message, '格式错误,请输入: 子域名 IP')
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
sub, ip = parts[0], parts[1]
|
|||
|
|
domain = state['domain']
|
|||
|
|
del USER_STATE[uid]
|
|||
|
|
|
|||
|
|
zone_id = get_zone_id(domain)
|
|||
|
|
if not zone_id:
|
|||
|
|
bot.reply_to(message, f'找不到域名 {domain}')
|
|||
|
|
return
|
|||
|
|
name = f"{sub}.{domain}" if sub != '@' else domain
|
|||
|
|
headers = {"Authorization": f"Bearer {CF_API_TOKEN}", "Content-Type": "application/json"}
|
|||
|
|
data = {"type": "A", "name": name, "content": ip, "ttl": 1, "proxied": False}
|
|||
|
|
r = requests.post(f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records", headers=headers, json=data).json()
|
|||
|
|
if r.get("success"):
|
|||
|
|
bot.reply_to(message, f'✅ 添加成功: {name} -> {ip}', reply_markup=main_menu())
|
|||
|
|
else:
|
|||
|
|
bot.reply_to(message, f'❌ 失败: {r.get("errors", [])}')
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
print('CF Bot 启动...')
|
|||
|
|
bot.infinity_polling()
|