ZaStoGram/Tools/check_zapret_proxy_sponsor.py
loop-uh 8f4389b846
Some checks failed
ZaStoGram source guards / guards (push) Failing after 21s
Build three ZaStoGram APKs / build (x86, ZaStoGram-standalone-x86, X86, x86) (push) Failing after 1m52s
Build three ZaStoGram APKs / build (armeabi-v7a, ZaStoGram-standalone-armeabi-v7a, Armv7, armv7) (push) Failing after 1m53s
Build three ZaStoGram APKs / build (arm64-v8a, ZaStoGram-standalone-arm64-v8a, Arm64, arm64) (push) Failing after 1m55s
Не ронять клиент ошибкой плагина и дать открепить канал ZaStoGram
BulletinHelper принимает аргументы exteraGram (fragment и прочие), а
исключение из кода плагина в главном потоке больше не завершает
приложение: оно пишется в лог и показывается уведомлением, плагин,
который падает трижды за минуту, отключается. Остальные ошибки падают
как раньше.

В настройках приватности ZaStoGram появился переключатель закрепления
канала ZaStoGram (по умолчанию включён). Без закрепления канал
становится обычным чатом и его можно убрать в архив.
2026-09-23 23:25:29 +03:00

236 lines
11 KiB
Python

#!/usr/bin/env python3
"""Static guard for the permanent ZaStoGram promo and free-proxy shortcuts."""
from pathlib import Path
import re
import sys
ROOT = Path(__file__).resolve().parents[1]
DIALOGS_ADAPTER = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java"
DIALOGS_ACTIVITY = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java"
DIALOG_CELL = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java"
SETTINGS_ACTIVITY = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java"
FREE_PROXY_SETTINGS_ACTIVITY = ROOT / "TMessagesProj/src/main/java/org/telegram/ui/FreeProxySettingsActivity.java"
STRINGS = ROOT / "TMessagesProj/src/main/res/values/strings.xml"
ZASTO_PRIVACY = ROOT / "TMessagesProj/src/main/java/org/telegram/messenger/ZaStoPrivacy.java"
EXPECTED_STRINGS = {
"ZapretVpnBot": "ZaSto VPN",
"FreeProxyChannels": "Бесплатные прокси",
"FreeProxyMtProxyEveryday": "MTProxy everyday",
"FreeProxyProxyMtProto": "Proxy MTProto",
"FreeProxyProxyFreeRu": "Proxy Free Ru",
"FreeProxyTgMtProxyLol": "TG MTProxy LOL",
"FreeProxyMemtproxy": "memtproxy",
"FreeProxyTProxyRu": "TProxy RU",
"FreeProxyProxyFreeMTProto": "Proxy Free MTProto",
"FreeProxyTelMTProto": "Tel MTProto",
}
EXPECTED_LINKS = {
28: ("FreeProxyMtProxyEveryday", "https://t.me/MTProxy_everyday"),
29: ("FreeProxyProxyMtProto", "https://t.me/ProxyMTProto"),
30: ("FreeProxyProxyFreeRu", "https://t.me/ProxyFree_Ru"),
31: ("FreeProxyTgMtProxyLol", "https://t.me/tgmtproxylol"),
32: ("FreeProxyMemtproxy", "https://t.me/memtproxy"),
33: ("FreeProxyTProxyRu", "https://t.me/TProxyRU"),
34: ("FreeProxyProxyFreeMTProto", "https://t.me/ProxyFreeMTProto"),
35: ("FreeProxyTelMTProto", "https://t.me/TelMTProto"),
}
def main() -> int:
dialogs_adapter = DIALOGS_ADAPTER.read_text(encoding="utf-8")
dialogs_activity = DIALOGS_ACTIVITY.read_text(encoding="utf-8")
dialog_cell = DIALOG_CELL.read_text(encoding="utf-8")
settings_activity = SETTINGS_ACTIVITY.read_text(encoding="utf-8")
free_proxy_settings_activity = FREE_PROXY_SETTINGS_ACTIVITY.read_text(encoding="utf-8")
strings = STRINGS.read_text(encoding="utf-8")
errors: list[str] = []
def require(condition: bool, message: str) -> None:
if not condition:
errors.append(message)
for name, value in EXPECTED_STRINGS.items():
require(
f'<string name="{name}">{value}</string>' in strings,
f"Missing string resource {name}={value}",
)
require(
"VIEW_TYPE_ZASTOGRAM_PROMO" in dialogs_adapter,
"ZaStoGram promo must use a stable dedicated item type",
)
require(
"shouldShowZastogramPromo()" in dialogs_adapter,
"DialogsAdapter must gate the ZaStoGram row to the default chat list",
)
should_show_start = dialogs_adapter.find("private boolean shouldShowZastogramPromo()")
should_show_end = dialogs_adapter.find("public boolean isZastogramPromoDialog", should_show_start)
should_show_body = dialogs_adapter[should_show_start:should_show_end]
# The only allowed dependency is the user's own "pin the ZaStoGram channel"
# choice (on by default); proxy and ad state must never hide the row.
other_privacy_flags = should_show_body.replace("ZaStoPrivacy.SHOW_ZASTOGRAM_PROMO", "")
require(
"SharedConfig" not in should_show_body and "ZaStoPrivacy" not in other_privacy_flags,
"ZaStoGram promo must not depend on proxy or ad state (only on SHOW_ZASTOGRAM_PROMO)",
)
zasto_privacy = ZASTO_PRIVACY.read_text(encoding="utf-8")
require(
"SHOW_ZASTOGRAM_PROMO = true;" in zasto_privacy
and "getBoolean(KEY_SHOW_ZASTOGRAM_PROMO, true)" in zasto_privacy,
"The ZaStoGram channel must stay pinned by default",
)
require(
'ZASTOGRAM_PROMO_USERNAME = "zastogram"' in dialogs_adapter,
"DialogsAdapter must use the real ZaStoGram channel username",
)
require(
"filterLegacyProxySponsorDialogs(" in dialogs_adapter
and "messagesController.promoDialogType == MessagesController.PROMO_TYPE_PROXY" in dialogs_adapter,
"DialogsAdapter must remove the legacy Telegram proxy promo dialog",
)
require(
"removeZastogramPromoDialogFromArray(" in dialogs_adapter
and "insertZastogramPromoItem(" in dialogs_adapter
and "isArchiveDialog(item.dialog) ? i + 1 : i" in dialogs_adapter,
"DialogsAdapter must keep ZaStoGram before chats but after the archive row",
)
is_promo_method = dialogs_adapter[
dialogs_adapter.find("public boolean isZastogramPromoDialog"):
dialogs_adapter.find("private ArrayList<TLRPC.Dialog> filterLegacyProxySponsorDialogs")
]
require(
"!shouldShowZastogramPromo()" in is_promo_method,
"ZaStoGram row detection must be limited to the main chat list",
)
require(
"DialogCell.CustomDialog" not in extract_promo_adapter_region(dialogs_adapter)
and "customDialog.name" not in extract_promo_adapter_region(dialogs_adapter)
and "customDialog.message" not in extract_promo_adapter_region(dialogs_adapter),
"ZaStoGram row must not be rendered as a fake CustomDialog",
)
require(
"getUserNameResolver().resolve(ZASTOGRAM_PROMO_USERNAME" in dialogs_adapter
and "messagesController.dialogs_dict.get(dialogId)" in dialogs_adapter,
"DialogsAdapter must resolve @zastogram and prefer an existing real dialog",
)
require(
"cell.setZastogramPromo(true)" in dialogs_adapter
and "cell.setDialog(promoDialog, dialogsType, folderId)" in dialogs_adapter
and "cell.setDialog(zastogramPromoDialogId, zastogramPromoPost" in dialogs_adapter
and "timeString = getString(R.string.AppName)" in dialog_cell,
"ZaStoGram row must bind the real peer and render the app-name promo badge",
)
require(
"TLRPC.TL_messages_getHistory" in dialogs_adapter
and "request.limit = 1" in dialogs_adapter
and "zastogramPromoPost = new MessageObject" in dialogs_adapter
and "zastogramPromoPostLastRequestTime != 0" in dialogs_adapter
and "requestZastogramPromoPost(messagesController)" in dialogs_adapter,
"ZaStoGram row must load and render one latest public post for non-subscribers without delaying the first request",
)
require(
"isZastogramPromoDialog(position)" in dialogs_activity
and "openByUserName(DialogsAdapter.ZASTOGRAM_PROMO_USERNAME" in dialogs_activity,
"DialogsActivity must open @zastogram through Telegram username resolution",
)
custom_dialog_start = dialog_cell.find("public void setDialog(CustomDialog dialog)")
custom_dialog_end = dialog_cell.find("private void checkOnline()", custom_dialog_start)
custom_dialog_body = dialog_cell[custom_dialog_start:custom_dialog_end]
require(
"currentDialogId = 0;" in custom_dialog_body
and "message = null;" in custom_dialog_body
and "user = null;" in custom_dialog_body
and "chat = null;" in custom_dialog_body
and "encryptedChat = null;" in custom_dialog_body,
"DialogCell custom rows must clear recycled real-dialog state",
)
require(
dialog_cell.count("customDialog = null;") >= 4,
"DialogCell real chat/topic setters must clear recycled custom-dialog state",
)
real_dialog_start = dialog_cell.find("public void setDialog(TLRPC.Dialog dialog, int type, int folder)")
real_dialog_end = dialog_cell.find("protected boolean drawLock2()", real_dialog_start)
real_dialog_body = dialog_cell[real_dialog_start:real_dialog_end]
require(
"forumTopic = null;" in real_dialog_body
and "isTopic = false;" in real_dialog_body
and "isForum = false;" in real_dialog_body
and "groupMessages = null;" in real_dialog_body,
"DialogCell real dialog binding must clear recycled topic/folder state",
)
require(
"SettingCell.Factory.of(1004," in settings_activity
and "getString(R.string.FreeProxyChannels)" in settings_activity
and "case 1004:" in settings_activity
and "presentSettingFragment(new FreeProxySettingsActivity())" in settings_activity,
"Settings must open the dedicated FreeProxySettingsActivity",
)
require(
"items.add(UItem.asHeader(LocaleController.getString(R.string.FreeProxyChannels)))" in free_proxy_settings_activity,
"FreeProxySettingsActivity must include the FreeProxyChannels block",
)
require(
"SettingCell.Factory.of(27," in free_proxy_settings_activity
and "LocaleController.getString(R.string.ZapretVpnBot)" in free_proxy_settings_activity
and '"@zapretvpns_bot"' in free_proxy_settings_activity,
"FreeProxySettingsActivity must pin the ZaSto VPN bot above the catalog",
)
require(
free_proxy_settings_activity.find("SettingCell.Factory.of(27,")
< free_proxy_settings_activity.find("SettingCell.Factory.of(1002,")
< free_proxy_settings_activity.find("items.add(UItem.asHeader(LocaleController.getString(R.string.FreeProxyChannels)))"),
"ZaSto VPN bot must be the first row in FreeProxySettingsActivity",
)
require(
'getMessagesController().openByUserName("zapretvpns_bot", this, 1)' in free_proxy_settings_activity,
"FreeProxySettingsActivity must open the pinned bot inside Telegram",
)
for item_id, (string_name, url) in EXPECTED_LINKS.items():
require(
re.search(
rf"SettingCell\.Factory\.of\({item_id},[^;]+LocaleController\.getString\(R\.string\.{string_name}\)\)",
free_proxy_settings_activity,
re.DOTALL,
)
is not None,
f"FreeProxySettingsActivity item {item_id} must use {string_name}",
)
require(
re.search(
rf"case {item_id}:\s+Browser\.openUrl\(getParentActivity\(\), \"{re.escape(url)}\"\);\s+break;",
free_proxy_settings_activity,
re.DOTALL,
)
is not None,
f"FreeProxySettingsActivity item {item_id} must open {url}",
)
if errors:
print("Zapret proxy sponsor check failed:")
for error in errors:
print(f"- {error}")
return 1
print("Zapret proxy sponsor check passed")
return 0
def extract_promo_adapter_region(dialogs_adapter: str) -> str:
start = dialogs_adapter.find("ZASTOGRAM_PROMO")
if start < 0:
return ""
end = dialogs_adapter.find("case VIEW_TYPE_FORWARD_TO_STORIES_CELL", start)
if end < 0:
end = dialogs_adapter.find("case VIEW_TYPE_EMPTY", start)
return dialogs_adapter[start:end]
if __name__ == "__main__":
sys.exit(main())