Back to Tech
Tech • Crate Telegram Bot

Outline key api ကို Telegram Bot နဲ့ချတ်ဆက်ပြီး Outline key server အများကြီး ထည့်ပြီး key အလွယ်ကူဆုံးထုတ်နည်း

Ubuntu 24.04 • dotenv + dotpy

သတိ: .env ကိုပြင်တဲ့ အခါမှာ ရှေ့မှာ server name ထည့်ပြီး (:)ခံပြီး api ကို မှန်ကန်အောင်ထည့်ဖို့ လိုပါတယ် နောက်ထပ် server များထပ်ထည့်ရင် အောက်တကြောင်းလုံးဝ မဆင်းပဲ comma ခံပြီး server name နဲ့ api ကို ဆက်ထည့်သွားရပါမယ်။ Domain သုံးမယ်ဆို ထိုနည်းတူပါပဲ
Goal: VPS အသစ်မှာ bot ကနေကြိုကတဲ့ server ကနေ key ထုတ်လို့ရဖို့ပါပဲ

vps ကို update လုပ်ပြိး လိုအပ်တဲ့ app များထည့်သင်းပါ


VPS Environment ပြင်ဆင်ခြင်း အရင်ဆုံး VPS အသစ်ကို Update လုပ်ပြီး လိုအပ်တဲ့ Python Environment တည်ဆောက်ပါ။

sudo apt update && sudo apt upgrade -y
sudo apt install python3 python3-pip nano -y
      

Library များ Install လုပ်ခြင်း Bot အလုပ်လုပ်ဖို့ လိုအပ်တဲ့ Python Library ၃ ခုကို သွင်းပါ။

pip3 install pyTelegramBotAPI python-dotenv requests
      

Project Folder နှင့် ဖိုင်များ တည်ဆောက်ခြင်း ဖိုင်တွေကို စနစ်တကျရှိအောင် folder တစ်ခုထဲမှာ ထည့်ပါမယ်။

mkdir ~/outline-bot && cd ~/outline-bot
      

.env ဖိုင်ဆောက်ခြင်း

nano .env
      

အောက်က စာတွေကို ပြင်ဆင်ပြီး ထည့် save ပါ

BOT_TOKEN=Telegram bot token ထည့်ပါ
SERVERS=SG-1:https://178.xxx.16.73:XXX57/LL.........(ယခုလို Outline api ထည့်ပါ),(serverများထပ်ထည့်လိုပါက ကော်မာခံ မိမိပေးလိုသောနာမည်ထည့်)SG-2:https://143.xxx.198.249:xxxxx/LLYTUJHG.........
DOMAINS=SG-1:outline.yourdomain.com,SG-2:sg2.another-domain.net
      
Ctrl+O enter Ctrl+X နှိပ်ပြီး save

outline_bot.py ဖိုင်ဆောက်ခြင်း

nano outline_bot.py

အောက်က စာများကူးထည့်ပါ

import os
import telebot
import requests
import re
from telebot.types import InlineKeyboardMarkup, InlineKeyboardButton
from dotenv import load_dotenv

load_dotenv()

BOT_TOKEN = os.getenv('BOT_TOKEN')
ADMIN_ID = 5XXXXXX(မိမိ Telegram Id ထည့်ပါ)
bot = telebot.TeleBot(BOT_TOKEN)

# Servers နှင့် Domains စာရင်းများကို Dictionary ပြောင်းခြင်း
servers_raw = os.getenv('SERVERS').split(',')
SERVERS = {s.split(':')[0]: ":".join(s.split(':')[1:]) for s in servers_raw}

domains_raw = os.getenv('DOMAINS', '').split(',')
SERVER_DOMAINS = {d.split(':')[0]: d.split(':')[1] for d in domains_raw if ':' in d}

user_states = {}

def is_admin(chat_id):
    return chat_id == ADMIN_ID

def bytes_to_gb(bytes_val):
    return round(bytes_val / (1024**3), 2)

# IP ကို Domain နှင့် အစားထိုးပေးသည့် function
def replace_with_domain(access_url, server_name):
    domain = SERVER_DOMAINS.get(server_name)
    if domain:
        return re.sub(r'@\[?([a-zA-Z0-9\.-]+)\]?:', f'@{domain}:', access_url)
    return access_url

def get_server_markup(action):
    markup = InlineKeyboardMarkup()
    for name in SERVERS.keys():
        markup.add(InlineKeyboardButton(name, callback_data=f"{action}:{name}"))
    return markup

@bot.message_handler(commands=['start'])
def welcome(message):
    if not is_admin(message.chat.id): return
    bot.reply_to(message, "🌟 Multi-Server Outline Bot\nPrefix: 🇸🇬_Good_Speed_Server\n/newkey, /list, /del command များကို သုံးနိုင်ပါသည်။")

@bot.message_handler(commands=['newkey'])
def ask_newkey(message):
    if not is_admin(message.chat.id): return
    args = message.text.split()
    if len(args) < 3:
        bot.reply_to(message, "⚠️ format: `/newkey [Name] [GB]`")
        return
    user_states[message.chat.id] = {'name': args[1], 'gb': int(args[2])}
    bot.send_message(message.chat.id, "ဘယ် Server မှာ Key ထုတ်မလဲ ရွေးပါ-", reply_markup=get_server_markup("create"))

@bot.message_handler(commands=['list'])
def ask_list(message):
    if not is_admin(message.chat.id): return
    bot.send_message(message.chat.id, "ဘယ် Server ရဲ့ စာရင်းကို ကြည့်မလဲ-", reply_markup=get_server_markup("list"))

@bot.message_handler(commands=['del'])
def ask_delete(message):
    if not is_admin(message.chat.id): return
    args = message.text.split()
    if len(args) < 2:
        bot.reply_to(message, "⚠️ format: `/del [ID]`")
        return
    user_states[message.chat.id] = {'del_id': args[1]}
    bot.send_message(message.chat.id, f"ID {args[1]} ကို ဘယ် Server ကနေ ဖျက်မလဲ ရွေးပါ-", reply_markup=get_server_markup("confirm_del"))

@bot.callback_query_handler(func=lambda call: True)
def handle_query(call):
    action, server_name = call.data.split(':')
    api_url = SERVERS.get(server_name)

    if action == "create":
        state = user_states.get(call.message.chat.id)
        if not state: return

        # နာမည်ပေးစနစ်သစ်: 🇸🇬_Good_Speed_Server-[Name]
        full_name = f"🇸🇬_Good_Speed_Server-{state['name']}"

        bot.edit_message_text(f"⏳ {server_name} မှာ Key ထုတ်နေပါတယ်...", call.message.chat.id, call.message.message_id)
        try:
            bytes_limit = state['gb'] * 1024 * 1024 * 1024
            res = requests.post(f"{api_url}/access-keys", verify=False).json()
            k_id = res.get('id')

            # Domain link ပြောင်းခြင်း
            original_url = res.get('accessUrl')
            domain_url = replace_with_domain(original_url, server_name)

            requests.put(f"{api_url}/access-keys/{k_id}/name", data={'name': full_name}, verify=False)
            requests.put(f"{api_url}/access-keys/{k_id}/data-limit", json={"limit": {"bytes": bytes_limit}}, verify=False)

            final_link = f"{domain_url}#{full_name}"

            bot.send_message(call.message.chat.id, f"✅ **Key Created!**\n🏰 Server: {server_name}\n👤 Name: {full_name}\n🆔 ID: {k_id}\n\n`{final_link}`", parse_mode='Markdown')
        except Exception as e:
            bot.send_message(call.message.chat.id, f"❌ Error: {e}")

    elif action == "list":
        bot.edit_message_text(f"📋 {server_name} စာရင်းယူနေပါတယ်...", call.message.chat.id, call.message.message_id)
        try:
            keys = requests.get(f"{api_url}/access-keys", verify=False).json()
            usage = requests.get(f"{api_url}/metrics/transfer", verify=False).json().get('bytesTransferredByUserId', {})
            report = f"🏰 **{server_name} Key List**\n\n"
            total = 0
            for k in keys.get('accessKeys', []):
                u = usage.get(str(k.get('id')), 0)
                total += u
                d_url = replace_with_domain(k.get('accessUrl'), server_name)
                report += f"🆔 {k.get('id')} | 👤 {k.get('name')}\n📊 {bytes_to_gb(u)} / {bytes_to_gb(k.get('dataLimit', {}).get('bytes', 0))} GB\n🔗 `{d_url}#{k.get('name')}`\n\n"
            report += f"➖➖➖➖➖➖\n☁️ Total Server Usage: {bytes_to_gb(total)} GB"
            bot.send_message(call.message.chat.id, report, parse_mode='Markdown')
        except Exception as e:
            bot.send_message(call.message.chat.id, f"❌ Error: {e}")

    elif action == "confirm_del":
        state = user_states.get(call.message.chat.id)
        if not state: return
        del_id = state['del_id']
        bot.edit_message_text(f"🔍 {server_name} မှာ ID {del_id} ကို စစ်ဆေးနေပါတယ်...", call.message.chat.id, call.message.message_id)
        try:
            keys = requests.get(f"{api_url}/access-keys", verify=False).json()
            usage = requests.get(f"{api_url}/metrics/transfer", verify=False).json().get('bytesTransferredByUserId', {})
            target_key = next((k for k in keys.get('accessKeys', []) if str(k.get('id')) == str(del_id)), None)

            if target_key:
                confirm_msg = (f"⚠️ **Key ဖျက်ရန် သေချာပါသလား?**\n\n"
                               f"🏰 Server: {server_name}\n"
                               f"👤 Name: {target_key.get('name')}\n"
                               f"📊 Usage: {bytes_to_gb(usage.get(str(del_id), 0))} GB\n\n"
                               f"ဖျက်မည်ဆိုပါက 'Confirm Delete' ကို နှိပ်ပါ။")
                markup = InlineKeyboardMarkup()
                markup.add(InlineKeyboardButton("✅ Confirm Delete", callback_data=f"final_del:{server_name}"),
                           InlineKeyboardButton("❌ Cancel", callback_data="cancel_del:x"))
                bot.send_message(call.message.chat.id, confirm_msg, reply_markup=markup, parse_mode='Markdown')
            else:
                bot.send_message(call.message.chat.id, f"❌ ID {del_id} ကို {server_name} မှာ ရှာမတွေ့ပါ။")
        except Exception as e:
            bot.send_message(call.message.chat.id, f"❌ Error: {e}")

    elif action == "final_del":
        state = user_states.get(call.message.chat.id)
        if not state: return
        del_id = state['del_id']
        try:
            res = requests.delete(f"{api_url}/access-keys/{del_id}", verify=False)
            if res.status_code == 204:
                bot.edit_message_text(f"🗑️ {server_name} မှ ID {del_id} ကို ဖျက်ပြီးပါပြီ။", call.message.chat.id, call.message.message_id)
            else:
                bot.edit_message_text("❌ ဖျက်လို့ မရနိုင်ပါ။", call.message.chat.id, call.message.message_id)
        except Exception as e:
            bot.send_message(call.message.chat.id, f"❌ Error: {e}")

    elif action == "cancel_del":
        bot.edit_message_text("❌ လုပ်ဆောင်ချက်ကို ပယ်ဖျက်လိုက်ပါပြီ။", call.message.chat.id, call.message.message_id)

bot.polling()

Systemd Service အဖြစ် သတ်မှတ်ခြင်း (Auto-run) VPS ပိတ်သွားရင်တောင် Bot ပြန်ပွင့်လာဖို့အတွက် Service ဖိုင် ဆောက်ရပါမယ်။

sudo nano /etc/systemd/system/outlinebot.service

အောက်က code များကူးထည့်ပါ (မိမိတည်ဆောက်ထားတဲ့ bot file location နဲ့ နာမည် တူရပါမယ်

[Unit]
Description=Outline Multi-Server Bot
After=network.target

[Service]
User=root
WorkingDirectory=/root/outline-bot
ExecStart=/usr/bin/python3 /root/outline-bot/outline_bot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
Ctrl+O enter Ctrl+X နှိပ်ပြီး save

Service ကို စတင်ခြင်း:

sudo systemctl daemon-reload
sudo systemctl enable outlinebot
sudo systemctl start outlinebot
Goal: Bot မှာ start နှပ်ပြီး အလုပ်မလုပ်စမ်းသက်လို့ရပါပြီ
သတိ: server အသစ်ထည့်တိုင်း .env မှာ outline api ထပ်ထည့်ဖို့လိုပါတယ်။ .env ပြင်ပြီးတိုင်း bot ကို restart ပြန်လုပ်ပေးပါ