├── .gitignore
├── Dockerfile
├── README.md
├── bot.py
├── config.yaml
├── db
└── cert
│ └── cetrificates.here
├── docker-compose.yaml
├── link_manager.py
├── main.py
├── requirements.txt
├── scheduler.py
├── scripts
├── base.json
├── count.py
├── generate_urls.py
├── get_username.py
├── nginx.conf
└── traffic.sh
├── sqlitedb.py
├── subscribe.py
├── traffic_manager.py
└── utils.py
/.gitignore:
--------------------------------------------------------------------------------
1 | db/*
2 | config.yaml
3 | *.code-workspace
4 | *.pyc
5 | *.db
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.9
2 | COPY requirements.txt .
3 | RUN pip3 install -r requirements.txt
4 | WORKDIR /workspace
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Xray-Bot
2 |
3 | ## Getting Started
4 |
5 | These instructions will guide you through the setup process of Xray-Bot.
6 |
7 | ### Prerequisites
8 |
9 | ### Steps
10 | 0. Install docker by:
11 | `curl -fsSL get.docker.com -o get-docker.sh && sh get-docker.sh`
12 |
13 | 1. Generate 3000 UUIDs by running the following command:
14 | `cd scripts & python3 generate_urls.py`
15 | 2. customize your `config.yaml` file.
16 | 3. Run the Xray-Bot container using docker-compose:
17 | `docker-compose up --build -d`
18 |
19 | Note: Make sure your firewall is set up properly and your port (default 443) is open.
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import logging
4 | import requests
5 | from telegram import __version__ as TG_VER
6 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, WebAppInfo
7 | from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes, MessageHandler, filters
8 | import yaml
9 | from utils import load_config
10 | import argparse
11 | import datetime
12 | import random
13 | import asyncio
14 |
15 | from link_manager import LinkManager
16 | from utils import get_outline_key
17 |
18 |
19 | # Enable logging
20 | logging.basicConfig(
21 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
22 | logger = logging.getLogger(__name__)
23 |
24 |
25 | async def instructions(update: Update, context: ContextTypes.DEFAULT_TYPE):
26 | await context.bot.send_message(chat_id=update.effective_chat.id, text="""Download the applications below and import the generated link.
27 | Android: V2rayNG
28 | IOS: wingsX
29 | Windows: v2rayN
30 | MacOS: V2RayXS """, parse_mode="HTML")
31 |
32 |
33 | async def gen_link(update: Update, context: ContextTypes.DEFAULT_TYPE, type):
34 | maintenance = await is_maintenance(update, context)
35 | if maintenance:
36 | return
37 |
38 | if not update.effective_user.username:
39 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Please set a username for your telegram account.")
40 | return
41 |
42 | if not await is_member(update, context, send_thank_you=False):
43 | return
44 |
45 | if type == 'reality':
46 | ret, urls = link_manager.get_link_reality(
47 | str(update.effective_user.id), str(update.effective_user.username))
48 | logger.info(f'REALITY, Gave link to @{update.effective_user.username}')
49 |
50 | if ret:
51 | for (link_type, url) in urls.items():
52 | if url:
53 | print(url)
54 | await context.bot.send_message(chat_id=update.effective_chat.id, text=link_type)
55 |
56 | text = "`" + '\n'.join(url) + "`"
57 | await context.bot.send_message(chat_id=update.effective_chat.id, text=text, parse_mode="MarkdownV2")
58 | else:
59 | await context.bot.send_message(chat_id=update.effective_chat.id, text=urls[0])
60 |
61 |
62 | async def is_maintenance(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
63 | config = load_config(config_path)
64 | if config['maintenance']:
65 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Sorry. The bot is under maintanance right now.")
66 | await context.bot.send_message(chat_id=update.effective_chat.id, text=".در حال ارتقا ربات هستیم. ربات بصورت موقتی غیرفعال است.")
67 | # await context.bot.send_message(chat_id=update.effective_chat.id, text="در حال حاضر سرور پر شده است. ")
68 |
69 | return config['maintenance']
70 |
71 |
72 | async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
73 | """Sends a message with three inline buttons attached."""
74 | maintenance = await is_maintenance(update, context)
75 | if maintenance:
76 | return
77 |
78 | keyboard = [[ InlineKeyboardButton("REALITY", callback_data="gen_reality")],
79 | [InlineKeyboardButton("گزارش استفاده", callback_data="usage")],
80 | [InlineKeyboardButton(
81 | "Outline VPN گرفتن لینک", callback_data="gen_outline")],
82 | [InlineKeyboardButton(
83 | "رو چه نرم افزاری کار میکنه؟", callback_data="instructions")],
84 | [InlineKeyboardButton(
85 | "می خواهم کمک کنم", url="https://t.me/+0l8_7FaM-UkyNzIx", callback_data="contribute")],
86 | [InlineKeyboardButton(
87 | "لینک کانال", url="https://t.me/WomanLifeFreedomVPN", callback_data="contact_support")],
88 | [InlineKeyboardButton("تست سرعت", web_app=WebAppInfo(url="https://pcmag.speedtestcustom.com"))]]
89 | reply_markup = InlineKeyboardMarkup(keyboard)
90 |
91 | await update.message.reply_text("Please choose one of the following options:", reply_markup=reply_markup)
92 |
93 |
94 | async def gen_report(update: Update, context: ContextTypes.DEFAULT_TYPE):
95 | usage = link_manager.get_usage(update.effective_user.id)
96 | await context.bot.send_message(chat_id=update.effective_chat.id, text=usage)
97 |
98 | async def get_sub(update: Update, context: ContextTypes.DEFAULT_TYPE):
99 | maintenance = await is_maintenance(update, context)
100 | if maintenance:
101 | return
102 |
103 | if not await is_member(update, context):
104 | return
105 |
106 | if not update.effective_user.username:
107 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Please set a username for your telegram account.")
108 | return
109 |
110 | logging.info(
111 | f'Subscription, Gave sub to @{update.effective_user.username}')
112 | await context.bot.send_message(chat_id=update.effective_chat.id, text="لینک سابسکریپشن اگه اولی نشد دومی رو بزنید. هر دو یکین نیاز نیست هر دو رو بزنید.")
113 | link = '`'+link_manager.get_sub(str(update.effective_user.id),str(update.effective_user.username))[0]+'`'
114 | await context.bot.send_message(chat_id=update.effective_chat.id, text=link, parse_mode="MarkdownV2")
115 | link = '`'+link_manager.get_sub(str(update.effective_user.id),str(update.effective_user.username))[1]+'`'
116 | await context.bot.send_message(chat_id=update.effective_chat.id, text=link, parse_mode="MarkdownV2")
117 |
118 |
119 | async def gen_outline(update: Update, context: ContextTypes.DEFAULT_TYPE):
120 | if not await is_member(update, context):
121 | return
122 | maintenance = await is_maintenance(update, context)
123 | if maintenance:
124 | return
125 |
126 | await context.bot.send_message(chat_id=update.effective_chat.id, text="درحال برقراری ارتباط با سرور. لطفا چند دقیقه صبر کنید...")
127 | email = str(update.effective_user.id)+"@telegram.ca"
128 | logging.info(f'OUTLINE, Gave link to @{update.effective_user.username}')
129 |
130 | status, url = await get_outline_key(email)
131 | if status == 200:
132 | text = "`" + url + "`"
133 | await context.bot.send_message(chat_id=update.effective_chat.id, text=text, parse_mode="MarkdownV2")
134 | await context.bot.send_message(chat_id=update.effective_chat.id, text="حجم 4 گیگابایت. انقضا یک هفته.")
135 | await context.bot.send_message(chat_id=update.effective_chat.id, text="برای اطلاعات بیشتر به https://instagram.com/getoutlinevpnkey مراجعه کنید.")
136 |
137 | elif status == 226:
138 | await context.bot.send_message(chat_id=update.effective_chat.id, text="شما قبلا درخواست داده اید. لطفا یک هفته از درخواست قبلی صبر کنید.")
139 |
140 | elif status == 408:
141 | await context.bot.send_message(chat_id=update.effective_chat.id, text="ارتباط با سرور اوتلاین برقرار نشد. احتمالا سرور در حال تعمیر میباشد. برای اطلاعات بیشتر به https://instagram.com/getoutlinevpnkey مراجه کنید.")
142 |
143 |
144 | async def button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
145 | """Parses the CallbackQuery and updates the message text."""
146 | query = update.callback_query
147 |
148 | # CallbackQueries need to be answered, even if no notification to the user is needed
149 | # Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
150 | await query.answer()
151 | if query.data == "instructions":
152 | await instructions(update, context)
153 | elif query.data == "gen_trojan":
154 | await gen_link(update, context, 'trojan')
155 | elif query.data == "gen_vless":
156 | await gen_link(update, context, 'vless')
157 | elif query.data == "gen_reality":
158 | await gen_link(update, context, 'reality')
159 | elif query.data == "gen_vmess":
160 | await gen_link(update, context, 'vmess')
161 | elif query.data == "get_sub":
162 | await get_sub(update, context)
163 | elif query.data == "usage":
164 | await gen_report(update, context)
165 | elif query.data == "gen_outline":
166 | await gen_outline(update, context)
167 |
168 |
169 | async def is_member(update: Update, context: ContextTypes.DEFAULT_TYPE, send_thank_you=True):
170 | config = load_config(config_path)
171 | # Check if the client is a member of the specified channel
172 | user_id = update.effective_user.id # update.message.from_user.id # Get the client's user ID
173 | try:
174 | chat_member = await context.bot.get_chat_member(chat_id=config["telegram_channel_id"], user_id=user_id)
175 | if chat_member.status in ["member", "creator"]:
176 | if send_thank_you:
177 | await context.bot.send_message(chat_id=update.effective_chat.id, text="Thank you for subscribing to our channel!")
178 | else:
179 | await context.bot.send_message(chat_id=update.effective_chat.id, text=f"Please subscribe to our channel {config['telegram_channel_id']}.")
180 | await context.bot.send_message(chat_id=update.effective_chat.id, text="لطفا ابتدا عضو کانال شوید. این وی پی ان محدود به اعضای کانال می باشد.")
181 | return chat_member.status in ["member", "creator"]
182 | except:
183 | logger.error(f"Error in checking the members of the channel. Please make sure robot is admin to your channel {config['telegram_channel_id']}")
184 | return False
185 |
186 | def main() -> None:
187 | # Parse the config file path from the command line arguments
188 | parser = argparse.ArgumentParser()
189 | parser.add_argument(
190 | '--config_path', help='Path to the config file', default='config.yaml')
191 | args = parser.parse_args()
192 | global config_path
193 | config_path = args.config_path
194 |
195 | # Load the config file
196 | config = load_config(config_path)
197 |
198 | global link_manager
199 | link_manager = LinkManager(config)
200 |
201 | """Run the bot."""
202 | # Create the Application and pass it your bot's token.
203 | application = Application.builder().token(
204 | config['telegram_bot_token']).build()
205 |
206 | application.add_handler(CommandHandler("start", start))
207 | application.add_handler(CallbackQueryHandler(button))
208 |
209 | # Run the bot until the user presses Ctrl-C
210 | application.run_polling()
211 |
212 |
213 | if __name__ == "__main__":
214 | main()
215 |
--------------------------------------------------------------------------------
/config.yaml:
--------------------------------------------------------------------------------
1 | telegram_bot_token: "5737744160:AAHWNc3rnHF_uoOIdo5sNff-eUGhdk-tsvE" #change it
2 | maintenance: false #turn it to true to force the bot to go into maintanance mode.
3 | db_path: "db/database.db"
4 | max_users: 3000
5 | traffic_limit: 8716192768 #5 GB
6 | outline_ip: "100.25.47.59"
7 | telegram_channel_id: "@WomanLifeFreedomVPN"
8 | domain: "135.181.35.207"
9 | ip: "135.181.35.207"
--------------------------------------------------------------------------------
/db/cert/cetrificates.here:
--------------------------------------------------------------------------------
1 | make a folder and put your certificates here.
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.9'
2 | services:
3 | core:
4 | image: teddysun/xray
5 | volumes:
6 | - $PWD/db/:/etc/xray
7 | - /dev/shm/:/dev/shm/
8 | restart: always
9 | network_mode: host
10 |
11 | scheduler:
12 | build: .
13 | volumes:
14 | - $PWD:/workspace
15 | restart: always
16 | network_mode: host
17 | command: python scheduler.py
18 |
19 | bot:
20 | build: .
21 | volumes:
22 | - $PWD:/workspace
23 | command: python bot.py
24 | restart: always
25 | network_mode: host
--------------------------------------------------------------------------------
/link_manager.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import logging
4 | import requests
5 | import json
6 | import base64
7 |
8 | from sqlitedb import SQLiteDB
9 | from utils import get_cf_ip, get_daily_number
10 |
11 | # Enable logging
12 | logging.basicConfig(
13 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
14 | logger = logging.getLogger(__name__)
15 |
16 |
17 | class LinkManager:
18 | def __init__(self, config):
19 | self.db = SQLiteDB(config['db_path'])
20 | self.config = config
21 | self.domain = config['domain']
22 |
23 | def register_id(self, telegram_id, telegram_username):
24 | if self.db.get_uuid(telegram_id):
25 | return True
26 | else:
27 | if int(self.db.count_users_with_telegram_id()) > self.config['max_users']:
28 | return False
29 |
30 | self.db.register_telegram_id(
31 | telegram_id=telegram_id, telegram_username=telegram_username, traffic_limit=self.config['traffic_limit'])
32 | return True
33 |
34 | def get_link_trojan(self, telegram_id, telegram_username):
35 | if not self.register_id(telegram_id, telegram_username):
36 | return False, ["Server is full."]
37 |
38 | user_uuid = self.db.get_uuid(telegram_id)
39 | if user_uuid is None:
40 | return False, ["Error connetion to server database."]
41 |
42 | # sni_id = telegram_id
43 | sni_id = get_daily_number()
44 |
45 | urls = {}
46 | urls['Trojan-WS'] = self.trojan_ws_cdn(user_uuid, sni_id)
47 | urls['Trojan-gRPC'] = self.trojan_grpc_cdn(user_uuid, sni_id)
48 | return True, urls
49 |
50 | def get_link_reality(self,telegram_id,telegram_username):
51 | if not self.register_id(telegram_id, telegram_username):
52 | return False, ["Server is full."]
53 |
54 | user_uuid = self.db.get_uuid(telegram_id)
55 | if user_uuid is None:
56 | return False, ["Error connetion to server database."]
57 |
58 | urls = {}
59 | sni_id = 0
60 | urls['VLESS-REALITY'] = [self.vless_xtls_reality(user_uuid, sni_id)]
61 | return True, urls
62 |
63 | def get_link_vless(self, telegram_id, telegram_username):
64 | if not self.register_id(telegram_id, telegram_username):
65 | return False, ["Server is full."]
66 |
67 | user_uuid = self.db.get_uuid(telegram_id)
68 | if user_uuid is None:
69 | return False, ["Error connetion to server database."]
70 |
71 | # sni_id = telegram_id
72 | sni_id = get_daily_number()
73 |
74 | urls = {}
75 | urls['VLESS-WS'] = self.vless_ws_cdn(user_uuid, sni_id)
76 | urls['VLESS-gRPC'] = self.vless_grpc_cdn(user_uuid, sni_id)
77 | return True, urls
78 |
79 | def get_link_vmess(self, telegram_id, telegram_username):
80 | if not self.register_id(telegram_id, telegram_username):
81 | return False, ["Server is full."]
82 |
83 | user_uuid = self.db.get_uuid(telegram_id)
84 | if user_uuid is None:
85 | return False, ["Error connetion to server database."]
86 |
87 | # sni_id = telegram_id
88 | sni_id = get_daily_number()
89 |
90 | urls = {}
91 | urls['VMess-WS'] = self.vmess_ws_cdn(user_uuid, sni_id)
92 | # urls['VMess-gRPC'] = self.vmess_grpc_cdn(user_uuid, sni_id)
93 | return True, urls
94 |
95 | def trojan_grpc_cdn(self, uuid, sni_id):
96 | url = f"trojan://{uuid}@{self.subdomain_preffix}{sni_id}.{self.domain}:443?security=tls&type=grpc&serviceName=trgrpc&mode=gun&alpn=h2&sni={self.subdomain_preffix}{sni_id}.{self.domain}#@WLF-TgRPC"
97 | urls = LinkManager.alternate_vless_trojan(url)
98 | return urls
99 |
100 | def trojan_ws_cdn(self, uuid, sni_id):
101 | url = f"trojan://{uuid}@{self.subdomain_preffix}{sni_id}.{self.domain}:443?security=tls&alpn=http%2F1.1&type=ws&path=%2Ftrojanws%3Fed%3D2048#@WLF-Tws"
102 | urls = LinkManager.alternate_vless_trojan(url)
103 | return urls
104 |
105 | def vless_grpc_cdn(self, uuid, sni_id):
106 | url = f"vless://{uuid}@{self.subdomain_preffix}{sni_id}.{self.domain}:443?encryption=none&security=tls&type=grpc&alpn=h2&sni={self.subdomain_preffix}{sni_id}.{self.domain}&serviceName=vlgrpc&mode=gun#@WLF-VLESSgRPC"
107 |
108 | urls = LinkManager.alternate_vless_trojan(url)
109 | return urls
110 |
111 | def vless_ws_cdn(self, uuid, sni_id):
112 | url = f"vless://{uuid}@{self.subdomain_preffix}{sni_id}.{self.domain}:443?encryption=none&alpn=http%2F1.1&security=tls&type=ws&path=%2Fvlws%3Fed%3D2048#@WLF-VLESSws"
113 | urls = LinkManager.alternate_vless_trojan(url)
114 | return urls
115 |
116 | def vless_xtls_reality(self, uuid, sni_id):
117 | import random
118 | import string
119 | spx = ''.join(random.choices(string.ascii_lowercase, k=7))+'/'+''.join(random.choices(string.ascii_lowercase, k=5))
120 | url = f"vless://{uuid}@{self.domain}:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.yahoo.com&fp=chrome&pbk=GAUjNf1wQnDm5ziCGqRmb3yVfU9bg_UPwZ2_QU4JWRU&sid=6bb85179e30d4fc2&spx=%2F{spx}&type=tcp&headerType=none#@WomanLifeFreedomVPN"
121 | return url
122 |
123 | def vmess_ws_cdn(self, uuid, sni_id):
124 | data = {"v": "2", "ps": "@WLF-VMessws", "add": f"{self.subdomain_preffix}{sni_id}.{self.domain}", "port": "443", "id": uuid, "aid": "0", "scy": "none",
125 | "net": "ws", "type": "none", "host": "", "path": "/vmws", "tls": "tls", "sni": "", "alpn": "http/1.1"}
126 |
127 | json_string = json.dumps(data)
128 | base64_encoded_string = base64.b64encode(json_string.encode()).decode()
129 | url = f"vmess://{base64_encoded_string}"
130 | urls = LinkManager.alternate_vmess(
131 | url, use_cf_ip=self.config['use_cf_ip'])
132 | return urls
133 |
134 | def vmess_grpc_cdn(self, uuid, sni_id):
135 | data = {"v": "2", "ps": "@WLF-VMessgRPC", "add": f"{self.subdomain_preffix}{sni_id}.{self.domain}", "port": "443", "id": uuid, "aid": "0", "scy": "none",
136 | "net": "grpc", "type": "http", "host": "", "path": "vmgrpc", "tls": "tls", "sni": f"{self.subdomain_preffix}{sni_id}.{self.domain}", "alpn": "h2"}
137 | json_string = json.dumps(data)
138 | base64_encoded_string = base64.b64encode(json_string.encode()).decode()
139 | url = f"vmess://{base64_encoded_string}"
140 | urls = LinkManager.alternate_vmess(
141 | url, use_cf_ip=self.config['use_cf_ip'])
142 | return urls
143 |
144 | @staticmethod
145 | def alternate_vless_trojan(url, use_cf_ip=True):
146 | import re
147 |
148 | # get address
149 | addr_match = re.search(r'@([^:]+):', url)
150 | if addr_match:
151 | address = addr_match.group(1)
152 | else:
153 | return
154 | # get host and add it
155 | host = None
156 | match_host = re.search(r'host=([^]+)', url)
157 | if match_host:
158 | host = match_host.group(1)
159 | else:
160 | host = address
161 | url_parts = url.split("#")
162 | url = url_parts[0] + f"&host={host}#" + url_parts[1]
163 |
164 | # get sni and add it
165 | match_sni = re.search(r'sni=([^]+)', url)
166 | if not match_sni:
167 | url_parts = url.split("#")
168 | url = url_parts[0] + f"&sni={host}#" + url_parts[1]
169 |
170 | cf_ips = [{"NAME": "iranserver.com", "IP": "iranserver.com", "TIME": None, "DESC": "2"}, {"NAME": "45.85.118.88", "IP": "45.85.118.88", "TIME": None, "DESC": "Irancell"}, {
171 | "NAME": "45.85.118.48", "IP": "45.85.118.48", "TIME": None, "DESC": "Irancell2"}, {"NAME": "104.16.219.215", "IP": "104.16.219.215", "TIME": None, "DESC": "Irancell3"}]
172 |
173 | if use_cf_ip:
174 | cf_good_ips = get_cf_ip()
175 | cf_ips += cf_good_ips
176 |
177 | urls = [url]
178 | for cf_ip in cf_ips:
179 | # if cf_ip["NAME"] in ["IRC"]:
180 | # continue
181 | if cf_ip["IP"]:
182 | new_url = url.replace(address, cf_ip["IP"], 1)
183 | url_parts = new_url.split("#")
184 | new_url += f'-{cf_ip["DESC"]}'
185 | urls.append(new_url)
186 | return urls
187 |
188 | @staticmethod
189 | def alternate_vmess(url, use_cf_ip=True):
190 | cf_ips = []
191 | cf_ips = [{"NAME": "iranserver.com",
192 | "IP": "iranserver.com", "TIME": None, "DESC": "2"}]
193 | if use_cf_ip:
194 | cf_good_ips = get_cf_ip()
195 | cf_ips += cf_good_ips
196 | urls = [url]
197 | url = url[8:]
198 | json_string = base64.b64decode(url).decode()
199 | data = json.loads(json_string)
200 | for cf_ip in cf_ips:
201 | if cf_ip["NAME"] not in ["MCI", "IRC"]:
202 | continue
203 |
204 | data_new = dict(data)
205 | data_new['sni'] = data['add']
206 | data_new['host'] = data['add']
207 |
208 | if cf_ip["IP"]:
209 | data_new['add'] = cf_ip["IP"]
210 | data_new['ps'] += f'-{cf_ip["DESC"]}'
211 | json_string = json.dumps(data_new)
212 | base64_encoded_string = base64.b64encode(
213 | json_string.encode()).decode()
214 | new_url = f'vmess://{base64_encoded_string}'
215 | urls.append(new_url)
216 | return urls
217 |
218 | def get_sub(self, telegram_id, telegram_username):
219 | """
220 | Returns the link to your subscription.
221 | """
222 | if not self.register_id(telegram_id, telegram_username):
223 | return ["Server is full.", "Server is full."]
224 |
225 | user_uuid = self.db.get_uuid(telegram_id)
226 | if user_uuid is None or user_uuid == "None":
227 | return [f"Bad request. Please Contact admin and provide this error: {user_uuid}", f"Bad request. Please Contact admin and provide this error: {user_uuid}"]
228 |
229 | url = [f"https://{self.config['subscription']['domain']}:{self.config['subscription']['https_port']}/subscriptions?token={user_uuid}",
230 | f"http://{self.config['subscription']['domain']}:{self.config['subscription']['http_port']}/subscriptions?token={user_uuid}"]
231 | return url
232 |
233 | def get_usage(self, telegram_id):
234 | usage = self.db.get_usage(telegram_id)
235 | return f"Used {round(usage/1024**3,2)}/{round(self.config['traffic_limit']/1024**3,2)} GB"
236 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os
4 | from utils import load_config
5 |
6 | config = load_config('config.yaml')
7 | byobu_command = f"byobu new-session -d -s mysession 'python3 bot.py' \\; new-window -t mysession 'python3 scheduler.py' \\; new-window -t mysession 'uvicorn subscribe:app --reload --host {config['subscription']['ip']} --ssl-keyfile db/cert/wlfvip.au1.store/sub-private.key --ssl-certfile /root/xray-bot/db/cert/wlfvip.au1.store/sub-cert.crt --port {config['subscription']['https_port']}'\\; new-window -t mysession 'uvicorn subscribe:app --reload --host {config['subscription']['ip']} --port {config['subscription']['http_port']}'"
8 |
9 | os.system(byobu_command)
10 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | tqdm
2 | schedule
3 | xtlsapi
4 | pyyaml
5 | requests
6 | python-telegram-bot
--------------------------------------------------------------------------------
/scheduler.py:
--------------------------------------------------------------------------------
1 | from utils import add_cloudflare_record, load_config
2 | import schedule
3 | import time
4 | import logging
5 | import datetime
6 |
7 | from utils import get_daily_number
8 | from traffic_manager import TrafficManager
9 | # Summary:
10 | # This code sets up a scheduler to run every day at midnight and 1 minute after midnight.
11 | # It calls the add_cloudflare_record function with config from config.yaml file and today_number or tomorrow_number as arguments.
12 | # The scheduler runs indefinitely until stopped with run_pending and sleep statements.
13 | # Logging is also enabled to log information during execution.
14 |
15 |
16 | # Enable logging
17 | logging.basicConfig(
18 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
19 | logger = logging.getLogger(__name__)
20 |
21 | def next_monday():
22 | today = datetime.datetime.today()
23 | next_monday = today + datetime.timedelta((7 - today.weekday()) % 7)
24 | delta = next_monday - today
25 | return delta.total_seconds()
26 |
27 | def update_cf(cf_config):
28 | add_cloudflare_record(config=cf_config, number_str=get_daily_number(delta=0))
29 | add_cloudflare_record(config=cf_config, number_str=get_daily_number(delta=1))
30 |
31 | config = load_config('config.yaml')
32 | logger.info("Scheduler started")
33 | today_number = get_daily_number()
34 | tomorrow_number = get_daily_number(delta=1)
35 | traffic_manager = TrafficManager(config)
36 | # schedule.every().day.at("00:00").do(update_cf, cf_config=config['cloudflare'])
37 |
38 | schedule.every(1).minutes.do(traffic_manager.run)
39 | schedule.every().monday.at("00:00").do(traffic_manager.reset_traffic)
40 |
41 | for job in schedule.jobs:
42 | logger.info(" - " + str(job))
43 |
44 | traffic_manager.run()
45 | # update_cf(config['cloudflare'])
46 |
47 | while True:
48 | schedule.run_pending()
49 | time.sleep(1)
50 |
--------------------------------------------------------------------------------
/scripts/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "log":{
3 | "access": "/etc/xray/access.log",
4 | "loglevel":"debug"
5 | },
6 | "inbounds":[
7 | {
8 | "listen": "0.0.0.0",
9 | "tag": "Vless-XTLS-reality",
10 | "port": 443,
11 | "protocol": "vless",
12 | "settings": {
13 | "clients": [
14 | {
15 | "id" : "general" ,
16 | "flow": "xtls-rprx-vision"
17 | }
18 | ],
19 | "decryption": "none"
20 | },
21 | "streamSettings": {
22 | "network": "tcp",
23 | "security": "reality",
24 | "realitySettings": {
25 | "show": true,
26 | "dest": "www.yahoo.com:443",
27 | "xver": 0,
28 | "maxTimeDiff": 0,
29 | "minClientVer": "1.8.0",
30 | "serverNames": [
31 | "www.yahoo.com",
32 | "www.varzesh3.com",
33 | ""
34 | ],
35 | "privateKey": "qM0mqldIHJIooZOs6YyrLFbLtb3bz-lLPRdJ5oa9CVs",
36 | "pubKey": "GAUjNf1wQnDm5ziCGqRmb3yVfU9bg_UPwZ2_QU4JWRU",
37 | "shortIds": [
38 | "6bb85179e30d4fc2"
39 | ]
40 | }
41 | }
42 | },
43 |
44 | {
45 | "listen":"127.0.0.1",
46 | "port":62789,
47 | "protocol":"dokodemo-door",
48 | "settings":{
49 | "address":"127.0.0.1"
50 | },
51 | "tag":"api",
52 | "sniffing":null
53 | }
54 | ],
55 | "routing":{
56 | "domainStrategy":"IPIfNonMatch",
57 | "rules":[
58 | {
59 | "inboundTag":[
60 | "api"
61 | ],
62 | "outboundTag":"api",
63 | "type":"field"
64 | },
65 | {
66 | "type":"field",
67 | "ip":[
68 | "geoip:ir",
69 | "geoip:private"
70 | ],
71 | "outboundTag":"block"
72 | },
73 | {
74 | "type":"field",
75 | "outboundTag":"block",
76 | "domain":[
77 | "geosite:category-ir",
78 | "geosite:private",
79 | "domain:intrack.ir",
80 | "domain:divar.ir",
81 | "domain:irancell.ir",
82 | "domain:yooz.ir",
83 | "domain:iran-cell.com",
84 | "domain:irancell.i-r",
85 | "domain:shaparak.ir",
86 | "domain:learnit.ir",
87 | "domain:yooz.ir",
88 | "domain:baadesaba.ir",
89 | "domain:webgozar.ir"
90 | ]
91 | }
92 | ]
93 | },
94 | "outbounds":[
95 | {
96 | "protocol":"freedom",
97 | "tag":"direct"
98 | },
99 | {
100 | "protocol":"blackhole",
101 | "tag":"block"
102 | }
103 | ],
104 | "api":{
105 | "services":[
106 | "HandlerService",
107 | "LoggerService",
108 | "StatsService"
109 | ],
110 | "tag":"api"
111 | },
112 | "stats":{
113 |
114 | },
115 | "policy":{
116 | "levels":{
117 | "0":{
118 | "statsUserUplink":true,
119 | "statsUserDownlink":true
120 | }
121 | },
122 | "system":{
123 | "statsInboundUplink":true,
124 | "statsInboundDownlink":true,
125 | "statsOutboundUplink":true,
126 | "statsOutboundDownlink":true
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/scripts/count.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import sys
3 | import os
4 | repo_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
5 | sys.path.append(repo_folder)
6 |
7 | from sqlitedb import SQLiteDB
8 |
9 |
10 | db = SQLiteDB(f'{repo_folder}/db/database.db')
11 | print(db.count_users_with_telegram_id())
--------------------------------------------------------------------------------
/scripts/generate_urls.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import json
3 | import os
4 | import sys
5 | from tqdm import tqdm
6 | import uuid
7 | repo_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8 | sys.path.append(repo_folder)
9 |
10 | from sqlitedb import SQLiteDB
11 |
12 | """
13 | This scripts generates a 10000 uuid connections for tags.
14 | """
15 | #############################################################################
16 | tags = ['Vless-XTLS-reality'] #,'vmess-grpc'
17 | quantity = 7000
18 | db_path = f'{repo_folder}/db/database.db'
19 | config_path = f"{repo_folder}/db/config.json"
20 | domain = 'cdir-*.wlfvip.shop'
21 | port = 443
22 | ##############################################################################
23 |
24 | # Open the file
25 | with open(f'{repo_folder}/scripts/base.json', 'r') as file:
26 | # Load the JSON data from the file
27 | data = json.load(file)
28 | db = SQLiteDB(db_path)
29 |
30 | for i in tqdm(range(quantity)):
31 | random_uuid = str(uuid.uuid4())
32 | client = {'email':f'{random_uuid}', 'id': f'{random_uuid}' ,'password': f'{random_uuid}',"flow": "xtls-rprx-vision", 'level': 0}
33 | added = False
34 | for inbound in data['inbounds']:
35 | if 'listen' in inbound.keys() and inbound['listen'] in tags or 'tag' in inbound.keys() and inbound['tag'] in tags:
36 | inbound['settings']['clients'].append(client)
37 | added =True
38 | if added:
39 | db.add_entry(None, None, 1, random_uuid, domain, port, 0, None)
40 |
41 | with open(config_path, "w") as outfile:
42 | json.dump(data, outfile, indent=4)
--------------------------------------------------------------------------------
/scripts/get_username.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import json
3 | import os
4 | import sys
5 | from tqdm import tqdm
6 | import uuid
7 | repo_folder = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
8 | sys.path.append(repo_folder)
9 |
10 | uuid = ''
11 | from sqlitedb import SQLiteDB
12 | db = SQLiteDB('/root/xray-bot/db/database.db')
13 |
14 | print(db.get_username(uuid))
--------------------------------------------------------------------------------
/scripts/nginx.conf:
--------------------------------------------------------------------------------
1 | # Restrict access to the website by IP or wrong domain name) and return 400
2 | server {
3 | listen unix:/dev/shm/h1.sock proxy_protocol default_server;
4 | listen unix:/dev/shm/h2c.sock http2 proxy_protocol default_server;
5 | set_real_ip_from unix:;
6 | real_ip_header proxy_protocol;
7 | server_name _;
8 | return 400;
9 | }
10 |
11 |
12 | # HTTP1 UDS listener
13 | server {
14 | listen unix:/dev/shm/h1.sock proxy_protocol; # HTTP/1.1 server monitor process and enable PROXY protocol reception
15 | set_real_ip_from unix:;
16 | real_ip_header proxy_protocol;
17 |
18 | server_name "~^cdir-.*\.wlfvip\.com";
19 |
20 | location / {
21 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # enable HSTS
22 | root /var/www/html; # Modify to the path of the WEB file stored by yourself (check the permissions)
23 | index index.html index2.html;
24 | }
25 |
26 |
27 | }
28 | # HTTP2 UDS listener
29 | server {
30 | listen unix:/dev/shm/h2c.sock http2 proxy_protocol ; # H2C server monitor process and enable PROXY protocol reception
31 | set_real_ip_from unix:;
32 | real_ip_header proxy_protocol;
33 |
34 | # server_name "~^cdir-.*\.wlfvip\.com|^wlfvip\.au1\.store";
35 | server_name "~^cdir-.*\.wlfvip\.com";
36 |
37 |
38 | # grpc settings
39 | grpc_read_timeout 1h;
40 | grpc_send_timeout 1h;
41 | grpc_set_header X-Real-IP $remote_addr;
42 |
43 | # Decoy website
44 | location / {
45 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # enable HSTS
46 | root /var/www/html; # Modify to the path of the WEB file stored by yourself (check the permissions)
47 | index index.html index2.html;
48 | }
49 |
50 | location /trgrpc { #corresponds to serviceName in trojan-grpc config of xray
51 | # POST returns 404 when negotiation fails
52 | if ($request_method != "POST") {
53 | return 404;
54 | }
55 | client_body_buffer_size 1m;
56 | client_body_timeout 1h;
57 | client_max_body_size 0;
58 | grpc_pass grpc://127.0.0.1:3001;
59 |
60 | }
61 |
62 | location /vlgrpc { # corresponds to serviceName in vless-grpc config of xray
63 | # return 404 if HTTP Method is not POST
64 | if ($request_method != "POST") {
65 | return 404;
66 | }
67 | client_body_buffer_size 1m;
68 | client_body_timeout 1h;
69 | client_max_body_size 0;
70 | grpc_pass grpc://127.0.0.1:3002;
71 |
72 | }
73 |
74 | location /vmgrpc { # corresponds to serviceName in vmess-grpc config of xray
75 | # return 404 if HTTP Method is not POST
76 | if ($request_method != "POST") {
77 | return 404;
78 | }
79 | client_body_buffer_size 1m;
80 | client_body_timeout 1h;
81 | client_max_body_size 0;
82 | grpc_pass grpc://127.0.0.1:3003;
83 |
84 | }
85 |
86 | location /ssgrpc { # corresponds to serviceName in shadowsocks-grpc config of xray
87 | # return 404 if HTTP Method is not POST
88 | if ($request_method != "POST") {
89 | return 404;
90 | }
91 | client_body_buffer_size 1m;
92 | client_body_timeout 1h;
93 | client_max_body_size 0;
94 | grpc_pass grpc://127.0.0.1:3004;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/scripts/traffic.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | _APISERVER=127.0.0.1:62789
4 | _XRAY=xray
5 |
6 | apidata () {
7 | local ARGS=
8 | if [[ $1 == "reset" ]]; then
9 | ARGS="-reset=true"
10 | fi
11 | $_XRAY api statsquery --server=$_APISERVER "${ARGS}" \
12 | | awk '{
13 | if (match($1, /"name":/)) {
14 | f=1; gsub(/^"|link"|,$/, "", $2);
15 | split($2, p, ">>>");
16 | printf "%s:%s->%s\t", p[1],p[2],p[4];
17 | }
18 | else if (match($1, /"value":/) && f){
19 | f = 0;
20 | gsub(/"/, "", $2);
21 | printf "%.0f\n", $2;
22 | }
23 | else if (match($0, /}/) && f) { f = 0; print 0; }
24 | }'
25 | }
26 |
27 | print_sum() {
28 | local DATA="$1"
29 | local PREFIX="$2"
30 | local SORTED=$(echo "$DATA" | grep "^${PREFIX}" | sort -r)
31 | local SUM=$(echo "$SORTED" | awk '
32 | /->up/{us+=$2}
33 | /->down/{ds+=$2}
34 | END{
35 | printf "SUM->up:\t%.0f\nSUM->down:\t%.0f\nSUM->TOTAL:\t%.0f\n", us, ds, us+ds;
36 | }')
37 | echo -e "${SORTED}\n${SUM}" \
38 | | numfmt --field=2 --suffix=B --to=iec \
39 | | column -t
40 | }
41 |
42 | DATA=$(apidata $1)
43 | echo "------------Inbound----------"
44 | print_sum "$DATA" "inbound"
45 | echo "-----------------------------"
46 | echo "------------Outbound----------"
47 | print_sum "$DATA" "outbound"
48 | echo "-----------------------------"
49 | echo
50 | echo "-------------User------------"
51 | print_sum "$DATA" "user"
52 | echo "-----------------------------"
--------------------------------------------------------------------------------
/sqlitedb.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | from datetime import datetime
3 |
4 | class SQLiteDB:
5 | """
6 | SQLiteDB is a class that allows you to interact with a SQLite database using Python.
7 | It creates a table called 'users' with the following columns:
8 | telegram_id, telegram_username, enabled, date_created, uuid, server, port, usage, traffic_limit
9 | """
10 |
11 | def __init__(self, db_file):
12 | """
13 | Initializes the SQLiteDB class and creates the 'users' table if it does not already exist.
14 |
15 | Args:
16 | db_file (str): The file path for the SQLite database.
17 | """
18 | self.conn = sqlite3.connect(db_file)
19 | c = self.conn.cursor()
20 | c.execute('''CREATE TABLE IF NOT EXISTS users (
21 | telegram_id INTEGER,
22 | telegram_username TEXT,
23 | enabled INTEGER,
24 | date_created DATETIME,
25 | uuid TEXT,
26 | server TEXT,
27 | port INTEGER,
28 | usage INTEGER,
29 | traffic_limit INTEGER)''')
30 | self.conn.commit()
31 |
32 | def count_users_with_telegram_id(self):
33 | """
34 | Counts the number of entries in the 'users' table where telegram_id is not NULL.
35 | """
36 | c = self.conn.cursor()
37 | c.execute("SELECT COUNT(*) FROM users WHERE telegram_id IS NOT NULL")
38 | count = c.fetchone()[0]
39 | return count
40 |
41 | def add_entry(self, telegram_id, telegram_username, enabled, uuid, server, port, usage, traffic_limit):
42 | """
43 | Adds a new entry to the 'users' table.
44 |
45 | Args:
46 | telegram_id (int): The Telegram ID of the user.
47 | telegram_username (str): The Telegram username of the user.
48 | enabled (int): A flag indicating whether the user is enabled (1) or disabled (0).
49 | uuid (str): A unique identifier for the user.
50 | server (str): The server that the user is connected to.
51 | port (int): The port that the user is connected to.
52 | usage (int): The usage of the user.
53 | traffic_limit (int): The traffic_limit of the user.
54 | """
55 | c = self.conn.cursor()
56 | current_date = datetime.now().strftime('%Y-%m-%d')
57 | c.execute("INSERT INTO users VALUES (?,?,?,?,?,?,?,?,?)", (telegram_id, telegram_username, enabled, current_date, uuid, server, port, usage, traffic_limit))
58 | self.conn.commit()
59 |
60 | def remove_entry(self, uuid):
61 | """
62 | Removes an entry from the 'users' table based on the provided uuid.
63 |
64 | Args:
65 | uuid (str): The unique identifier of the user to be removed.
66 | """
67 | c = self.conn.cursor()
68 | with self.conn:
69 | c.execute("DELETE FROM users WHERE uuid=?", (uuid,))
70 | self.conn.commit()
71 |
72 | def register_telegram_id(self, telegram_id, telegram_username, traffic_limit):
73 | """
74 | Registers a Telegram ID and username for a user, and updates the date_created and bandwidth_limit in the table.
75 |
76 | Args:
77 | telegram_id (int): The Telegram ID of the user.
78 | telegram_username (str): The Telegram username of the user.
79 | bandwidth_limit (int): The bandwidth limit of the user.
80 | """
81 |
82 | c = self.conn.cursor()
83 | current_date = datetime.now().strftime('%Y-%m-%d')
84 | with self.conn:
85 | c.execute("UPDATE users SET telegram_id = ?, telegram_username = ?, date_created = ?, traffic_limit = ? WHERE telegram_id is NULL LIMIT 1", (telegram_id, telegram_username, current_date, traffic_limit))
86 |
87 | def get_uuid(self, telegram_id):
88 | """
89 | Retrieves the uuid of a user based on the provided Telegram ID.
90 |
91 | Args:
92 | telegram_id (int): The Telegram ID of the user.
93 |
94 | Returns:
95 | str: The uuid of the user, or None if no user was found with the provided Telegram ID.
96 | """
97 | c = self.conn.cursor()
98 | c.execute("SELECT uuid FROM users WHERE telegram_id=?", (telegram_id,))
99 | result = c.fetchone()
100 | if result:
101 | return result[0]
102 | else:
103 | return None
104 |
105 | def get_username(self, uuid):
106 | """
107 | Retrieves the telegram_username of a user based on the provided uuid.
108 |
109 | Args:
110 | uuid (str): The uuid of the user.
111 |
112 | Returns:
113 | str: The telegram username of the user, or None if no user was found with the provided Telegram ID.
114 | """
115 | c = self.conn.cursor()
116 | c.execute("SELECT telegram_id, telegram_username FROM users WHERE uuid=?", (uuid,))
117 | result = c.fetchone()
118 | if result:
119 | return result
120 | else:
121 | return None,None
122 |
123 | def get_usage(self, telegram_id):
124 | """
125 | Retrieves the usage of a user based on the provided telegram_id.
126 |
127 | Args:
128 | telegram_id (str): The telegram_id of the user.
129 |
130 | Returns:
131 | int: The usage of the user, or None if no user was found with the provided Telegram ID.
132 | """
133 | c = self.conn.cursor()
134 | c.execute("SELECT usage FROM users WHERE telegram_id=?", (telegram_id,))
135 | result = c.fetchone()
136 | if result:
137 | return result[0]
138 | else:
139 | return 0
140 |
141 | def add_usage(self, uuid, amount):
142 | c = self.conn.cursor()
143 | with self.conn:
144 | c.execute("UPDATE users SET usage = usage + ? WHERE uuid = ?", (amount, uuid))
145 |
146 | def get_uuid_by_usage(self, traffic_limit):
147 | c = self.conn.cursor()
148 | c.execute("SELECT uuid FROM users WHERE usage > ?", (traffic_limit,))
149 | result = c.fetchall()
150 | return result
151 | def get_all_uuid(self):
152 | c = self.conn.cursor()
153 | c.execute("SELECT uuid FROM users")
154 | result = c.fetchall()
155 | return result
156 |
157 | def reset_usage(self):
158 | c = self.conn.cursor()
159 | with self.conn:
160 | c.execute("UPDATE users SET usage = 0")
161 |
162 | if __name__=="__main__":
163 | table = SQLiteDB('database_test.db')
164 | for i in range(10):
165 | table.add_entry(None, None, 1, str(i), 'server1', 443, 0, None)
166 |
167 | table.register_telegram_id(98765, 'new_username',9999)
168 | table.register_telegram_id(9000, 'new_username2',9999)
169 | table.register_telegram_id(10200, 'new_username3',9999)
170 |
171 |
172 | uuid = table.get_uuid(12345)
173 | print(uuid)
174 | uuid = table.get_uuid(9000)
175 | print(uuid)
176 | print('count:',table.count_users_with_telegram_id())
--------------------------------------------------------------------------------
/subscribe.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI, HTTPException
2 | from link_manager import LinkManager
3 | from utils import load_config
4 | import base64
5 | from fastapi.responses import PlainTextResponse
6 | from utils import get_daily_number, is_valid_uuid
7 | import random
8 | from sqlitedb import SQLiteDB
9 |
10 |
11 | # Create a FastAPI application object
12 | app = FastAPI()
13 |
14 | # Load the configuration from 'config.yaml' file
15 | config = load_config('config.yaml')
16 |
17 | # Create a LinkManager object using the loaded configuration
18 | link_manager = LinkManager(config)
19 |
20 | # A fake items database used for demonstration purposes
21 | db = SQLiteDB("db/database.db")
22 |
23 |
24 | # Define the API endpoint to retrieve subscription links
25 | @app.get("/subscriptions", response_class=PlainTextResponse)
26 | async def get_subscription(token: str = 0, s: int = 2):
27 | if not is_valid_uuid(token):
28 | return random.choice(["Dahanet service! :))", "Ey baba! Ajab giri kardima", "Khodaei chetor root mishe inkaro koni!", " Dada hichi in posht nist! talash nakn alaki!"])
29 | # Store the 's' parameter as 'sni' variable
30 |
31 | if db.get_username(token)[0] is None:
32 | raise HTTPException(status_code=404, detail="Link Jadid Begir")
33 | sni = get_daily_number()
34 |
35 | # Create an empty list to store links
36 | links = []
37 |
38 | # Get the links using the various methods provided by the LinkManager object
39 | links += link_manager.trojan_grpc_cdn(token, sni)
40 | links += link_manager.vless_grpc_cdn(token, sni)
41 | # links += link_manager.vmess_grpc_cdn(token, sni)
42 | links += link_manager.trojan_ws_cdn(token, sni)
43 | links += link_manager.vless_ws_cdn(token, sni)
44 | links += link_manager.vmess_ws_cdn(token, sni)
45 |
46 | # Encode the links into base64 format
47 | sub = base64.b64encode('\n'.join(links).encode("utf-8"))
48 |
49 | # Return the encoded links
50 | return sub
--------------------------------------------------------------------------------
/traffic_manager.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import logging
4 | import os
5 |
6 | from sqlitedb import SQLiteDB
7 | from utils import get_cf_ip, get_daily_number
8 | from xtlsapi import XrayClient, utils, exceptions
9 |
10 | # Enable logging
11 | logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
12 |
13 | class TrafficManager:
14 | def __init__(self, config):
15 | self.logger = logging.getLogger("TrafficManager")
16 |
17 | self.db = SQLiteDB(config['db_path'])
18 | self.config = config
19 | self.xray_client = XrayClient('127.0.0.1', 62789)
20 | self.removed = []
21 |
22 | def update_traffic(self):
23 | from collections import defaultdict
24 | usage = defaultdict(int)
25 | uuids = self.db.get_all_uuid()
26 | for uuid in uuids:
27 | uuid = uuid[0]
28 | dl = self.xray_client.get_client_download_traffic(uuid,reset=True)
29 | if dl is None:
30 | dl = 0
31 | upload = self.xray_client.get_client_upload_traffic(uuid,reset=True)
32 | if upload is None:
33 | upload = 0
34 | usage = dl + upload
35 | self.db.add_usage(uuid, usage)
36 | self.logger.info("Updated traffic.")
37 |
38 | def remove_users_higher(self):
39 | inbounds = ["Vless-XTLS-reality"]
40 | for inbound in inbounds:
41 | emails = self.db.get_uuid_by_usage(self.config['traffic_limit'])
42 | for email in emails:
43 | if email[0] in self.removed:
44 | continue
45 | try:
46 | self.removed.append(email[0])
47 | self.xray_client.remove_client(inbound,email=email[0])
48 | except Exception as e:
49 | self.logger.error(e)
50 | self.logger.info(f"{len(emails)} users used all traffic. {str([self.db.get_username(x)[1] for x in self.removed])}")
51 |
52 | def reset_traffic(self):
53 | try:
54 | self.db.reset_usage()
55 | self.xray_client.restart_logger()
56 | self.removed = []
57 | os.system('docker restart xray-bot')
58 | self.logger.info("Traffic was reset.")
59 |
60 |
61 | except Exception as e:
62 | self.logger.error(e)
63 |
64 | def run(self):
65 | self.update_traffic()
66 | self.remove_users_higher()
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import yaml
2 | import logging
3 | # Enable logging
4 | logging.basicConfig(
5 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO)
6 | logger = logging.getLogger(__name__)
7 |
8 |
9 | def load_config(path='config.yaml'):
10 | """Load the configuration from the specified yaml file.
11 |
12 | Args:
13 | path (str, optional): The path of the configuration file. Defaults to 'config.yaml'.
14 |
15 | Returns:
16 | dict: The configuration data.
17 | """
18 | with open(path, 'r') as stream:
19 | config = yaml.safe_load(stream)
20 | return config
21 |
22 | def get_cf_ip(url = "http://bot.sudoer.net/best.cf.iran"):
23 | """Get the list of nonblocked cloudflare internet service providers in Iran.
24 |
25 | Returns:
26 | list: A list of dictionaries containing provider name, IP, time and description.
27 | """
28 | from collections import defaultdict
29 | import requests
30 |
31 | data = []
32 | try:
33 | response = requests.get(url,timeout=1)
34 | data = response.text.strip().split('\n')
35 | except:
36 | pass
37 | net = defaultdict(str, {"MCI": "HamrahAval", "RTL": "Rightel", "AST": "Asiatek", "IRC": "Irancel",
38 | "SHT": "Shatel", "MKB": "Mokhaberat", "MBT": "Mobinnet", "ZTL": "Zitel", "PRS": "ParsOnline"})
39 | data_list = []
40 | for i in data:
41 | parts = i.split()
42 | if parts[0] not in net.keys():
43 | continue
44 | if len(parts) >= 3:
45 | data_dict = {'NAME': parts[0], 'IP': parts[1],
46 | 'Time': parts[2], "DESC": net[parts[0]]}
47 | else:
48 | data_dict = {'NAME': parts[0],
49 | 'IP': None, 'Time': None, "DESC": None}
50 | data_list.append(data_dict)
51 | return data_list
52 |
53 |
54 | async def get_outline_key(user_id):
55 | """
56 | This function retrieves the Outline key for a user.
57 |
58 | :param user_id: ID of the user.
59 | :type user_id: str
60 | :return: A tuple of HTTP status code and Outline key.
61 | :rtype: tuple (int, str) status code and the url
62 | """
63 | config = load_config('config.yaml')
64 | import requests
65 | url = f"http://{config['outline_ip']}/get_outline_key?user_id={user_id}"
66 | try:
67 | response = requests.get(url, timeout = 15)
68 | except:
69 | return 408, ""
70 |
71 | return response.status_code, str(response.content).split('"')[1]
72 |
73 |
74 | def add_cloudflare_record(config, number_str):
75 | """
76 | This function adds a record in Cloudflare.
77 |
78 | :param config: Configuration object containing the necessary authentication information.
79 | :type config: dict
80 | :param number_str: The string representation of a number to be added to the record name.
81 | :type number_str: str
82 | :return: None
83 | :rtype: None
84 | """
85 | import requests
86 | import time
87 |
88 | # code for adding the record goes here
89 | auth_email = config['auth_email']
90 | auth_key = config['auth_key']
91 | zone_id = config['zone_id']
92 | record_name = config['prefix_subdomain_name'] + number_str
93 | record_content = config['ip']
94 | record_type = "A"
95 |
96 | headers = {
97 | "X-Auth-Email": auth_email,
98 | "X-Auth-Key": auth_key,
99 | "Content-Type": "application/json"
100 | }
101 |
102 | data = {
103 | "name": record_name,
104 | "type": record_type,
105 | "content": record_content,
106 | "proxied": True
107 | }
108 |
109 | url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records"
110 |
111 | response = requests.get(url, headers=headers)
112 | records = response.json()["result"]
113 | record_exists = False
114 | for record in records:
115 | if record["name"].split('.')[0] == record_name and record["type"] == record_type:
116 | record_exists = True
117 | break
118 |
119 | if record_exists:
120 | logging.info(f"Record A {record_name} already exists.")
121 |
122 | return
123 |
124 | retry = 0
125 | record_added = False
126 |
127 | while not record_added and retry < 300:
128 | response = requests.post(url, headers=headers, json=data)
129 | if response.status_code == 200:
130 | logging.info(
131 | f"Record A {record_name} successfully added to Cloudflare")
132 | record_added = True
133 | else:
134 | logging.info(f"Failed to add record A {record_name} to Cloudflare")
135 | time.sleep(1)
136 | retry += 1
137 |
138 |
139 | def get_daily_number(delta=0):
140 | import datetime
141 | number = datetime.datetime.now().date() + datetime.timedelta(days=delta)
142 | return number.strftime("%m%d")
143 |
144 |
145 | if __name__ == "__main__":
146 | config = load_config('config.yaml')
147 | tomorrow = get_daily_number(1)
148 | today = get_daily_number(0)
149 | add_cloudflare_record(config['cloudflare'], today)
150 | add_cloudflare_record(config['cloudflare'], tomorrow)
151 |
152 |
153 | def is_valid_uuid(val):
154 | import uuid
155 | try:
156 | uuid.UUID(str(val))
157 | return True
158 | except ValueError:
159 | return False
--------------------------------------------------------------------------------