Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c4530d4
fix: add confirmation for deep link profile addition to prevent SSRF
veto9292 May 31, 2026
8cbe574
refactor: use slang translations for add profile dialog
veto9292 May 31, 2026
01f46b4
Looking at the diff and the error message, I need to create a proper …
veto9292 May 31, 2026
d05adc6
refactor: rename addProfileFromLink key and add translations
veto9292 May 31, 2026
6127ce1
feat: add addProfileByDeepLinkWarning translation for ar, tr, zh-TW
veto9292 May 31, 2026
fc4631a
feat: add Psiphon override support and unified chain license flow
veto9292 Jun 9, 2026
15aa004
feat: add Psiphon translations and fix warp message trailing periods
veto9292 Jun 9, 2026
fb1dc8f
feat: add Psiphon license translations and update WARP messages
veto9292 Jun 9, 2026
46101cb
fix: add missing required text to fa WARP missingLicense translation
veto9292 Jun 9, 2026
9b0efc5
fix: correct Turkish translation for WARP license required
veto9292 Jun 9, 2026
c2f9ac7
feat: add file import option for profiles and adjust modal grid layout
veto9292 Jun 10, 2026
0f3c370
feat: add common.file translation to multiple languages
veto9292 Jun 10, 2026
54842a2
chore: regenerate protobuf bindings
veto9292 Jun 10, 2026
82ba583
chore: sync proto files from hiddify-core
veto9292 Jun 10, 2026
3f60de6
build: update dependencies and remove unused packages
veto9292 Jun 10, 2026
0242b0c
refactor: resolve LAN IP via core RPC in settings
veto9292 Jun 10, 2026
23f70ac
fix: resolve ChainTimelineHeader background visibility in light theme
veto9292 Jun 10, 2026
38a4f4d
fix: correct arrow icon direction in chain quick settings
veto9292 Jun 10, 2026
c2db7f0
fix(router): pass triggeredByDeepLink flag when auto-importing profiles
veto9292 Jun 10, 2026
14654bd
chore(makefile): optimize Linux docker builds and fix core download URL
veto9292 Jun 10, 2026
865ea73
build: update macOS plugin registrant after dependency removal
veto9292 Jun 10, 2026
c50ce39
aider: Translation helper
veto9292 Jun 10, 2026
ac4d26f
Fix AppImage launch crashes with launcher %u args and enable deep-lin…
veto9292 Jun 10, 2026
787dcf9
feat(linux): enhance desktop entry with localization and AppImage upd…
veto9292 Jun 10, 2026
0299c1c
pubspec.yaml: remove unused packages
veto9292 Jun 10, 2026
13c24d5
feat(settings): add trailing widget support to ValuePreferenceWidget …
veto9292 Jun 11, 2026
961f611
feat(settings): add new port configuration options and integrate with…
veto9292 Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
479 changes: 479 additions & 0 deletions .aider.conf.yml

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,7 @@ app.*.map.json

# Windows Self-Signed for msix
windows/sign.pfx
windows/sign.cer
windows/sign.cer
.aider.input.history
.aider.chat.history.md
.aider.tags.cache.v4
11 changes: 4 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ CORE_NAME=hiddify-lib
LIB_NAME=hiddify-core

ifeq ($(CHANNEL),prod)
CORE_URL=https://github.com/hiddify/hiddify-next-core/releases/download/v$(core.version)
CORE_URL=https://github.com/hiddify/hiddify-core/releases/download/v$(core.version)
else
CORE_URL=https://github.com/hiddify/hiddify-next-core/releases/download/draft
CORE_URL=https://github.com/hiddify/hiddify-core/releases/download/draft
endif

ifeq ($(CHANNEL),prod)
Expand Down Expand Up @@ -422,7 +422,7 @@ DOCKER_CMD := \
set -e; \
echo '** Copying source code to container...'; \
mkdir -p /app; \
cp -r /host/. /app/; \
tar -cf - --exclude='build' --exclude='.dart_tool' --exclude='dist' --exclude='dist_docker' --exclude='android' --exclude='windows' --exclude='ios' --exclude='macos' --exclude='.git' -C /host . | tar -xf - -C /app; \
cd /app; \
make linux-flutter-sync; \
make linux-prepare; \
Expand All @@ -439,10 +439,7 @@ DOCKER_CMD := \
exit 1; \
fi;

linux-docker-release:
@$(BLUE)Cleaning main project to reduce context size$(DONE)
flutter clean

linux-docker-release:
@$(BLUE)Building docker image (Cached)$(DONE)
docker build -t $(DOCKER_IMAGE_NAME) -f Dockerfile .

Expand Down
21 changes: 21 additions & 0 deletions SLANG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Custom Command: update-slang
Requirements: Master Flutter's `slang` framework. Perform all translations strictly based on project context (Hiddify VPN client).

Execute these phases sequentially when `update-slang` is triggered:

1. **Analyze:**
- Scan added files for hardcoded, deleted, or relocated keys.
- Run `dart run slang analyze --split --full` and analyze the generated `missing_translations_*.json` and `unused_translations_*.json` files inside `assets/translations/` to identify structural gaps.

2. **Propose & Pause (CRITICAL STOP):**
- Based on Phase 1 data, propose the updated English translation and JSON hierarchy changes.
- **Do NOT exit this phase or modify any other files until the user explicitly approves this English update.**

3. **MD3 Translation & Sync:**
- Once approved, generate translations for all other remaining languages based on the English reference.
- Strictly follow Material Design 3 guidelines (sentence case, concise, no periods) and update the respective translation files (keep technical networking terms in English).

4. **Refactor & Build:**
- Update the source code, replacing hardcoded strings with `t.path.to.key`.
- Run `dart run slang` to regenerate localization files.
- Delete the generated `missing_translations_*.json` and `unused_translations_*.json` files from `assets/translations/` to clean up the workspace.
98 changes: 98 additions & 0 deletions android/app/src/main/protos/v2/ezytel/ezytel.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
syntax = "proto3";

package ezytel;
option go_package = "github.com/hiddify/hiddify-core/v2/ezytel";
option java_package = "com.hiddify.core.api.v2.ezytel";

// Request the metadata card of a Telegram public channel
// (name, description preview, avatar, last-post timestamp, unread badge).
message ChannelInfoRequest {
// Channel id, e.g. "durov" — without "@" or "t.me/".
string channel_id = 1;
// Last post id the client has already read; used to compute the
// unread badge (newmsg). 0 means "everything is unread".
int64 last_read = 2;
// By default avatar_path in the response carries a
// "data:image/jpeg;base64,…" URI ready to drop into <img src>.
// Set this to true to opt out and receive the legacy
// "cache/<md5>.jpg" server-side file path instead.
bool disable_inline_images = 3;
}

message ChannelInfo {
string name = 1;
// Plain-text preview of the most recent post, or "فایل" if it is a media post.
string description = 2;
// By default this is a "data:image/jpeg;base64,…" URI ready for
// <img src>. When the request set disable_inline_images=true it is
// the legacy "cache/<md5>.jpg" file path relative to GetCacheDir().
// Empty if the avatar could not be fetched.
string avatar_path = 3;
// Unix epoch seconds of the most recent post.
int64 date = 4;
// Persian-calendar formatted "MM-DD HH:mm" string for the most recent post.
string date_str = 5;
// Unread badge, e.g. "+12" or "+99" (capped). Empty when caught up.
string newmsg = 6;
// Last post id observed on the channel page (server-side cookie replacement).
int64 last_post_id = 7;
// True when the channel page was reachable and parsed; false if served
// from stale cache (newmsg is then forced to "OFF").
bool ok = 8;
}

// Request a slab of HTML for the channel timeline.
message ChannelMessagesRequest {
string channel_id = 1;
// 0 = latest page; otherwise the "before" cursor returned by Telegram
// (the data-before attribute of messages_more_wrap).
int64 before = 2;
// By default every image URL in the returned html and the
// channel_avatar field is a "data:image/jpeg;base64,…" URI ready
// to render. Set this to true to opt out: html and channel_avatar
// will both carry the legacy "proxy.php?url=<hex>" placeholder
// form, requiring per-image ProxyImage calls.
bool disable_inline_images = 3;
}

message ChannelMessagesResponse {
// Pre-rendered HTML fragment ready to be injected into .main_block.
// Dates have been converted to the Persian calendar. By default
// <img src> and background-image:url(...) carry inline
// "data:image/jpeg;base64,…" payloads; with
// disable_inline_images=true they carry "proxy.php?url=<hex>"
// placeholders.
string html = 1;
// Channel avatar embedded in the header (only set when before == 0).
// "data:image/jpeg;base64,…" by default, "proxy.php?url=<hex>"
// placeholder when disable_inline_images=true.
string channel_avatar = 2;
// Last post id seen on the page (mirrors the lastread_<chid> cookie).
int64 last_post_id = 3;
}

// Fetch a Telegram-hosted image through the translate.goog domain front
// and cache it locally. Mirrors proxy.php?url=<hex>.
message ProxyImageRequest {
// Hex-encoded source URL without the "https://" prefix, exactly as the
// PHP version expected (bin2hex of the host+path).
string hex_url = 1;
}

message ProxyImageResponse {
bytes data = 1;
string content_type = 2;
// Local cache filename (md5(url) + ".jpg") for clients that prefer to
// reload from disk instead of holding the bytes.
string cache_name = 3;
}

// Parse the operator-supplied newline-separated channel list into the
// normalised ids the other RPCs accept.
message ParseChannelsRequest {
string raw = 1;
}

message ParseChannelsResponse {
repeated string channel_ids = 1;
}
16 changes: 16 additions & 0 deletions android/app/src/main/protos/v2/ezytel/ezytel_service.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
syntax = "proto3";

package ezytel;
option go_package = "github.com/hiddify/hiddify-core/v2/ezytel";
option java_package = "com.hiddify.core.api.v2.ezytel";
import "v2/ezytel/ezytel.proto";

// Ezytel exposes the Telegram public-channel viewer originally shipped as
// the ezytel.zip PHP app. Traffic to t.me is routed through Google's
// translate.goog domain front so it reaches users behind GFW-style filters.
service Ezytel {
rpc GetChannelInfo (ChannelInfoRequest) returns (ChannelInfo);
rpc GetChannelMessages (ChannelMessagesRequest) returns (ChannelMessagesResponse);
rpc ProxyImage (ProxyImageRequest) returns (ProxyImageResponse);
rpc ParseChannels (ParseChannelsRequest) returns (ParseChannelsResponse);
}
6 changes: 3 additions & 3 deletions android/app/src/main/protos/v2/hcore/hcore.proto
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,6 @@ message LogRequest {
message StopRequest{
}




message LANIPResponse {
string ip = 1;
}
3 changes: 2 additions & 1 deletion android/app/src/main/protos/v2/hcore/hcore_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ service Core {
rpc GetSystemProxyStatus (hcommon.Empty) returns (SystemProxyStatus);
rpc SetSystemProxyEnabled (SetSystemProxyEnabledRequest) returns (hcommon.Response);
rpc LogListener (LogRequest) returns (stream LogMessage);
rpc Close (CloseRequest) returns (hcommon.Empty);
rpc Close (CloseRequest) returns (hcommon.Empty);
rpc GetLANIP (hcommon.Empty) returns (LANIPResponse);
}


17 changes: 15 additions & 2 deletions assets/translations/ar.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"psiphon": "Psiphon",
"profile": "ملف تعريف",
"configuration": "تكوين",
"file": "ملف",
"interval": {
"day": {
"zero": "",
Expand Down Expand Up @@ -586,6 +587,10 @@
"title": "موافقة Cloudflare WARP",
"description(rich)": "Cloudflare WARP هو مزود VPN مجاني لـ WireGuard. بتفعيل هذا الخيار، فإنك توافق على ${tos(شروط الخدمة)} و ${privacy(سياسة الخصوصية)} الخاصة بـ Cloudflare WARP."
},
"psiphonLicense": {
"title": "اتفاقية رخصة Psiphon",
"description(rich)": "باستخدام Psiphon، أنت توافق على ${tos(شروط الخدمة)} وتقر بـ ${privacy(سياسة الخصوصية)}. يرجى قراءتها بعناية قبل المتابعة."
},
"newVersion": {
"title": "تحديث متاح",
"msg": "إصدار جديد من @:common.appTitle متاح. هل ترغب في التحديث الآن؟",
Expand Down Expand Up @@ -619,6 +624,10 @@
"title": "حذف القاعدة",
"msg": "هل أنت متأكد من رغبتك في حذف قاعدة \"$rulename\"؟"
}
},
"addProfileByDeepLinkWarning": {
"title": "إضافة ملف شخصي؟",
"message": "رابط خارجي من \"${host}\" يطلب إضافة ملف شخصي جديد. هل تريد استيراده؟"
}
},
"experimentalNotice": {
Expand Down Expand Up @@ -707,7 +716,11 @@
},
"warp": {
"missingLicense": "رخصة WARP مفقودة",
"missingLicenseMsg": "الملف الشخصي المحدد يستخدم ميزة WARP. لاستخدام هذه الميزة، يجب الموافقة على شروط رخصة WARP."
"missingLicenseMsg": "الملف الشخصي المحدد يستخدم ميزة WARP؛ لاستخدام هذه الميزة، يجب الموافقة على رخصة WARP"
},
"psiphon": {
"missingLicense": "رخصة Psiphon مطلوبة",
"missingLicenseMsg": "الملف الشخصي المحدد يستخدم ميزة Psiphon؛ لاستخدام هذه الميزة، يجب الموافقة على رخصة Psiphon"
}
}
}
}
17 changes: 15 additions & 2 deletions assets/translations/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"psiphon": "Psiphon",
"profile": "Profile",
"configuration": "Configuration",
"file": "File",
"interval": {
"day": {
"zero": "",
Expand Down Expand Up @@ -580,6 +581,10 @@
"title": "Cloudflare WARP consent",
"description(rich)": "Cloudflare WARP is a free WireGuard VPN provider. By enabling this option you are agreeing to the Cloudflare WARP's ${tos(Terms of service)} and ${privacy(Privacy policy)}."
},
"psiphonLicense": {
"title": "Psiphon License Agreement",
"description(rich)": "By using Psiphon, you agree to the ${tos(Terms of Service)} and acknowledge the ${privacy(Privacy Policy)}. Please read them carefully before proceeding."
},
"newVersion": {
"title": "Update available",
"msg": "A new version of @:common.appTitle is available. Would you like to update now?",
Expand Down Expand Up @@ -613,6 +618,10 @@
"title": "Delete rule",
"msg": "Are you sure you want to delete the \"$rulename\" rule?"
}
},
"addProfileByDeepLinkWarning": {
"title": "Add Profile?",
"message": "An external link from \"${host}\" is requesting to add a new profile. Do you want to import it?"
}
},
"experimentalNotice": {
Expand Down Expand Up @@ -700,8 +709,12 @@
"invalidConfig": "Invalid configuration"
},
"warp": {
"missingLicense": "Warp license",
"missingLicenseMsg": "The selected profile uses the WARP feature; to use this feature, the WARP license must be agreed to."
"missingLicense": "Warp license required",
"missingLicenseMsg": "The selected profile uses the WARP feature; to use this feature, the WARP license must be agreed to"
},
"psiphon": {
"missingLicense": "Psiphon license required",
"missingLicenseMsg": "The selected profile uses the Psiphon feature; to use this feature, the Psiphon license must be agreed to"
}
}
}
17 changes: 15 additions & 2 deletions assets/translations/es.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"psiphon": "Psiphon",
"profile": "Perfil",
"configuration": "Configuración",
"file": "Archivo",
"interval": {
"day": {
"zero": "",
Expand Down Expand Up @@ -580,6 +581,10 @@
"title": "Consentimiento de Cloudflare WARP",
"description(rich)": "Cloudflare WARP es un proveedor de VPN WireGuard gratuito. Al habilitar esta opción, aceptas los ${tos(Términos de servicio)} y la ${privacy(Política de privacidad)} de Cloudflare WARP."
},
"psiphonLicense": {
"title": "Acuerdo de licencia de Psiphon",
"description(rich)": "Al usar Psiphon, aceptas los ${tos(Términos de servicio)} y reconoces la ${privacy(Política de privacidad)}. Por favor, léelos detenidamente antes de continuar."
},
"newVersion": {
"title": "Actualización disponible",
"msg": "Hay disponible una nueva versión de @:common.appTitle. ¿Quieres actualizar ahora?",
Expand Down Expand Up @@ -613,6 +618,10 @@
"title": "Eliminar regla",
"msg": "¿Estás seguro de que quieres eliminar la regla \"$rulename\"?"
}
},
"addProfileByDeepLinkWarning": {
"title": "¿Añadir perfil?",
"message": "Un enlace externo de \"${host}\" está solicitando añadir un nuevo perfil. ¿Quieres importarlo?"
}
},
"experimentalNotice": {
Expand Down Expand Up @@ -701,7 +710,11 @@
},
"warp": {
"missingLicense": "Falta la licencia de WARP",
"missingLicenseMsg": "El perfil seleccionado utiliza la función WARP. Para usar esta función, debes aceptar la licencia de WARP."
"missingLicenseMsg": "El perfil seleccionado utiliza la función WARP; para utilizar esta función, debe aceptarse la licencia de WARP"
},
"psiphon": {
"missingLicense": "Se requiere licencia de Psiphon",
"missingLicenseMsg": "El perfil seleccionado utiliza la función Psiphon; para utilizar esta función, debe aceptarse la licencia de Psiphon"
}
}
}
}
19 changes: 16 additions & 3 deletions assets/translations/fa.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"psiphon": "سایفون",
"profile": "پروفایل",
"configuration": "پیکربندی",
"file": "فایل",
"interval": {
"day": {
"zero": "",
Expand Down Expand Up @@ -580,6 +581,10 @@
"title": "رضایت‌نامه Cloudflare WARP",
"description(rich)": "Cloudflare WARP یک ارائه‌دهنده رایگان WireGuard VPN است. با فعال کردن این گزینه، شما با ${tos(شرایط خدمات)} و ${privacy(سیاست حفظ حریم خصوصی)} Cloudflare WARP موافقت می‌کنید."
},
"psiphonLicense": {
"title": "قرارداد لایسنس Psiphon",
"description(rich)": "با استفاده از Psiphon، شما با ${tos(شرایط خدمات)} موافقت کرده و ${privacy(سیاست حفظ حریم خصوصی)} را تایید می‌کنید. لطفاً پیش از ادامه، آن‌ها را با دقت بخوانید."
},
"newVersion": {
"title": "به‌روزرسانی موجود است",
"msg": "نسخه جدیدی از @:common.appTitle در دسترس است. آیا مایل به به‌روزرسانی هستید؟",
Expand Down Expand Up @@ -613,6 +618,10 @@
"title": "حذف قانون",
"msg": "آیا از حذف قانون «$rulename» مطمئن هستید؟"
}
},
"addProfileByDeepLinkWarning": {
"title": "افزودن پروفایل؟",
"message": "یک لینک خارجی از «${host}» درخواست افزودن پروفایل جدید دارد. آیا می‌خواهید آن را وارد کنید؟"
}
},
"experimentalNotice": {
Expand Down Expand Up @@ -700,8 +709,12 @@
"invalidConfig": "پیکربندی نامعتبر"
},
"warp": {
"missingLicense": "لایسنس WARP",
"missingLicenseMsg": "پروفایل انتخاب‌شده از ویژگی WARP استفاده می‌کند؛ برای استفاده از این قابلیت، باید با لایسنس WARP موافقت شود."
"missingLicense": "لایسنس WARP لازم است",
"missingLicenseMsg": "پروفایل انتخاب‌شده از ویژگی WARP استفاده می‌کند؛ برای استفاده از این قابلیت، باید با لایسنس WARP موافقت شود"
},
"psiphon": {
"missingLicense": "لایسنس Psiphon لازم است",
"missingLicenseMsg": "پروفایل انتخاب‌شده از ویژگی Psiphon استفاده می‌کند؛ برای استفاده از این قابلیت، باید با لایسنس Psiphon موافقت شود"
}
}
}
}
Loading
Loading