From f7726eb7d3938883926cf03ed91267d597e4eb45 Mon Sep 17 00:00:00 2001 From: twilgate <170779666+twilgate@users.noreply.github.com> Date: Sat, 27 Jun 2026 02:39:28 +0400 Subject: [PATCH 1/2] feat(subscription): recognize InHive client for sing-box format InHive (https://inhive.ru) is a public sing-box-based VPN client (github.com/TwilgateLabs: inhive-android, inhive-windows; iOS soon), UA "InHive/ (; )", core supports hysteria2/TUIC/Reality. Add the InHive token to both sing-box UA regexes (hutils/flask.py is_singbox flag + panel/user/user.py endpoint selection) so its UA is served the sing-box config instead of falling through to v2ray/base64. Co-Authored-By: Claude --- hiddifypanel/hutils/flask.py | 2 +- hiddifypanel/panel/user/user.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hiddifypanel/hutils/flask.py b/hiddifypanel/hutils/flask.py index 541e29c59..ff58ac57c 100644 --- a/hiddifypanel/hutils/flask.py +++ b/hiddifypanel/hutils/flask.py @@ -79,7 +79,7 @@ def __parse_user_agent(ua: str) -> dict: res['os_version'] = uaa.os.version res['is_clash'] = re.match('^(Clash|Stash)', ua, re.IGNORECASE) and True res['is_clash_meta'] = re.match('^(Clash-verge|Clash-?Meta|Stash|NekoBox|NekoRay|Pharos|hiddify-desktop)', ua, re.IGNORECASE) and True - res['is_singbox'] = re.match('^(HiddifyNext|Dart|SFI|SFA)', ua, re.IGNORECASE) and True + res['is_singbox'] = re.match('^(HiddifyNext|Dart|SFI|SFA|InHive)', ua, re.IGNORECASE) and True res['is_hiddify'] = re.match('^(HiddifyNext)', ua, re.IGNORECASE) and True res['is_hiddify_prefere_xray'] = re.match('^(HiddifyNextX)', ua, re.IGNORECASE) and True res['is_streisand'] = re.match('^(Streisand)', ua, re.IGNORECASE) and True diff --git a/hiddifypanel/panel/user/user.py b/hiddifypanel/panel/user/user.py index e5797f782..8ae796be4 100644 --- a/hiddifypanel/panel/user/user.py +++ b/hiddifypanel/panel/user/user.py @@ -157,7 +157,7 @@ def get_proper_config(self): return None ua = request.user_agent.string - if g.user_agent['is_singbox'] or re.match('^(HiddifyNext|Dart|SFI|SFA)', ua, re.IGNORECASE): + if g.user_agent['is_singbox'] or re.match('^(HiddifyNext|Dart|SFI|SFA|InHive)', ua, re.IGNORECASE): return self.full_singbox_imp() if re.match('^(Clash-verge|Clash-?Meta|Stash|NekoBox|NekoRay|Pharos|hiddify-desktop)', ua, re.IGNORECASE): From 2e6b8e173c72fb4600188c07c62db7462974c67f Mon Sep 17 00:00:00 2001 From: twilgate <170779666+twilgate@users.noreply.github.com> Date: Fri, 10 Jul 2026 02:41:49 +0400 Subject: [PATCH 2/2] fix(subscription): route InHive to base64 links (is_v2ray) not sing-box The sing-box branch (to_singbox) drops kcp; the base64 links branch (to_link) carries the full protocol set including kcp. Move InHive from is_singbox to is_v2ray so its clients receive every configured protocol. Co-Authored-By: Claude --- hiddifypanel/hutils/flask.py | 6 +++--- hiddifypanel/panel/user/user.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hiddifypanel/hutils/flask.py b/hiddifypanel/hutils/flask.py index ff58ac57c..888f876c2 100644 --- a/hiddifypanel/hutils/flask.py +++ b/hiddifypanel/hutils/flask.py @@ -79,7 +79,7 @@ def __parse_user_agent(ua: str) -> dict: res['os_version'] = uaa.os.version res['is_clash'] = re.match('^(Clash|Stash)', ua, re.IGNORECASE) and True res['is_clash_meta'] = re.match('^(Clash-verge|Clash-?Meta|Stash|NekoBox|NekoRay|Pharos|hiddify-desktop)', ua, re.IGNORECASE) and True - res['is_singbox'] = re.match('^(HiddifyNext|Dart|SFI|SFA|InHive)', ua, re.IGNORECASE) and True + res['is_singbox'] = re.match('^(HiddifyNext|Dart|SFI|SFA)', ua, re.IGNORECASE) and True res['is_hiddify'] = re.match('^(HiddifyNext)', ua, re.IGNORECASE) and True res['is_hiddify_prefere_xray'] = re.match('^(HiddifyNextX)', ua, re.IGNORECASE) and True res['is_streisand'] = re.match('^(Streisand)', ua, re.IGNORECASE) and True @@ -103,10 +103,10 @@ def __parse_user_agent(ua: str) -> dict: res['singbox_version'] = [1, 13, 0] - res['is_v2ray'] = re.match('^(Hiddify|FoXray|Fair|v2rayNG|SagerNet|Shadowrocket|V2Box|Loon|Liberty)', ua, re.IGNORECASE) and True + res['is_v2ray'] = re.match('^(Hiddify|FoXray|Fair|v2rayNG|SagerNet|Shadowrocket|V2Box|Loon|Liberty|InHive)', ua, re.IGNORECASE) and True if res['os'] == 'Other': - if re.match('^(FoXray|Fair|Shadowrocket|V2Box|Loon|Liberty)', ua, re.IGNORECASE): + if re.match('^(FoXray|Fair|Shadowrocket|V2Box|Loon|Liberty|InHive)', ua, re.IGNORECASE): res['os'] = 'iOS' # res['os_version'] diff --git a/hiddifypanel/panel/user/user.py b/hiddifypanel/panel/user/user.py index 8ae796be4..3f2699cd7 100644 --- a/hiddifypanel/panel/user/user.py +++ b/hiddifypanel/panel/user/user.py @@ -157,7 +157,7 @@ def get_proper_config(self): return None ua = request.user_agent.string - if g.user_agent['is_singbox'] or re.match('^(HiddifyNext|Dart|SFI|SFA|InHive)', ua, re.IGNORECASE): + if g.user_agent['is_singbox'] or re.match('^(HiddifyNext|Dart|SFI|SFA)', ua, re.IGNORECASE): return self.full_singbox_imp() if re.match('^(Clash-verge|Clash-?Meta|Stash|NekoBox|NekoRay|Pharos|hiddify-desktop)', ua, re.IGNORECASE): @@ -172,7 +172,7 @@ def get_proper_config(self): elif g.user_agent.get('is_streisand'): return self.xray() - if re.match('^(Hiddify|FoXray|Fair|v2rayNG|SagerNet|Shadowrocket|V2Box|Loon|Liberty|Streisand)', ua, re.IGNORECASE): + if re.match('^(Hiddify|FoXray|Fair|v2rayNG|SagerNet|Shadowrocket|V2Box|Loon|Liberty|Streisand|InHive)', ua, re.IGNORECASE): return self.links_imp(base64=True) @route('/clash//proxies.yml')