1212
1313from aiohttp import web
1414from aiohttp .web_middlewares import middleware
15- from urllib .parse import unquote , parse_qs , urlparse , urljoin
15+ from urllib .parse import unquote , parse_qs , urlparse , urljoin , quote
1616from dotenv import load_dotenv
1717import qrcode
1818from jinja2 import Environment , FileSystemLoader
@@ -127,9 +127,14 @@ class TemplateContext:
127127 expiration_date : str
128128 sublink_qrcode : str
129129 sub_link : str
130+ sub_link_encoded : str
130131 blocked : bool = False
131132 local_uris : List [NodeURI ] = field (default_factory = list )
132133 node_uris : List [NodeURI ] = field (default_factory = list )
134+ singbox_qrcode : Optional [str ] = None
135+ hiddify_qrcode : Optional [str ] = None
136+ streisand_qrcode : Optional [str ] = None
137+ nekobox_qrcode : Optional [str ] = None
133138
134139
135140class Utils :
@@ -398,14 +403,14 @@ def get_normal_subscription(self, username: str, user_agent: str) -> str:
398403 f"total={ user_info .max_download_bytes } ; "
399404 f"expire={ user_info .expiration_timestamp } \n "
400405 )
401- profile_lines = f"//profile-title: { username } -Hysteria2 🚀 \n //profile-update-interval: 1\n "
406+ profile_lines = f"//profile-title: { username } -Blitz ⚡ \n //profile-update-interval: 1\n "
402407 return profile_lines + subscription_info + "\n " .join (all_processed_uris )
403408
404409
405410class TemplateRenderer :
406411 def __init__ (self , template_dir : str , config : AppConfig ):
407412 self .env = Environment (loader = FileSystemLoader (template_dir ), autoescape = True )
408- self .html_template = self .env .get_template ('template .html' )
413+ self .html_template = self .env .get_template ('index .html' )
409414 self .config = config
410415
411416 def render (self , context : TemplateContext ) -> str :
@@ -430,6 +435,8 @@ def __init__(self):
430435 safe_subpath = self .validate_and_escape_subpath (self .config .subpath )
431436
432437 base_path = f'/{ safe_subpath } '
438+ self .app .router .add_get (f'{ base_path } /sub/normal/style.css' , self .handle_style )
439+ self .app .router .add_get (f'{ base_path } /sub/normal/script.js' , self .handle_script )
433440 self .app .router .add_get (f'{ base_path } /sub/normal/{{password_token}}' , self .handle )
434441 self .app .router .add_get (f'{ base_path } /robots.txt' , self .robots_handler )
435442 self .app .router .add_route ('*' , f'{ base_path } /{{tail:.*}}' , self .handle_404_subpath )
@@ -452,7 +459,7 @@ def _load_config(self) -> AppConfig:
452459 extra_config_path = '/etc/hysteria/extra.json'
453460 rate_limit = 100
454461 rate_limit_window = 60
455- template_dir = os .path .dirname (__file__ )
462+ template_dir = os .path .join ( os . path . dirname (__file__ ), 'template' )
456463
457464 sni = self ._load_sni_from_env (sni_file )
458465 return AppConfig (domain = domain , external_port = external_port ,
@@ -565,6 +572,7 @@ def _get_blocked_template_context(self, fake_uri: str, user_info: UserInfo) -> T
565572 expiration_date = user_info .expiration_date ,
566573 sublink_qrcode = "" ,
567574 sub_link = "#blocked" ,
575+ sub_link_encoded = "" ,
568576 blocked = True ,
569577 local_uris = [
570578 NodeURI (
@@ -603,8 +611,14 @@ async def _get_template_context(self, username: str, user_info: UserInfo) -> Tem
603611 print (f"Warning: Constructed base URL '{ base_url } ' might be invalid. Check domain and port config." )
604612
605613 sub_link = f"{ base_url } /{ self .config .subpath } /sub/normal/{ user_info .password } "
614+ sub_link_encoded = quote (sub_link , safe = '' )
606615 sublink_qrcode = Utils .generate_qrcode_base64 (sub_link )
607616
617+ singbox_qrcode = Utils .generate_qrcode_base64 (f"sing-box://import-remote-profile?url={ sub_link_encoded } " )
618+ hiddify_qrcode = Utils .generate_qrcode_base64 (f"hiddify://import/{ sub_link_encoded } " )
619+ streisand_qrcode = Utils .generate_qrcode_base64 (f"streisand://import/sub?url={ sub_link_encoded } " )
620+ nekobox_qrcode = Utils .generate_qrcode_base64 (f"nekobox://import?url={ sub_link_encoded } " )
621+
608622 local_uris = []
609623 node_uris = []
610624
@@ -626,9 +640,14 @@ async def _get_template_context(self, username: str, user_info: UserInfo) -> Tem
626640 expiration_date = user_info .expiration_date ,
627641 sublink_qrcode = sublink_qrcode ,
628642 sub_link = sub_link ,
643+ sub_link_encoded = sub_link_encoded ,
629644 blocked = user_info .blocked ,
630645 local_uris = local_uris ,
631- node_uris = node_uris
646+ node_uris = node_uris ,
647+ singbox_qrcode = singbox_qrcode ,
648+ hiddify_qrcode = hiddify_qrcode ,
649+ streisand_qrcode = streisand_qrcode ,
650+ nekobox_qrcode = nekobox_qrcode
632651 )
633652
634653 async def robots_handler (self , request : web .Request ) -> web .Response :
@@ -637,6 +656,12 @@ async def robots_handler(self, request: web.Request) -> web.Response:
637656 async def handle_404_subpath (self , request : web .Request ) -> web .Response :
638657 print (f"404 Not Found (within subpath, unhandled by specific routes): { request .path } " )
639658 return web .Response (status = 404 , text = "Not Found within Subpath" )
659+
660+ async def handle_style (self , request : web .Request ) -> web .Response :
661+ return web .FileResponse (os .path .join (self .config .template_dir , 'style.css' ))
662+
663+ async def handle_script (self , request : web .Request ) -> web .Response :
664+ return web .FileResponse (os .path .join (self .config .template_dir , 'script.js' ))
640665
641666 def run (self ):
642667 print (f"Starting Hysteria Normalsub server on { self .config .aiohttp_listen_address } :{ self .config .aiohttp_listen_port } " )
0 commit comments