11#!/usr/bin/env python3
22# mirror_with_sni.py
3- # Зеркалирование источников + SNI-фильтрация + создание подписок
3+ # Зеркалирование источников + создание компактных подписок
44# Требует: requests, PyGithub
55# ENV: MY_TOKEN (GitHub token)
66
1313import time
1414from requests .adapters import HTTPAdapter
1515from urllib3 .util .retry import Retry
16- from github import Github , Auth , GithubException
16+ from github import Github , Auth , GithubException , InputGitTreeElement
1717from datetime import datetime
1818import zoneinfo
1919
3030RETRIES = 2
3131REQUESTS_POOL = 10
3232GITHUB_DELAY = 1.5
33- MAX_KEYS_PER_FILE = 1000 # Максимум ключей в одном файле
3433
3534CHROME_UA = (
3635 "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
6867 "https://raw.githubusercontent.com/Argh94/V2RayAutoConfig/refs/heads/main/configs/Vless.txt" ,
6968 "https://raw.githubusercontent.com/Argh94/V2RayAutoConfig/refs/heads/main/configs/Hysteria2.txt" ,
7069 "https://raw.githubusercontent.com/mahdibland/V2RayAggregator/master/sub/sub_list.json" ,
71-
72- # ----- НОВЫЕ ИСТОЧНИКИ -----
7370 "https://raw.githubusercontent.com/NiREvil/vless/main/sub/SSTime" ,
7471 "https://raw.githubusercontent.com/ndsphonemy/proxy-sub/main/speed.txt" ,
7572 "https://raw.githubusercontent.com/Mahdi0024/ProxyCollector/master/sub/proxies.txt" ,
@@ -185,6 +182,50 @@ def upload_file_if_changed(local_path: str, remote_path: str):
185182 else :
186183 raise
187184
185+ def upload_large_file_via_git (local_path : str , remote_path : str ):
186+ """Загрузка больших файлов через Git API"""
187+ try :
188+ with open (local_path , "rb" ) as f :
189+ content = f .read ()
190+
191+ # Создаём blob
192+ time .sleep (GITHUB_DELAY )
193+ blob = repo .create_git_blob (content .decode ('utf-8' ), 'utf-8' )
194+
195+ # Получаем текущий коммит main
196+ time .sleep (GITHUB_DELAY )
197+ ref = repo .get_git_ref ('heads/main' )
198+ commit = repo .get_git_commit (ref .object .sha )
199+
200+ # Создаём tree element
201+ element = InputGitTreeElement (
202+ path = remote_path ,
203+ mode = '100644' ,
204+ type = 'blob' ,
205+ sha = blob .sha
206+ )
207+
208+ # Создаём новый tree
209+ time .sleep (GITHUB_DELAY )
210+ base_tree = repo .get_git_tree (commit .tree .sha )
211+ new_tree = repo .create_git_tree ([element ], base_tree )
212+
213+ # Создаём коммит
214+ time .sleep (GITHUB_DELAY )
215+ new_commit = repo .create_git_commit (
216+ f"Update { remote_path } | { now_moscow ()} " ,
217+ new_tree ,
218+ [commit ]
219+ )
220+
221+ # Обновляем референс
222+ time .sleep (GITHUB_DELAY )
223+ ref .edit (new_commit .sha )
224+
225+ print (f"{ remote_path } загружен через Git API" )
226+ except Exception as e :
227+ print (f"Ошибка загрузки { remote_path } : { e } " )
228+
188229def is_valid_proxy (line : str ) -> bool :
189230 """Проверка, является ли строка валидным прокси-ключом"""
190231 protocols = ['vless://' , 'vmess://' , 'trojan://' , 'ss://' ,
@@ -211,19 +252,14 @@ def create_filtered_file():
211252 f .write ("\n " .join (out ))
212253 return final_file
213254
214- def split_into_chunks (items , chunk_size ):
215- """Разбивает список на части"""
216- for i in range (0 , len (items ), chunk_size ):
217- yield items [i :i + chunk_size ]
218-
219255def create_subscriptions ():
220- """Создаёт подписки из всех собранных ключей """
256+ """Создаёт ТОЛЬКО 4 основных файла подписок """
221257 print ("\n " + "=" * 60 )
222258 print ("Создание подписок..." )
223259
224260 all_keys = []
225261
226- # Собираем все ключи из файлов 1.txt - N.txt
262+ # Собираем все ключи
227263 total = len (URLS )
228264 for i in range (1 , total + 1 ):
229265 p = os .path .join (LOCAL_DIR , f"{ i } .txt" )
@@ -241,45 +277,24 @@ def create_subscriptions():
241277 all_keys = list (dict .fromkeys (all_keys ))
242278 print (f"Всего уникальных ключей: { len (all_keys )} " )
243279
280+ # Создаём ТОЛЬКО 4 файла
244281 subscriptions = []
245282
246- # Разбиваем на части, если больше MAX_KEYS_PER_FILE
247- if len (all_keys ) > MAX_KEYS_PER_FILE :
248- chunks = list (split_into_chunks (all_keys , MAX_KEYS_PER_FILE ))
249- print (f"Файл слишком большой, разбиваем на { len (chunks )} частей" )
250-
251- # Создаём части (raw)
252- for idx , chunk in enumerate (chunks , 1 ):
253- filename = f"all_part{ idx } .txt"
254- filepath = os .path .join (SUBSCRIPTIONS_DIR , filename )
255- with open (filepath , "w" , encoding = "utf-8" ) as f :
256- f .write ("\n " .join (chunk ))
257- subscriptions .append ((filename , len (chunk )))
258-
259- # Создаём части (base64)
260- for idx , chunk in enumerate (chunks , 1 ):
261- filename = f"all_part{ idx } _base64.txt"
262- filepath = os .path .join (SUBSCRIPTIONS_DIR , filename )
263- with open (filepath , "w" , encoding = "utf-8" ) as f :
264- content = "\n " .join (chunk )
265- encoded = base64 .b64encode (content .encode ('utf-8' )).decode ('utf-8' )
266- f .write (encoded )
267- subscriptions .append ((filename , len (chunk )))
268- else :
269- # Если меньше лимита, создаём один файл
270- all_raw = os .path .join (SUBSCRIPTIONS_DIR , "all.txt" )
271- with open (all_raw , "w" , encoding = "utf-8" ) as f :
272- f .write ("\n " .join (all_keys ))
273- subscriptions .append (("all.txt" , len (all_keys )))
274-
275- all_b64 = os .path .join (SUBSCRIPTIONS_DIR , "all_base64.txt" )
276- with open (all_b64 , "w" , encoding = "utf-8" ) as f :
277- content = "\n " .join (all_keys )
278- encoded = base64 .b64encode (content .encode ('utf-8' )).decode ('utf-8' )
279- f .write (encoded )
280- subscriptions .append (("all_base64.txt" , len (all_keys )))
283+ # 1. Все ключи (raw) - через Git API
284+ all_raw = os .path .join (SUBSCRIPTIONS_DIR , "all.txt" )
285+ with open (all_raw , "w" , encoding = "utf-8" ) as f :
286+ f .write ("\n " .join (all_keys ))
287+ subscriptions .append (("all.txt" , len (all_keys ), True ))
288+
289+ # 2. Все ключи (base64) - через Git API
290+ all_b64 = os .path .join (SUBSCRIPTIONS_DIR , "all_base64.txt" )
291+ with open (all_b64 , "w" , encoding = "utf-8" ) as f :
292+ content = "\n " .join (all_keys )
293+ encoded = base64 .b64encode (content .encode ('utf-8' )).decode ('utf-8' )
294+ f .write (encoded )
295+ subscriptions .append (("all_base64.txt" , len (all_keys ), True ))
281296
282- # SNI-фильтрованные (из файла N+1.txt)
297+ # 3. SNI-фильтрованные
283298 sni_file = os .path .join (LOCAL_DIR , f"{ total + 1 } .txt" )
284299 if os .path .exists (sni_file ):
285300 with open (sni_file , "r" , encoding = "utf-8" ) as f :
@@ -290,23 +305,28 @@ def create_subscriptions():
290305 sni_raw = os .path .join (SUBSCRIPTIONS_DIR , "sni_filtered.txt" )
291306 with open (sni_raw , "w" , encoding = "utf-8" ) as f :
292307 f .write ("\n " .join (sni_keys ))
293- subscriptions .append (("sni_filtered.txt" , len (sni_keys )))
308+ subscriptions .append (("sni_filtered.txt" , len (sni_keys ), False ))
294309
295310 # SNI base64
296311 sni_b64 = os .path .join (SUBSCRIPTIONS_DIR , "sni_filtered_base64.txt" )
297312 with open (sni_b64 , "w" , encoding = "utf-8" ) as f :
298313 content = "\n " .join (sni_keys )
299314 encoded = base64 .b64encode (content .encode ('utf-8' )).decode ('utf-8' )
300315 f .write (encoded )
301- subscriptions .append (("sni_filtered_base64.txt" , len (sni_keys )))
316+ subscriptions .append (("sni_filtered_base64.txt" , len (sni_keys ), False ))
302317
303318 # Загружаем на GitHub
304319 print ("\n Загрузка подписок на GitHub..." )
305- for filename , count in subscriptions :
320+ for filename , count , use_git_api in subscriptions :
306321 try :
307322 local_path = os .path .join (SUBSCRIPTIONS_DIR , filename )
308323 remote_path = f"{ SUBSCRIPTIONS_DIR } /{ filename } "
309- upload_file_if_changed (local_path , remote_path )
324+
325+ if use_git_api :
326+ upload_large_file_via_git (local_path , remote_path )
327+ else :
328+ upload_file_if_changed (local_path , remote_path )
329+
310330 except Exception as e :
311331 print (f"Ошибка загрузки { filename } : { e } " )
312332
@@ -315,11 +335,11 @@ def create_subscriptions():
315335 print ("✅ Подписки созданы!" )
316336 print ("=" * 60 )
317337 print ("\n Статистика:" )
318- for filename , count in subscriptions :
338+ for filename , count , _ in subscriptions :
319339 print (f" { filename } : { count } ключей" )
320340
321341 print (f"\n 📌 Ссылки на подписки:" )
322- for filename , _ in subscriptions :
342+ for filename , _ , _ in subscriptions :
323343 print (f"https://raw.githubusercontent.com/{ REPO_NAME } /main/{ SUBSCRIPTIONS_DIR } /{ filename } " )
324344 print ("=" * 60 + "\n " )
325345
0 commit comments