Files
vps-management-bot/scripts/cf-bot/cf_multi_bot.py
2026-03-21 01:10:53 +08:00

184 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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()