Merge pull request #4 from ReturnFI/Dev

Telegram bot
This commit is contained in:
Whispering Wind
2024-08-07 18:56:02 +03:30
committed by GitHub
14 changed files with 619 additions and 178 deletions

View File

@ -9,7 +9,12 @@
```shell
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/menu.sh)
```
بعد از نصب کافیه از دستور `hys2` برای اجرای اسکریپت Hysteria2 استفاده کنید و نیازی به اجرا دستور نصب نیست.
بعد از نصب کافیه از دستور `hys2` برای اجرای اسکریپت Hysteria2 استفاده کنید و نیازی به اجرا دوباره دستور نصب نیست.
### دستور آپدیت:
```shell
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/upgrade.sh)
```
<br />
<p align="center">

View File

@ -17,6 +17,10 @@ After installation, simply use the command `hys2` to run the Hysteria2 script.
There is no need to execute the installation command again.
### Upgrade command :
```shell
bash <(curl https://raw.githubusercontent.com/ReturnFI/Hysteria2/main/upgrade.sh)
```
<br />
<p align="center">

1
VERSION Normal file
View File

@ -0,0 +1 @@
0.1.3

View File

@ -12,7 +12,7 @@ import validator
SCRIPT_DIR = '/etc/hysteria/core/scripts'
DEBUG = True
DEBUG = False
class Command(Enum):
@ -30,6 +30,7 @@ class Command(Enum):
TRAFFIC_STATUS = 'traffic.py' # won't be call directly (it's a python module)
LIST_USERS = os.path.join(SCRIPT_DIR, 'hysteria2', 'list_users.sh')
SERVER_INFO = os.path.join(SCRIPT_DIR, 'hysteria2', 'server_info.sh')
INSTALL_TELEGRAMBOT = os.path.join(SCRIPT_DIR, 'telegrambot', 'runbot.sh')
INSTALL_TCP_BRUTAL = os.path.join(SCRIPT_DIR, 'tcp-brutal', 'install.sh')
INSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'install.sh')
UNINSTALL_WARP = os.path.join(SCRIPT_DIR, 'warp', 'uninstall.sh')
@ -42,11 +43,14 @@ def run_cmd(command: list[str]):
Runs a command and returns the output.
Could raise subprocess.CalledProcessError
'''
if DEBUG and Command.GET_USER.value not in command and Command.LIST_USERS.value not in command:
# if the command is GET_USER or LIST_USERS we don't print the debug-command and just print the output
if DEBUG and not (Command.GET_USER.value in command or Command.LIST_USERS.value in command):
print(' '.join(command))
result = subprocess.check_output(command, shell=False)
if DEBUG:
print(result.decode().strip())
print(result.decode().strip())
def generate_password() -> str:
@ -174,17 +178,27 @@ def edit_user(username: str, new_username: str, new_traffic_limit: int, new_expi
run_cmd(command_args)
@ cli.command('remove-user')
@ click.option('--username', '-u', required=True, help='Username for the user to remove', type=str)
def remove_user(username: str):
run_cmd(['bash', Command.REMOVE_USER.value, username])
@ cli.command('show-user-uri')
@ click.option('--username', '-u', required=True, help='Username for the user to show the URI', type=str)
def show_user_uri(username: str):
run_cmd(['bash', Command.SHOW_USER_URI.value, username])
@cli.command('show-user-uri')
@click.option('--username', '-u', required=True, help='Username for the user to show the URI', type=str)
@click.option('--qrcode', '-qr', is_flag=True, help='Generate QR code for the URI')
@click.option('--ipv', '-ip', type=click.IntRange(4, 6), default=4, help='IP version (4 or 6)')
@click.option('--all', '-a', is_flag=True, help='Show both IPv4 and IPv6 URIs and generate QR codes for both if requested')
def show_user_uri(username: str, qrcode: bool, ipv: int, all: bool):
command_args = ['bash', Command.SHOW_USER_URI.value, '-u', username]
if qrcode:
command_args.append('-qr')
if all:
command_args.append('-a')
else:
command_args.extend(['-ip', str(ipv)])
run_cmd(command_args)
@ cli.command('traffic-status')
@ -196,6 +210,7 @@ def traffic_status():
def list_users():
run_cmd(['bash', Command.LIST_USERS.value])
@cli.command('server-info')
def server_info():
output = run_cmd(['bash', Command.SERVER_INFO.value])
@ -233,11 +248,25 @@ def configure_warp(all: bool, popular_sites: bool, domestic_sites: bool, block_a
"domestic_sites": domestic_sites,
"block_adult_sites": block_adult_sites
}
options = {k: 'true' if v else 'false' for k, v in options.items()}
run_cmd(['bash', Command.CONFIGURE_WARP.value,
run_cmd(['bash', Command.CONFIGURE_WARP.value,
options['all'], options['popular_sites'], options['domestic_sites'], options['block_adult_sites']])
@cli.command('telegram')
@click.option('--action', '-a', required=True, help='Action to perform: start or stop', type=click.Choice(['start', 'stop'], case_sensitive=False))
@click.option('--token', '-t', required=False, help='Token for running the telegram bot', type=str)
@click.option('--adminid', '-aid', required=False, help='Telegram admins ID for running the telegram bot', type=str)
def telegram(action: str, token: str, adminid: str):
if action == 'start':
if not token or not adminid:
print("Error: Both --token and --adminid are required for the start action.")
return
admin_ids = f'{adminid}'
run_cmd(['bash', Command.INSTALL_TELEGRAMBOT.value, 'start', token, admin_ids])
elif action == 'stop':
run_cmd(['bash', Command.INSTALL_TELEGRAMBOT.value, 'stop'])
# endregion

View File

@ -2,8 +2,8 @@
# Source the path.sh script to load the necessary variables
source /etc/hysteria/core/scripts/path.sh
source /etc/hysteria/core/scripts/utils.sh
define_colors
# source /etc/hysteria/core/scripts/utils.sh
# define_colors
# Function to add a new user to the configuration
add_user() {
@ -55,7 +55,7 @@ add_user() {
python3 "$CLI_PATH" restart-hysteria2 > /dev/null 2>&1
echo -e "${green}User $username added successfully.${NC}"
echo -e "User $username added successfully."
}
# Call the function with the provided arguments

View File

@ -1,57 +1,86 @@
#!/bin/bash
# Source the path.sh script to load the configuration variables
source /etc/hysteria/core/scripts/path.sh
# Function to show URI if Hysteria2 is installed and active
show_uri() {
if [ -f "$USERS_FILE" ]; then
if systemctl is-active --quiet hysteria-server.service; then
# Check if the username is provided as an argument
if [ -z "$1" ]; then
echo "Usage: $0 <username>"
local username
local generate_qrcode=false
local ip_version=4
local show_all=false
while [[ "$#" -gt 0 ]]; do
case $1 in
-u|--username) username="$2"; shift ;;
-qr|--qrcode) generate_qrcode=true ;;
-ip) ip_version="$2"; shift ;;
-a|--all) show_all=true ;;
*) echo "Unknown parameter passed: $1"; exit 1 ;;
esac
shift
done
if [ -z "$username" ]; then
echo "Usage: $0 -u <username> [-qr] [-ip <4|6>] [-a]"
exit 1
fi
username=$1
# Validate the username
if jq -e "has(\"$username\")" "$USERS_FILE" > /dev/null; then
# Get the selected user's details
authpassword=$(jq -r ".\"$username\".password" "$USERS_FILE")
port=$(jq -r '.listen' "$CONFIG_FILE" | cut -d':' -f2)
sha256=$(jq -r '.tls.pinSHA256' "$CONFIG_FILE")
obfspassword=$(jq -r '.obfs.salamander.password' "$CONFIG_FILE")
# Get IP addresses
IP=$(curl -s -4 ip.gs)
IP6=$(curl -s -6 ip.gs)
generate_uri() {
local ip_version=$1
local ip=$2
if [ "$ip_version" -eq 4 ]; then
echo "hy2://$username%3A$authpassword@$ip:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv4"
elif [ "$ip_version" -eq 6 ]; then
echo "hy2://$username%3A$authpassword@[$ip]:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv6"
fi
}
# Construct URI
URI="hy2://$username%3A$authpassword@$IP:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv4"
URI6="hy2://$username%3A$authpassword@[$IP6]:$port?obfs=salamander&obfs-password=$obfspassword&pinSHA256=$sha256&insecure=1&sni=bts.com#$username-IPv6"
if [ "$show_all" = true ]; then
IP=$(curl -s -4 ip.gs)
URI=$(generate_uri 4 "$IP")
IP6=$(curl -s -6 ip.gs)
URI6=$(generate_uri 6 "$IP6")
echo -e "\nIPv4:\n$URI\n"
echo -e "\nIPv6:\n$URI6\n"
else
if [ "$ip_version" -eq 4 ]; then
IP=$(curl -s -4 ip.gs)
URI=$(generate_uri 4 "$IP")
echo -e "\nIPv4:\n$URI\n"
elif [ "$ip_version" -eq 6 ]; then
IP6=$(curl -s -6 ip.gs)
URI6=$(generate_uri 6 "$IP6")
echo -e "\nIPv6:\n$URI6\n"
else
echo "Invalid IP version. Use 4 for IPv4 or 6 for IPv6."
exit 1
fi
fi
# Generate QR codes
qr1=$(echo -n "$URI" | qrencode -t UTF8 -s 3 -m 2)
qr2=$(echo -n "$URI6" | qrencode -t UTF8 -s 3 -m 2)
# Display QR codes and URIs
cols=$(tput cols)
echo -e "\nIPv4:\n"
echo "$qr1" | while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
done
echo -e "\nIPv6:\n"
echo "$qr2" | while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
done
echo
echo "IPv4: $URI"
echo
echo "IPv6: $URI6"
echo
if [ "$generate_qrcode" = true ]; then
cols=$(tput cols)
if [ -n "$URI" ]; then
qr1=$(echo -n "$URI" | qrencode -t UTF8 -s 3 -m 2)
echo -e "\nIPv4 QR Code:\n"
echo "$qr1" | while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
done
fi
if [ -n "$URI6" ]; then
qr2=$(echo -n "$URI6" | qrencode -t UTF8 -s 3 -m 2)
echo -e "\nIPv6 QR Code:\n"
echo "$qr2" | while IFS= read -r line; do
printf "%*s\n" $(( (${#line} + cols) / 2)) "$line"
done
fi
fi
else
echo "Invalid username. Please try again."
fi
@ -63,5 +92,4 @@ show_uri() {
fi
}
# Call the function with the provided username argument
show_uri "$1"
show_uri "$@"

View File

@ -1,28 +1,37 @@
source /etc/hysteria/core/scripts/path.sh
echo "Uninstalling Hysteria2..."
#sleep 1
echo "Running uninstallation script..."
bash <(curl -fsSL https://get.hy2.sh/) --remove >/dev/null 2>&1
#sleep 1
echo "Removing WARP"
python3 $CLI_PATH uninstall-warp
echo "Removing Hysteria folder..."
rm -rf /etc/hysteria >/dev/null 2>&1
#sleep 1
echo "Deleting hysteria user..."
userdel -r hysteria >/dev/null 2>&1
#sleep 1
echo "Removing systemd service files..."
rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server.service >/dev/null 2>&1
rm -f /etc/systemd/system/multi-user.target.wants/hysteria-server@*.service >/dev/null 2>&1
#sleep 1
echo "Reloading systemd daemon..."
systemctl daemon-reload >/dev/null 2>&1
#sleep 1
echo "Removing cron jobs..."
(crontab -l | grep -v "python3 /etc/hysteria/core/cli.py traffic-status" | crontab -) >/dev/null 2>&1
(crontab -l | grep -v "/etc/hysteria/core/scripts/hysteria2/kick.sh" | crontab -) >/dev/null 2>&1
#sleep 1
echo "Removing alias 'hys2' from .bashrc..."
sed -i '/alias hys2=.*\/etc\/hysteria\/menu.sh/d' ~/.bashrc
echo "Stopping hysteria-bot.service..."
systemctl stop hysteria-bot.service >/dev/null 2>&1
echo "Disabling hysteria-bot.service..."
systemctl disable hysteria-bot.service >/dev/null 2>&1
echo "Hysteria2 uninstalled!"
echo ""
echo ""

View File

@ -0,0 +1,89 @@
#!/bin/bash
source /etc/hysteria/core/scripts/utils.sh
define_colors
install_dependencies() {
echo "Installing dependencies from /etc/hysteria/requirements.txt..."
if ! pip3 install -r /etc/hysteria/requirements.txt > /dev/null 2>&1; then
echo "Error: Failed to install dependencies. Please check the requirements file and try again."
exit 1
fi
echo -e "${green}Dependencies installed successfully. ${NC}"
}
update_env_file() {
local api_token=$1
local admin_user_ids=$2
cat <<EOL > /etc/hysteria/core/scripts/telegrambot/.env
API_TOKEN=$api_token
ADMIN_USER_IDS=[$admin_user_ids]
EOL
}
create_service_file() {
cat <<EOL > /etc/systemd/system/hysteria-bot.service
[Unit]
Description=Hysteria Telegram Bot
After=network.target
[Service]
ExecStart=/usr/bin/python3 /etc/hysteria/core/scripts/telegrambot/tbot.py
WorkingDirectory=/etc/hysteria/core/scripts/telegrambot
Restart=always
User=root
Group=root
[Install]
WantedBy=multi-user.target
EOL
}
start_service() {
local api_token=$1
local admin_user_ids=$2
if systemctl is-active --quiet hysteria-bot.service; then
echo "The hysteria-bot.service is already running."
return
fi
install_dependencies
update_env_file "$api_token" "$admin_user_ids"
create_service_file
systemctl daemon-reload
systemctl enable hysteria-bot.service > /dev/null 2>&1
systemctl start hysteria-bot.service > /dev/null 2>&1
if systemctl is-active --quiet hysteria-bot.service; then
echo -e "${green}Hysteria bot setup completed. The service is now running. ${NC}"
echo -e "\n\n"
else
echo "Hysteria bot setup completed. The service failed to start."
fi
}
stop_service() {
systemctl stop hysteria-bot.service > /dev/null 2>&1
systemctl disable hysteria-bot.service > /dev/null 2>&1
rm -f /etc/hysteria/core/scripts/telegrambot/.env
echo -e "\n"
echo "Hysteria bot service stopped and disabled. .env file removed."
}
case "$1" in
start)
start_service "$2" "$3"
;;
stop)
stop_service
;;
*)
echo "Usage: $0 {start|stop} <API_TOKEN> <ADMIN_USER_IDS>"
exit 1
;;
esac
define_colors

View File

@ -0,0 +1,234 @@
import telebot
import subprocess
import qrcode
import io
import json
import os
from dotenv import load_dotenv
from telebot import types
load_dotenv()
API_TOKEN = os.getenv('API_TOKEN')
ADMIN_USER_IDS = json.loads(os.getenv('ADMIN_USER_IDS'))
CLI_PATH = '/etc/hysteria/core/cli.py'
bot = telebot.TeleBot(API_TOKEN)
def run_cli_command(command):
try:
result = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
return result.decode('utf-8').strip()
except subprocess.CalledProcessError as e:
return f'Error: {e.output.decode("utf-8")}'
def create_main_markup():
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
markup.row('Show User', 'Add User')
markup.row('Delete User', 'Server Info')
return markup
def is_admin(user_id):
return user_id in ADMIN_USER_IDS
@bot.message_handler(commands=['start'])
def send_welcome(message):
if is_admin(message.from_user.id):
markup = create_main_markup()
bot.reply_to(message, "Welcome to the User Management Bot!", reply_markup=markup)
else:
bot.reply_to(message, "Unauthorized access. You do not have permission to use this bot.")
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Add User')
def add_user(message):
msg = bot.reply_to(message, "Enter username:")
bot.register_next_step_handler(msg, process_add_user_step1)
def process_add_user_step1(message):
username = message.text.strip()
if username == "":
bot.reply_to(message, "Username cannot be empty. Please enter a valid username.")
return
command = f"python3 {CLI_PATH} list-users"
result = run_cli_command(command)
try:
users = json.loads(result)
if username in users:
bot.reply_to(message, f"Username '{username}' already exists. Please choose a different username.")
return
except json.JSONDecodeError:
bot.reply_to(message, "Error retrieving user list. Please try again later.")
return
msg = bot.reply_to(message, "Enter traffic limit (GB):")
bot.register_next_step_handler(msg, process_add_user_step2, username)
def process_add_user_step2(message, username):
try:
traffic_limit = int(message.text.strip())
msg = bot.reply_to(message, "Enter expiration days:")
bot.register_next_step_handler(msg, process_add_user_step3, username, traffic_limit)
except ValueError:
bot.reply_to(message, "Invalid traffic limit. Please enter a number.")
def process_add_user_step3(message, username, traffic_limit):
try:
expiration_days = int(message.text.strip())
command = f"python3 {CLI_PATH} add-user -u {username} -t {traffic_limit} -e {expiration_days}"
result = run_cli_command(command)
bot.reply_to(message, result)
except ValueError:
bot.reply_to(message, "Invalid expiration days. Please enter a number.")
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Show User')
def show_user(message):
msg = bot.reply_to(message, "Enter username:")
bot.register_next_step_handler(msg, process_show_user)
def process_show_user(message):
username = message.text.strip()
command = f"python3 {CLI_PATH} get-user -u {username}"
result = run_cli_command(command)
if "Error" in result or "Invalid" in result:
bot.reply_to(message, result)
else:
user_details = json.loads(result)
formatted_details = (
f"Name: {username}\n"
f"Traffic limit: {user_details['max_download_bytes'] / (1024 ** 3):.2f} GB\n"
f"Days: {user_details['expiration_days']}\n"
f"Account Creation: {user_details['account_creation_date']}\n"
f"Blocked: {user_details['blocked']}"
)
qr_command = f"python3 {CLI_PATH} show-user-uri -u {username} -ip 4"
qr_result = run_cli_command(qr_command)
if "Error" in qr_result or "Invalid" in qr_result:
bot.reply_to(message, qr_result)
return
uri_v4 = qr_result.split('\n')[-1].strip()
qr_v4 = qrcode.make(uri_v4)
bio_v4 = io.BytesIO()
qr_v4.save(bio_v4, 'PNG')
bio_v4.seek(0)
markup = types.InlineKeyboardMarkup(row_width=2)
markup.add(types.InlineKeyboardButton("Edit Username", callback_data=f"edit_username:{username}"),
types.InlineKeyboardButton("Edit Traffic Limit", callback_data=f"edit_traffic:{username}"))
markup.add(types.InlineKeyboardButton("Edit Expiration Days", callback_data=f"edit_expiration:{username}"),
types.InlineKeyboardButton("Renew Password", callback_data=f"renew_password:{username}"))
markup.add(types.InlineKeyboardButton("Renew Creation Date", callback_data=f"renew_creation:{username}"),
types.InlineKeyboardButton("Block User", callback_data=f"block_user:{username}"))
bot.send_photo(message.chat.id, bio_v4, caption=f"User Details:\n{formatted_details}\n\nIPv4 URI: {uri_v4}", reply_markup=markup)
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Server Info')
def server_info(message):
command = f"python3 {CLI_PATH} server-info"
result = run_cli_command(command)
bot.reply_to(message, result)
@bot.callback_query_handler(func=lambda call: call.data.startswith('edit_') or call.data.startswith('renew_') or call.data.startswith('block_'))
def handle_edit_callback(call):
action, username = call.data.split(':')
if action == 'edit_username':
msg = bot.send_message(call.message.chat.id, f"Enter new username for {username}:")
bot.register_next_step_handler(msg, process_edit_username, username)
elif action == 'edit_traffic':
msg = bot.send_message(call.message.chat.id, f"Enter new traffic limit (GB) for {username}:")
bot.register_next_step_handler(msg, process_edit_traffic, username)
elif action == 'edit_expiration':
msg = bot.send_message(call.message.chat.id, f"Enter new expiration days for {username}:")
bot.register_next_step_handler(msg, process_edit_expiration, username)
elif action == 'renew_password':
command = f"python3 {CLI_PATH} edit-user -u {username} -rp"
result = run_cli_command(command)
bot.send_message(call.message.chat.id, result)
elif action == 'renew_creation':
command = f"python3 {CLI_PATH} edit-user -u {username} -rc"
result = run_cli_command(command)
bot.send_message(call.message.chat.id, result)
elif action == 'block_user':
markup = types.InlineKeyboardMarkup()
markup.add(types.InlineKeyboardButton("True", callback_data=f"confirm_block:{username}:true"),
types.InlineKeyboardButton("False", callback_data=f"confirm_block:{username}:false"))
bot.send_message(call.message.chat.id, f"Set block status for {username}:", reply_markup=markup)
@bot.callback_query_handler(func=lambda call: call.data.startswith('confirm_block:'))
def handle_block_confirmation(call):
_, username, block_status = call.data.split(':')
command = f"python3 {CLI_PATH} edit-user -u {username} {'-b' if block_status == 'true' else ''}"
result = run_cli_command(command)
bot.send_message(call.message.chat.id, result)
def process_edit_username(message, username):
new_username = message.text.strip()
command = f"python3 {CLI_PATH} edit-user -u {username} -nu {new_username}"
result = run_cli_command(command)
bot.reply_to(message, result)
def process_edit_traffic(message, username):
try:
new_traffic_limit = int(message.text.strip())
command = f"python3 {CLI_PATH} edit-user -u {username} -nt {new_traffic_limit}"
result = run_cli_command(command)
bot.reply_to(message, result)
except ValueError:
bot.reply_to(message, "Invalid traffic limit. Please enter a number.")
def process_edit_expiration(message, username):
try:
new_expiration_days = int(message.text.strip())
command = f"python3 {CLI_PATH} edit-user -u {username} -ne {new_expiration_days}"
result = run_cli_command(command)
bot.reply_to(message, result)
except ValueError:
bot.reply_to(message, "Invalid expiration days. Please enter a number.")
@bot.message_handler(func=lambda message: is_admin(message.from_user.id) and message.text == 'Delete User')
def delete_user(message):
msg = bot.reply_to(message, "Enter username:")
bot.register_next_step_handler(msg, process_delete_user)
def process_delete_user(message):
username = message.text.strip()
command = f"python3 {CLI_PATH} remove-user -u {username}"
result = run_cli_command(command)
bot.reply_to(message, result)
@bot.inline_handler(lambda query: is_admin(query.from_user.id))
def handle_inline_query(query):
command = f"python3 {CLI_PATH} list-users"
result = run_cli_command(command)
try:
users = json.loads(result)
except json.JSONDecodeError:
bot.answer_inline_query(query.id, results=[], switch_pm_text="Error retrieving users.", switch_pm_user_id=query.from_user.id)
return
query_text = query.query.lower()
results = []
for username, details in users.items():
if query_text in username.lower():
title = f"Username: {username}"
description = f"Traffic Limit: {details['max_download_bytes'] / (1024 ** 3):.2f} GB, Expiration Days: {details['expiration_days']}"
results.append(types.InlineQueryResultArticle(
id=username,
title=title,
description=description,
input_message_content=types.InputTextMessageContent(
message_text=f"Name: {username}\n"
f"Traffic limit: {details['max_download_bytes'] / (1024 ** 3):.2f} GB\n"
f"Days: {details['expiration_days']}\n"
f"Account Creation: {details['account_creation_date']}\n"
f"Blocked: {details['blocked']}"
)
))
bot.answer_inline_query(query.id, results)
if __name__ == '__main__':
bot.polling(none_stop=True)

View File

@ -19,4 +19,36 @@ get_system_info() {
IP=$(echo "$IP_API_DATA" | jq -r '.ip')
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2 + $4 "%"}')
RAM=$(free -m | awk 'NR==2{printf "%.2f%%", $3*100/$2 }')
}
}
version_greater_equal() {
IFS='.' read -r -a local_version_parts <<< "$1"
IFS='.' read -r -a latest_version_parts <<< "$2"
for ((i=0; i<${#local_version_parts[@]}; i++)); do
if [[ -z ${latest_version_parts[i]} ]]; then
latest_version_parts[i]=0
fi
if ((10#${local_version_parts[i]} > 10#${latest_version_parts[i]})); then
return 0
elif ((10#${local_version_parts[i]} < 10#${latest_version_parts[i]})); then
return 1
fi
done
return 0
}
check_version() {
local_version=$(cat /etc/hysteria/VERSION)
latest_version=$(curl -s https://raw.githubusercontent.com/ReturnFI/Hysteria2/Dev/VERSION)
if version_greater_equal "$local_version" "$latest_version"; then
echo -e "Panel Version: ${cyan}$local_version${NC}"
else
echo -e "Panel Version: ${cyan}$local_version${NC}"
echo -e "Latest Version: ${cyan}$latest_version${NC}"
echo -e "${red}Please update your panel.${NC}"
fi
}

79
menu.sh
View File

@ -28,7 +28,11 @@ hysteria2_add_user_handler() {
read -p "Enter the username: " username
if [[ "$username" =~ ^[a-zA-Z0-9]+$ ]]; then
break
if python3 $CLI_PATH get-user --username "$username" > /dev/null 2>&1; then
echo -e "${red}Error:${NC} Username already exists. Please choose another username."
else
break
fi
else
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
fi
@ -150,7 +154,7 @@ hysteria2_get_user_handler() {
done
# Run the command and suppress error output
if ! python3 "$CLI_PATH" get-user --username "$username" > /dev/null 2>&1; then
if ! python3 "$CLI_PATH" get-user --username "$username" 2>/dev/null; then
echo -e "${red}Error:${NC} User '$username' not found."
return 1
fi
@ -196,7 +200,7 @@ hysteria2_show_user_uri_handler() {
echo -e "${red}Error:${NC} Username can only contain letters and numbers."
fi
done
python3 $CLI_PATH show-user-uri --username "$username"
python3 $CLI_PATH show-user-uri --username "$username" -a -qr
}
hysteria2_change_port_handler() {
@ -238,6 +242,56 @@ warp_configure_handler() {
fi
}
telegram_bot_handler() {
while true; do
echo -e "${cyan}1.${NC} Start Telegram bot service"
echo -e "${red}2.${NC} Stop Telegram bot service"
echo "0. Back"
read -p "Choose an option: " option
case $option in
1)
if systemctl is-active --quiet hysteria-bot.service; then
echo "The hysteria-bot.service is already active."
else
while true; do
read -p "Enter the Telegram bot token: " token
if [ -z "$token" ]; then
echo "Token cannot be empty. Please try again."
else
break
fi
done
while true; do
read -p "Enter the admin IDs (comma-separated): " admin_ids
if [ -z "$admin_ids" ]; then
echo "Admin IDs cannot be empty. Please try again."
else
break
fi
done
python3 $CLI_PATH telegram -a start -t "$token" -aid "$admin_ids"
fi
;;
2)
if ! systemctl is-active --quiet hysteria-bot.service; then
echo "The hysteria-bot.service is already inactive."
else
python3 $CLI_PATH telegram -a stop
fi
;;
0)
break
;;
*)
echo "Invalid option. Please try again."
;;
esac
done
}
# Function to display the main menu
display_main_menu() {
clear
@ -250,6 +304,9 @@ display_main_menu() {
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
check_version
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
echo -e "${yellow} ☼ Main Menu ☼ ${NC}"
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
@ -293,7 +350,7 @@ display_hysteria2_menu() {
echo -e "${cyan}[3] ${NC}↝ Edit User"
echo -e "${cyan}[4] ${NC}↝ Remove User"
echo -e "${cyan}[5] ${NC}↝ Get User"
echo -e "${cyan}[6] ${NC}↝ List Users (WIP)"
echo -e "${cyan}[6] ${NC}↝ List Users"
echo -e "${cyan}[7] ${NC}↝ Check Traffic Status"
echo -e "${cyan}[8] ${NC}↝ Show User URI"
@ -339,9 +396,10 @@ display_advance_menu() {
echo -e "${green}[2] ${NC}↝ Install WARP"
echo -e "${cyan}[3] ${NC}↝ Configure WARP"
echo -e "${red}[4] ${NC}↝ Uninstall WARP"
echo -e "${cyan}[5] ${NC}Change Port Hysteria2"
echo -e "${cyan}[6] ${NC}Update Core Hysteria2"
echo -e "${red}[7] ${NC}↝ Uninstall Hysteria2"
echo -e "${green}[5] ${NC}Telegram Bot"
echo -e "${cyan}[6] ${NC}Change Port Hysteria2"
echo -e "${cyan}[7] ${NC}↝ Update Core Hysteria2"
echo -e "${red}[8] ${NC}↝ Uninstall Hysteria2"
echo -e "${red}[0] ${NC}↝ Back to Main Menu"
echo -e "${LPurple}◇──────────────────────────────────────────────────────────────────────◇${NC}"
echo -ne "${yellow}➜ Enter your option: ${NC}"
@ -359,9 +417,10 @@ advance_menu() {
2) python3 $CLI_PATH install-warp ;;
3) warp_configure_handler ;;
4) python3 $CLI_PATH uninstall-warp ;;
5) hysteria2_change_port_handler ;;
6) python3 $CLI_PATH update-hysteria2 ;;
7) python3 $CLI_PATH uninstall-hysteria2 ;;
5) telegram_bot_handler ;;
6) hysteria2_change_port_handler ;;
7) python3 $CLI_PATH update-hysteria2 ;;
8) python3 $CLI_PATH uninstall-hysteria2 ;;
0) return ;;
*) echo "Invalid option. Please try again." ;;
esac

106
modify.py
View File

@ -1,106 +0,0 @@
#!/usr/bin/env python3
import json
import os
import subprocess
from datetime import datetime
users_file_path = '/etc/hysteria/users/users.json'
def colors():
green='\033[0;32m'
cyan='\033[0;36m'
red='\033[0;31m'
NC='\033[0m' # No Color
return green, cyan, red, NC
def load_users():
with open(users_file_path, 'r') as file:
return json.load(file)
def save_users(users):
with open(users_file_path, 'w') as file:
json.dump(users, file, indent=4)
def generate_password():
result = subprocess.run(['pwgen', '-s', '32', '1'], capture_output=True, text=True)
return result.stdout.strip()
def list_users(users):
green, cyan, red, NC = colors()
print(f"{green}List of Users{NC}\n")
for i, user in enumerate(users.keys(), start=1):
print(f"{cyan}{i}. {user}{NC}")
def edit_user(users):
green, cyan, red, NC = colors()
list_users(users)
try:
choice = int(input(f"{green}Enter the number of the user to edit:{NC} "))
username = list(users.keys())[choice - 1]
except (ValueError, IndexError):
print(f"{green}Invalid choice.{NC}")
return
print(f"{green}Editing user: {cyan}{username}{NC}")
change_password = input(f"Change password? (current: {users[username]['password']}) [y/N]: ").lower() == 'y'
if change_password:
new_password = generate_password()
users[username]['password'] = new_password
print(f"{green}New password: {cyan}{new_password}{NC}")
while True:
current_max_download_gb = users[username]['max_download_bytes'] // (1024 ** 3)
max_download_gb = input(f"Enter new max download bytes in GB (current: {current_max_download_gb} GB, press Enter to keep current): ")
if max_download_gb.strip() == '':
break
elif max_download_gb.isdigit():
users[username]['max_download_bytes'] = int(max_download_gb) * (1024 ** 3)
break
else:
print(f"{red}Invalid input. Please enter a valid number or press Enter to keep current.{NC}")
while True:
expiration_days = input(f"Enter new expiration days (current: {users[username]['expiration_days']}, press Enter to keep current): ")
if expiration_days.strip() == '':
break
elif expiration_days.isdigit():
users[username]['expiration_days'] = int(expiration_days)
break
else:
print(f"{red}Invalid input. Please enter a valid number or press Enter to keep current.{NC}")
blocked = input(f"Blocked? (current: {users[username]['blocked']}) [true/false]: ").lower()
if blocked:
users[username]['blocked'] = blocked == 'true'
change_date = input(f"Change account creation date to today? (current: {users[username]['account_creation_date']}) [y/N]: ").lower() == 'y'
if change_date:
users[username]['account_creation_date'] = datetime.today().strftime('%Y-%m-%d')
def main():
green, cyan, red, NC = colors()
if not os.path.exists(users_file_path):
print(f"{red}File {users_file_path} does not exist.{NC}")
return
users = load_users()
while True:
print(f"{green}1. Edit user{NC}")
print(f"{red}2. Exit{NC}")
choice = input(f"{NC}Enter your choice: {NC}")
if choice == "1":
edit_user(users)
save_users(users)
elif choice == "2":
print(f"{red}Exiting...{NC}")
break
else:
print(f"{NC}Invalid choice. Please try again.{NC}")
if __name__ == "__main__":
main()

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
pyTelegramBotAPI
qrcode
python-dotenv
requests

53
upgrade.sh Normal file
View File

@ -0,0 +1,53 @@
#!/bin/bash
TEMP_DIR=$(mktemp -d)
FILES=(
"/etc/hysteria/ca.key"
"/etc/hysteria/ca.crt"
"/etc/hysteria/users.json"
"/etc/hysteria/traffic_data.json"
"/etc/hysteria/config.json"
"/etc/hysteria/core/scripts/telegrambot/.env"
)
echo "Backing up files to $TEMP_DIR"
for FILE in "${FILES[@]}"; do
cp "$FILE" "$TEMP_DIR"
done
echo "Removing /etc/hysteria directory"
rm -rf /etc/hysteria/
echo "Cloning Hysteria2 repository"
git clone https://github.com/ReturnFI/Hysteria2 /etc/hysteria
echo "Downloading geosite.dat and geoip.dat"
wget -O /etc/hysteria/geosite.dat https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geosite.dat >/dev/null 2>&1
wget -O /etc/hysteria/geoip.dat https://raw.githubusercontent.com/Chocolate4U/Iran-v2ray-rules/release/geoip.dat >/dev/null 2>&1
echo "Restoring backup files"
for FILE in "${FILES[@]}"; do
cp "$TEMP_DIR/$(basename $FILE)" "$FILE"
done
echo "Setting ownership and permissions for ca.key and ca.crt"
chown hysteria:hysteria /etc/hysteria/ca.key /etc/hysteria/ca.crt
chmod 640 /etc/hysteria/ca.key /etc/hysteria/ca.crt
echo "Restarting hysteria-server.service"
systemctl restart hysteria-server.service
echo "Setting execute permissions for user.sh and kick.sh"
chmod +x /etc/hysteria/core/scripts/hysteria2/user.sh
chmod +x /etc/hysteria/core/scripts/hysteria2/kick.sh
echo "Checking hysteria-server.service status"
if systemctl is-active --quiet hysteria-server.service; then
echo "Upgrade completed successfully"
else
echo "Upgrade failed: hysteria-server.service is not active"
fi
cd /etc/hysteria
chmod +x menu.sh
./menu.sh