This commit is contained in:
+55
-40
@@ -6,58 +6,73 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
|
||||
# imports
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
import json
|
||||
from typing import Any, Dict
|
||||
|
||||
import requests
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, Any
|
||||
from pydantic import Field
|
||||
from tqdm import tqdm
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.config import PatchTemplate
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
server: str = Field("https://anixarty.0x174.su/patch", description="URL сервера")
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
response = requests.get(config.server) # Получаем данные для патча
|
||||
assert response.status_code == 200, f"Failed to fetch data {response.status_code} {response.text}"
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
response = requests.get(self.server) # Получаем данные для патча
|
||||
assert (
|
||||
response.status_code == 200
|
||||
), f"Failed to fetch data {response.status_code} {response.text}"
|
||||
|
||||
new_api = json.loads(response.text)
|
||||
for item in new_api['modifications']: # Применяем замены API
|
||||
tqdm.write(f"Изменение {item['file']}")
|
||||
filepath = './decompiled/smali_classes2/com/swiftsoft/anixartd/network/api/'+item['file']
|
||||
new_api = json.loads(response.text)
|
||||
for item in new_api["modifications"]: # Применяем замены API
|
||||
tqdm.write(f"Изменение {item['file']}")
|
||||
filepath = (
|
||||
"./decompiled/smali_classes2/com/swiftsoft/anixartd/network/api/"
|
||||
+ item["file"]
|
||||
)
|
||||
|
||||
with open(filepath, 'r') as f:
|
||||
with open(filepath, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
with open(filepath, "w") as f:
|
||||
if content.count(item["src"]) == 0:
|
||||
tqdm.write(f"⚠ Не найдено {item['src']}")
|
||||
f.write(content.replace(item["src"], item["dst"]))
|
||||
|
||||
tqdm.write(
|
||||
f"Изменение Github ссылки"
|
||||
) # Обновление ссылки на поиск серверов в Github
|
||||
filepath = "./decompiled/smali_classes2/com/swiftsoft/anixartd/utils/anixnet/GithubPagesNetFetcher.smali"
|
||||
|
||||
with open(filepath, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
if content.count(item['src']) == 0:
|
||||
tqdm.write(f"⚠ Не найдено {item['src']}")
|
||||
f.write(content.replace(item['src'], item['dst']))
|
||||
with open(filepath, "w") as f:
|
||||
f.write(
|
||||
content.replace(
|
||||
'const-string v1, "https://anixhelper.github.io/pages/urls.json"',
|
||||
f'const-string v1, "{new_api["gh"]}"',
|
||||
)
|
||||
)
|
||||
|
||||
tqdm.write(f"Изменение Github ссылки") # Обновление ссылки на поиск серверов в Github
|
||||
filepath = './decompiled/smali_classes2/com/swiftsoft/anixartd/utils/anixnet/GithubPagesNetFetcher.smali'
|
||||
tqdm.write(
|
||||
"Удаление динамического выбора сервера"
|
||||
) # Отключение автовыбора сервера
|
||||
filepath = "./decompiled/smali_classes2/com/swiftsoft/anixartd/DaggerApp_HiltComponents_SingletonC$SingletonCImpl$SwitchingProvider.smali"
|
||||
|
||||
with open(filepath, 'r') as f:
|
||||
content = f.read()
|
||||
content = ""
|
||||
with open(filepath, "r") as f:
|
||||
for line in f.readlines():
|
||||
if "addInterceptor" in line:
|
||||
continue
|
||||
content += line
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content.replace('const-string v1, "https://anixhelper.github.io/pages/urls.json"', f'const-string v1, "{new_api["gh"]}"'))
|
||||
with open(filepath, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
tqdm.write("Удаление динамического выбора сервера") # Отключение автовыбора сервера
|
||||
filepath = './decompiled/smali_classes2/com/swiftsoft/anixartd/DaggerApp_HiltComponents_SingletonC$SingletonCImpl$SwitchingProvider.smali'
|
||||
|
||||
content = ""
|
||||
with open(filepath, 'r') as f:
|
||||
for line in f.readlines():
|
||||
if "addInterceptor" in line: continue
|
||||
content += line
|
||||
|
||||
with open(filepath, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
+146
-84
@@ -19,127 +19,189 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
from typing import Any, Dict
|
||||
|
||||
# imports
|
||||
from lxml import etree
|
||||
from typing import Dict, Any
|
||||
from pydantic import Field, BaseModel
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
from utils.config import PatchTemplate
|
||||
from utils.public import change_color, insert_after_color, insert_after_public
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.public import (
|
||||
insert_after_public,
|
||||
insert_after_color,
|
||||
change_color,
|
||||
)
|
||||
|
||||
#Config
|
||||
class Gradient(BaseModel):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
angle: float = Field(0.0, description="Угол градиента")
|
||||
start_color: str = Field("#ffccff00", description="Начальный цвет градиента")
|
||||
end_color: str = Field("#ffcccc00", description="Конечный цвет градиента")
|
||||
|
||||
|
||||
class Logo(BaseModel):
|
||||
gradient: Gradient = Field(Gradient(), description="Настройки градиента") # type: ignore [reportCallIssue]
|
||||
gradient: Gradient = Field(
|
||||
default_factory=Gradient, description="Настройки градиента"
|
||||
)
|
||||
ears_color: str = Field("#ffd0d0d0", description="Цвет ушей логотипа")
|
||||
|
||||
|
||||
class Colors(BaseModel):
|
||||
primary: str = Field("#ccff00", description="Основной цвет")
|
||||
secondary: str = Field("#ffcccc00", description="Вторичный цвет")
|
||||
background: str = Field("#ffffff", description="Фоновый цвет")
|
||||
text: str = Field("#000000", description="Цвет текста")
|
||||
|
||||
class Config(PatchConfig):
|
||||
logo: Logo = Field(Logo(), description="Настройки цветов логотипа") # type: ignore [reportCallIssue]
|
||||
colors: Colors = Field(Colors(), description="Настройки цветов") # type: ignore [reportCallIssue]
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
main_color = config.colors.primary
|
||||
splash_color = config.colors.secondary
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
logo: Logo = Field(default_factory=Logo, description="Настройки цветов логотипа")
|
||||
colors: Colors = Field(default_factory=Colors, description="Настройки цветов")
|
||||
|
||||
# Обновление сообщения об отсутствии подключения
|
||||
with open("./decompiled/assets/no_connection.html", "r", encoding="utf-8") as file:
|
||||
file_contents = file.read()
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def validate_nested(cls, data):
|
||||
if isinstance(data, dict):
|
||||
if "logo" in data and isinstance(data["logo"], dict):
|
||||
data["logo"] = Logo(**data["logo"])
|
||||
if "colors" in data and isinstance(data["colors"], dict):
|
||||
data["colors"] = Colors(**data["colors"])
|
||||
return data
|
||||
|
||||
new_contents = file_contents.replace("#f04e4e", main_color)
|
||||
def hex_to_lottie(hex_color: str) -> tuple[float, float, float]:
|
||||
hex_color = hex_color.lstrip("#")
|
||||
hex_color = hex_color[2:] if len(hex_color) == 8 else hex_color
|
||||
return (
|
||||
int(hex_color[:2], 16) / 255.0,
|
||||
int(hex_color[2:4], 16) / 255.0,
|
||||
int(hex_color[4:6], 16) / 255.0,
|
||||
)
|
||||
|
||||
with open("./decompiled/assets/no_connection.html", "w", encoding="utf-8") as file:
|
||||
file.write(new_contents)
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
main_color = self.colors.primary
|
||||
splash_color = self.colors.secondary
|
||||
|
||||
# Суффиксы лого
|
||||
drawable_types = ["", "-night"]
|
||||
# Обновление сообщения об отсутствии подключения
|
||||
with open(
|
||||
"./decompiled/assets/no_connection.html", "r", encoding="utf-8"
|
||||
) as file:
|
||||
file_contents = file.read()
|
||||
|
||||
for drawable_type in drawable_types:
|
||||
# Градиент лого приложения
|
||||
file_path = f"./decompiled/res/drawable{drawable_type}/$logo__0.xml"
|
||||
new_contents = file_contents.replace("#f04e4e", main_color)
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
with open(
|
||||
"./decompiled/assets/no_connection.html", "w", encoding="utf-8"
|
||||
) as file:
|
||||
file.write(new_contents)
|
||||
|
||||
# Замена атрибутов значениями из конфигурации
|
||||
root.set(f"{{{base['xml_ns']['android']}}}angle", str(config.logo.gradient.angle))
|
||||
root.set(f"{{{base['xml_ns']['android']}}}startColor", config.logo.gradient.start_color)
|
||||
root.set(f"{{{base['xml_ns']['android']}}}endColor", config.logo.gradient.end_color)
|
||||
# Суффиксы лого
|
||||
drawable_types = ["", "-night"]
|
||||
|
||||
# Сохранение
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
for drawable_type in drawable_types:
|
||||
# Градиент лого приложения
|
||||
file_path = f"./decompiled/res/drawable{drawable_type}/$logo__0.xml"
|
||||
|
||||
# Замена анимации лого
|
||||
file_path = f"./decompiled/res/drawable{drawable_type}/$logo_splash_anim__0.xml"
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
# Замена атрибутов значениями из конфигурации
|
||||
root.set(
|
||||
f"{{{base['xml_ns']['android']}}}angle", str(self.logo.gradient.angle)
|
||||
)
|
||||
root.set(
|
||||
f"{{{base['xml_ns']['android']}}}startColor",
|
||||
self.logo.gradient.start_color,
|
||||
)
|
||||
root.set(
|
||||
f"{{{base['xml_ns']['android']}}}endColor", self.logo.gradient.end_color
|
||||
)
|
||||
|
||||
for el in root.findall("path", namespaces=base["xml_ns"]):
|
||||
name = el.get(f"{{{base['xml_ns']['android']}}}name")
|
||||
if name == "path":
|
||||
el.set(f"{{{base['xml_ns']['android']}}}fillColor", config.colors.secondary)
|
||||
elif name in ["path_1", "path_2"]:
|
||||
el.set(f"{{{base['xml_ns']['android']}}}fillColor", config.logo.ears_color)
|
||||
# Сохранение
|
||||
tree.write(
|
||||
file_path, pretty_print=True, xml_declaration=True, encoding="utf-8"
|
||||
)
|
||||
|
||||
# Сохранение
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
# Замена анимации лого
|
||||
file_path = (
|
||||
f"./decompiled/res/drawable{drawable_type}/$logo_splash_anim__0.xml"
|
||||
)
|
||||
|
||||
for filename in ["$ic_launcher_foreground__0", "$ic_banner_foreground__0"]:
|
||||
file_path = f"./decompiled/res/drawable-v24/{filename}.xml"
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
for el in root.findall("path", namespaces=base["xml_ns"]):
|
||||
name = el.get(f"{{{base['xml_ns']['android']}}}name")
|
||||
if name == "path":
|
||||
el.set(
|
||||
f"{{{base['xml_ns']['android']}}}fillColor",
|
||||
self.colors.secondary,
|
||||
)
|
||||
elif name in ["path_1", "path_2"]:
|
||||
el.set(
|
||||
f"{{{base['xml_ns']['android']}}}fillColor",
|
||||
self.logo.ears_color,
|
||||
)
|
||||
|
||||
# Замена атрибутов значениями из конфигурации
|
||||
root.set(f"{{{base['xml_ns']['android']}}}angle", str(config.logo.gradient.angle))
|
||||
items = root.findall("item", namespaces=base['xml_ns'])
|
||||
assert len(items) == 2
|
||||
items[0].set(f"{{{base['xml_ns']['android']}}}color", config.logo.gradient.start_color)
|
||||
items[1].set(f"{{{base['xml_ns']['android']}}}color", config.logo.gradient.end_color)
|
||||
# Сохранение
|
||||
tree.write(
|
||||
file_path, pretty_print=True, xml_declaration=True, encoding="utf-8"
|
||||
)
|
||||
|
||||
# Сохранение
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
for filename in ["$ic_launcher_foreground__0", "$ic_banner_foreground__0"]:
|
||||
file_path = f"./decompiled/res/drawable-v24/{filename}.xml"
|
||||
|
||||
# Добаление новых цветов для темы
|
||||
insert_after_public("carmine", "custom_color")
|
||||
insert_after_public("carmine_alpha_10", "custom_color_alpha_10")
|
||||
insert_after_color("carmine", "custom_color", main_color[0]+'ff'+main_color[1:])
|
||||
insert_after_color("carmine_alpha_10", "custom_color_alpha_10", main_color[0]+'1a'+main_color[1:])
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
# Замена цветов
|
||||
change_color("accent_alpha_10", main_color[0]+'1a'+main_color[1:])
|
||||
change_color("accent_alpha_20", main_color[0]+'33'+main_color[1:])
|
||||
change_color("accent_alpha_50", main_color[0]+'80'+main_color[1:])
|
||||
change_color("accent_alpha_70", main_color[0]+'b3'+main_color[1:])
|
||||
# Замена атрибутов значениями из конфигурации
|
||||
root.set(
|
||||
f"{{{base['xml_ns']['android']}}}angle", str(self.logo.gradient.angle)
|
||||
)
|
||||
items = root.findall("item", namespaces=base["xml_ns"])
|
||||
assert len(items) == 2
|
||||
items[0].set(
|
||||
f"{{{base['xml_ns']['android']}}}color", self.logo.gradient.start_color
|
||||
)
|
||||
items[1].set(
|
||||
f"{{{base['xml_ns']['android']}}}color", self.logo.gradient.end_color
|
||||
)
|
||||
|
||||
change_color("colorAccent", main_color[0]+'ff'+main_color[1:])
|
||||
change_color("link_color", main_color[0]+'ff'+main_color[1:])
|
||||
change_color("link_color_alpha_70", main_color[0]+'b3'+main_color[1:])
|
||||
change_color("refresh_progress", main_color[0]+'ff'+main_color[1:])
|
||||
# Сохранение
|
||||
tree.write(
|
||||
file_path, pretty_print=True, xml_declaration=True, encoding="utf-8"
|
||||
)
|
||||
|
||||
change_color("ic_launcher_background", "#ff000000")
|
||||
change_color("bottom_nav_indicator_active", "#ffffffff")
|
||||
change_color("bottom_nav_indicator_icon_checked", main_color[0]+'ff'+main_color[1:])
|
||||
change_color("bottom_nav_indicator_label_checked", main_color[0]+'ff'+main_color[1:])
|
||||
# Добаление новых цветов для темы
|
||||
insert_after_public("carmine", "custom_color")
|
||||
insert_after_public("carmine_alpha_10", "custom_color_alpha_10")
|
||||
insert_after_color(
|
||||
"carmine", "custom_color", main_color[0] + "ff" + main_color[1:]
|
||||
)
|
||||
insert_after_color(
|
||||
"carmine_alpha_10",
|
||||
"custom_color_alpha_10",
|
||||
main_color[0] + "1a" + main_color[1:],
|
||||
)
|
||||
|
||||
return True
|
||||
# Замена цветов
|
||||
change_color("accent_alpha_10", main_color[0] + "1a" + main_color[1:])
|
||||
change_color("accent_alpha_20", main_color[0] + "33" + main_color[1:])
|
||||
change_color("accent_alpha_50", main_color[0] + "80" + main_color[1:])
|
||||
change_color("accent_alpha_70", main_color[0] + "b3" + main_color[1:])
|
||||
|
||||
change_color("colorAccent", main_color[0] + "ff" + main_color[1:])
|
||||
change_color("link_color", main_color[0] + "ff" + main_color[1:])
|
||||
change_color("link_color_alpha_70", main_color[0] + "b3" + main_color[1:])
|
||||
change_color("refresh_progress", main_color[0] + "ff" + main_color[1:])
|
||||
|
||||
change_color("ic_launcher_background", "#ff000000")
|
||||
change_color("bottom_nav_indicator_active", "#ffffffff")
|
||||
change_color(
|
||||
"bottom_nav_indicator_icon_checked", main_color[0] + "ff" + main_color[1:]
|
||||
)
|
||||
change_color(
|
||||
"bottom_nav_indicator_label_checked", main_color[0] + "ff" + main_color[1:]
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
+83
-75
@@ -8,92 +8,100 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
|
||||
# imports
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
import os
|
||||
import shutil
|
||||
from tqdm import tqdm
|
||||
from typing import Any, Dict
|
||||
|
||||
from lxml import etree
|
||||
from pydantic import Field
|
||||
from typing import Dict, Any
|
||||
from utils.config import PatchConfig
|
||||
from tqdm import tqdm
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
from utils.config import PatchTemplate
|
||||
|
||||
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
replace: bool = Field(True, description="Менять местами лайк/дизлайк")
|
||||
custom_icons: bool = Field(True, description="Кастомные иконки")
|
||||
icon_size: str = Field("18.0dip", description="Размер иконки")
|
||||
|
||||
# Patch
|
||||
def apply(config, base: Dict[str, Any]) -> bool:
|
||||
file_path = "./decompiled/res/layout/item_comment.xml"
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
file_path = "./decompiled/res/layout/item_comment.xml"
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
tqdm.write("Меняем размер иконок лайка и дизлайка...")
|
||||
for icon in root.xpath(
|
||||
".//*[@android:id='@id/votePlusInactive']//ImageView | "
|
||||
".//*[@android:id='@id/votePlusActive']//ImageView | "
|
||||
".//*[@android:id='@id/voteMinusInactive']//ImageView | "
|
||||
".//*[@android:id='@id/voteMinusActive']//ImageView",
|
||||
namespaces=base['xml_ns'],
|
||||
):
|
||||
icon.set(f"{{{base['xml_ns']['android']}}}layout_width", config.icon_size)
|
||||
# icon.set(f"{{{base['xml_ns']['android']}}}layout_height", config.icon_size)
|
||||
|
||||
if config.replace:
|
||||
tqdm.write("Меняем местами лайк и дизлайк комментария...")
|
||||
|
||||
containers = root.xpath(
|
||||
".//LinearLayout[.//*[@android:id='@id/voteMinus'] and .//*[@android:id='@id/votePlus']]",
|
||||
namespaces=base["xml_ns"],
|
||||
)
|
||||
|
||||
found = False
|
||||
for container in containers:
|
||||
children = list(container)
|
||||
vote_plus = None
|
||||
vote_minus = None
|
||||
|
||||
for ch in children:
|
||||
cid = ch.get(f'{{{base["xml_ns"]["android"]}}}id')
|
||||
if cid == "@id/votePlus":
|
||||
vote_plus = ch
|
||||
elif cid == "@id/voteMinus":
|
||||
vote_minus = ch
|
||||
|
||||
if vote_plus is not None and vote_minus is not None:
|
||||
found = True
|
||||
i_plus = children.index(vote_plus)
|
||||
i_minus = children.index(vote_minus)
|
||||
children[i_plus], children[i_minus] = children[i_minus], children[i_plus]
|
||||
container[:] = children
|
||||
tqdm.write("Кнопки лайк и дизлайк поменялись местами.")
|
||||
break
|
||||
|
||||
if not found:
|
||||
tqdm.write("Не удалось найти оба узла votePlus/voteMinus даже в общих LinearLayout.")
|
||||
|
||||
if config.custom_icons:
|
||||
tqdm.write("Заменяем иконки лайка и дизлайка на кастомные...")
|
||||
for suffix in ["up", "up_40", "down", "down_40"]:
|
||||
shutil.copy(
|
||||
f"./resources/ic_chevron_{suffix}.xml",
|
||||
f"./decompiled/res/drawable/ic_chevron_{suffix}.xml",
|
||||
)
|
||||
|
||||
for inactive in root.xpath(
|
||||
".//*[@android:id='@id/votePlusInactive'] | .//*[@android:id='@id/voteMinusInactive']",
|
||||
tqdm.write("Меняем размер иконок лайка и дизлайка...")
|
||||
for icon in root.xpath(
|
||||
".//*[@android:id='@id/votePlusInactive']//ImageView | "
|
||||
".//*[@android:id='@id/votePlusActive']//ImageView | "
|
||||
".//*[@android:id='@id/voteMinusInactive']//ImageView | "
|
||||
".//*[@android:id='@id/voteMinusActive']//ImageView",
|
||||
namespaces=base["xml_ns"],
|
||||
):
|
||||
for img in inactive.xpath(".//ImageView[@android:src]", namespaces=base["xml_ns"]):
|
||||
src = img.get(f'{{{base["xml_ns"]["android"]}}}src', "")
|
||||
if src.startswith("@drawable/") and not src.endswith("_40"):
|
||||
img.set(f'{{{base["xml_ns"]["android"]}}}src', src + "_40")
|
||||
icon.set(f"{{{base['xml_ns']['android']}}}layout_width", self.icon_size)
|
||||
# icon.set(f"{{{base['xml_ns']['android']}}}layout_height", self.icon_size)
|
||||
|
||||
# Сохраняем
|
||||
tree.write(file_path, encoding="utf-8", xml_declaration=True, pretty_print=True)
|
||||
if self.replace:
|
||||
tqdm.write("Меняем местами лайк и дизлайк комментария...")
|
||||
|
||||
return True
|
||||
containers = root.xpath(
|
||||
".//LinearLayout[.//*[@android:id='@id/voteMinus'] and .//*[@android:id='@id/votePlus']]",
|
||||
namespaces=base["xml_ns"],
|
||||
)
|
||||
|
||||
found = False
|
||||
for container in containers:
|
||||
children = list(container)
|
||||
vote_plus = None
|
||||
vote_minus = None
|
||||
|
||||
for ch in children:
|
||||
cid = ch.get(f'{{{base["xml_ns"]["android"]}}}id')
|
||||
if cid == "@id/votePlus":
|
||||
vote_plus = ch
|
||||
elif cid == "@id/voteMinus":
|
||||
vote_minus = ch
|
||||
|
||||
if vote_plus is not None and vote_minus is not None:
|
||||
found = True
|
||||
i_plus = children.index(vote_plus)
|
||||
i_minus = children.index(vote_minus)
|
||||
children[i_plus], children[i_minus] = (
|
||||
children[i_minus],
|
||||
children[i_plus],
|
||||
)
|
||||
container[:] = children
|
||||
tqdm.write("Кнопки лайк и дизлайк поменялись местами.")
|
||||
break
|
||||
|
||||
if not found:
|
||||
tqdm.write(
|
||||
"Не удалось найти оба узла votePlus/voteMinus даже в общих LinearLayout."
|
||||
)
|
||||
|
||||
if self.custom_icons:
|
||||
tqdm.write("Заменяем иконки лайка и дизлайка на кастомные...")
|
||||
for suffix in ["up", "up_40", "down", "down_40"]:
|
||||
shutil.copy(
|
||||
f"./resources/ic_chevron_{suffix}.xml",
|
||||
f"./decompiled/res/drawable/ic_chevron_{suffix}.xml",
|
||||
)
|
||||
|
||||
for inactive in root.xpath(
|
||||
".//*[@android:id='@id/votePlusInactive'] | .//*[@android:id='@id/voteMinusInactive']",
|
||||
namespaces=base["xml_ns"],
|
||||
):
|
||||
for img in inactive.xpath(
|
||||
".//ImageView[@android:src]", namespaces=base["xml_ns"]
|
||||
):
|
||||
src = img.get(f'{{{base["xml_ns"]["android"]}}}src', "")
|
||||
if src.startswith("@drawable/") and not src.endswith("_40"):
|
||||
img.set(f'{{{base["xml_ns"]["android"]}}}src', src + "_40")
|
||||
|
||||
# Сохраняем
|
||||
tree.write(file_path, encoding="utf-8", xml_declaration=True, pretty_print=True)
|
||||
|
||||
return True
|
||||
|
||||
+285
-260
@@ -26,283 +26,308 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = -1
|
||||
|
||||
# imports
|
||||
__author__ = "Kentai Radiquum <radiquum@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from tqdm import tqdm
|
||||
from pydantic import Field
|
||||
from typing import Dict, List, Any
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from pydantic import Field
|
||||
from tqdm import tqdm
|
||||
|
||||
from utils.config import PatchTemplate
|
||||
from utils.smali_parser import get_smali_lines, save_smali_lines
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
remove_language_files: bool = Field(True, description="Удаляет все языки кроме русского и английского")
|
||||
remove_AI_voiceover: bool = Field(True, description="Заменяет ИИ озвучку маскота аниксы пустыми mp3 файлами")
|
||||
remove_debug_lines: bool = Field(False, description="Удаляет строки `.line n` из smali файлов использованные для дебага")
|
||||
remove_drawable_files: bool = Field(False, description="Удаляет неиспользованные drawable-* из директории decompiled/res")
|
||||
remove_unknown_files: bool = Field(True, description="Удаляет файлы из директории decompiled/unknown")
|
||||
remove_unknown_files_keep_dirs: List[str] = Field(["META-INF", "kotlin"], description="Оставляет указанные директории в decompiled/unknown")
|
||||
compress_png_files: bool = Field(True, description="Сжимает PNG в директории decompiled/res")
|
||||
|
||||
# Patch
|
||||
def remove_unknown_files(config: Config, base: Dict[str, Any]):
|
||||
path = "./decompiled/unknown"
|
||||
items = os.listdir(path)
|
||||
for item in items:
|
||||
item_path = f"{path}/{item}"
|
||||
if os.path.isfile(item_path):
|
||||
os.remove(item_path)
|
||||
if base.get("verbose", False):
|
||||
tqdm.write(f"Удалён файл: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
if item not in config.remove_unknown_files_keep_dirs:
|
||||
shutil.rmtree(item_path)
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=-1)
|
||||
remove_language_files: bool = Field(
|
||||
True, description="Удаляет все языки кроме русского и английского"
|
||||
)
|
||||
remove_AI_voiceover: bool = Field(
|
||||
True, description="Заменяет ИИ озвучку маскота аниксы пустыми mp3 файлами"
|
||||
)
|
||||
remove_debug_lines: bool = Field(
|
||||
False,
|
||||
description="Удаляет строки `.line n` из smali файлов использованные для дебага",
|
||||
)
|
||||
remove_drawable_files: bool = Field(
|
||||
False,
|
||||
description="Удаляет неиспользованные drawable-* из директории decompiled/res",
|
||||
)
|
||||
remove_unknown_files: bool = Field(
|
||||
True, description="Удаляет файлы из директории decompiled/unknown"
|
||||
)
|
||||
remove_unknown_files_keep_dirs: List[str] = Field(
|
||||
["META-INF", "kotlin"],
|
||||
description="Оставляет указанные директории в decompiled/unknown",
|
||||
)
|
||||
compress_png_files: bool = Field(
|
||||
True, description="Сжимает PNG в директории decompiled/res"
|
||||
)
|
||||
|
||||
def do_remove_unknown_files(self, base: Dict[str, Any]):
|
||||
path = "./decompiled/unknown"
|
||||
items = os.listdir(path)
|
||||
for item in items:
|
||||
item_path = f"{path}/{item}"
|
||||
if os.path.isfile(item_path):
|
||||
os.remove(item_path)
|
||||
if base.get("verbose", False):
|
||||
tqdm.write(f"Удалёна директория: {item_path}")
|
||||
return True
|
||||
|
||||
|
||||
def remove_debug_lines(config: Dict[str, Any]):
|
||||
for root, _, files in os.walk("./decompiled"):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
if os.path.isfile(file_path) and filename.endswith(".smali"):
|
||||
file_content = get_smali_lines(file_path)
|
||||
new_content = []
|
||||
for line in file_content:
|
||||
if line.find(".line") >= 0:
|
||||
continue
|
||||
new_content.append(line)
|
||||
save_smali_lines(file_path, new_content)
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Удалены дебаг линии из: {file_path}")
|
||||
return True
|
||||
|
||||
|
||||
def compress_png(config: Dict[str, Any], png_path: str):
|
||||
try:
|
||||
assert subprocess.run(
|
||||
[
|
||||
"pngquant",
|
||||
"--force",
|
||||
"--ext", ".png",
|
||||
"--quality=65-90",
|
||||
png_path,
|
||||
],
|
||||
capture_output=True,
|
||||
).returncode in [0, 99]
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Сжат файл PNG: {png_path}")
|
||||
tqdm.write(f"Удалён файл: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
if item not in self.remove_unknown_files_keep_dirs:
|
||||
shutil.rmtree(item_path)
|
||||
if base.get("verbose", False):
|
||||
tqdm.write(f"Удалёна директория: {item_path}")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
tqdm.write(f"Ошибка при сжатии {png_path}: {e}")
|
||||
return False
|
||||
|
||||
def do_remove_debug_lines(self, config: Dict[str, Any]):
|
||||
for root, _, files in os.walk("./decompiled"):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
if os.path.isfile(file_path) and filename.endswith(".smali"):
|
||||
file_content = get_smali_lines(file_path)
|
||||
new_content = []
|
||||
for line in file_content:
|
||||
if line.find(".line") >= 0:
|
||||
continue
|
||||
new_content.append(line)
|
||||
save_smali_lines(file_path, new_content)
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Удалены дебаг линии из: {file_path}")
|
||||
return True
|
||||
|
||||
def compress_png(self, config: Dict[str, Any], png_path: str):
|
||||
try:
|
||||
assert subprocess.run(
|
||||
[
|
||||
"pngquant",
|
||||
"--force",
|
||||
"--ext",
|
||||
".png",
|
||||
"--quality=65-90",
|
||||
png_path,
|
||||
],
|
||||
capture_output=True,
|
||||
).returncode in [0, 99]
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Сжат файл PNG: {png_path}")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
tqdm.write(f"Ошибка при сжатии {png_path}: {e}")
|
||||
return False
|
||||
|
||||
def do_compress_png_files(self, config: Dict[str, Any]):
|
||||
compressed = []
|
||||
for root, _, files in os.walk("./decompiled"):
|
||||
for file in files:
|
||||
if file.lower().endswith(".png"):
|
||||
self.compress_png(config, f"{root}/{file}")
|
||||
compressed.append(f"{root}/{file}")
|
||||
return len(compressed) > 0 and any(compressed)
|
||||
|
||||
def do_remove_AI_voiceover(self, config: Dict[str, Any]):
|
||||
blank = "./resources/blank.mp3"
|
||||
path = "./decompiled/res/raw"
|
||||
files = [
|
||||
"reputation_1.mp3",
|
||||
"reputation_2.mp3",
|
||||
"reputation_3.mp3",
|
||||
"sound_beta_1.mp3",
|
||||
"sound_create_blog_1.mp3",
|
||||
"sound_create_blog_2.mp3",
|
||||
"sound_create_blog_3.mp3",
|
||||
"sound_create_blog_4.mp3",
|
||||
"sound_create_blog_5.mp3",
|
||||
"sound_create_blog_6.mp3",
|
||||
"sound_create_blog_reputation_1.mp3",
|
||||
"sound_create_blog_reputation_2.mp3",
|
||||
"sound_create_blog_reputation_3.mp3",
|
||||
"sound_create_blog_reputation_4.mp3",
|
||||
"sound_create_blog_reputation_5.mp3",
|
||||
"sound_create_blog_reputation_6.mp3",
|
||||
]
|
||||
|
||||
def compress_png_files(config: Dict[str, Any]):
|
||||
compressed = []
|
||||
for root, _, files in os.walk("./decompiled"):
|
||||
for file in files:
|
||||
if file.lower().endswith(".png"):
|
||||
compress_png(config, f"{root}/{file}")
|
||||
compressed.append(f"{root}/{file}")
|
||||
return len(compressed) > 0 and any(compressed)
|
||||
if os.path.exists(f"{path}/{file}"):
|
||||
os.remove(f"{path}/{file}")
|
||||
shutil.copyfile(blank, f"{path}/{file}")
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Файл mp3 был заменён на пустой: {path}/{file}")
|
||||
|
||||
return True
|
||||
|
||||
def remove_AI_voiceover(config: Dict[str, Any]):
|
||||
blank = "./resources/blank.mp3"
|
||||
path = "./decompiled/res/raw"
|
||||
files = [
|
||||
"reputation_1.mp3",
|
||||
"reputation_2.mp3",
|
||||
"reputation_3.mp3",
|
||||
"sound_beta_1.mp3",
|
||||
"sound_create_blog_1.mp3",
|
||||
"sound_create_blog_2.mp3",
|
||||
"sound_create_blog_3.mp3",
|
||||
"sound_create_blog_4.mp3",
|
||||
"sound_create_blog_5.mp3",
|
||||
"sound_create_blog_6.mp3",
|
||||
"sound_create_blog_reputation_1.mp3",
|
||||
"sound_create_blog_reputation_2.mp3",
|
||||
"sound_create_blog_reputation_3.mp3",
|
||||
"sound_create_blog_reputation_4.mp3",
|
||||
"sound_create_blog_reputation_5.mp3",
|
||||
"sound_create_blog_reputation_6.mp3",
|
||||
]
|
||||
def do_remove_language_files(self, config: Dict[str, Any]):
|
||||
path = "./decompiled/res"
|
||||
folders = [
|
||||
"values-af",
|
||||
"values-am",
|
||||
"values-ar",
|
||||
"values-as",
|
||||
"values-az",
|
||||
"values-b+es+419",
|
||||
"values-b+sr+Latn",
|
||||
"values-be",
|
||||
"values-bg",
|
||||
"values-bn",
|
||||
"values-bs",
|
||||
"values-ca",
|
||||
"values-cs",
|
||||
"values-da",
|
||||
"values-de",
|
||||
"values-el",
|
||||
"values-en-rAU",
|
||||
"values-en-rCA",
|
||||
"values-en-rGB",
|
||||
"values-en-rIN",
|
||||
"values-en-rXC",
|
||||
"values-es",
|
||||
"values-es-rGT",
|
||||
"values-es-rUS",
|
||||
"values-et",
|
||||
"values-eu",
|
||||
"values-fa",
|
||||
"values-fi",
|
||||
"values-fr",
|
||||
"values-fr-rCA",
|
||||
"values-gl",
|
||||
"values-gu",
|
||||
"values-hi",
|
||||
"values-hr",
|
||||
"values-hu",
|
||||
"values-hy",
|
||||
"values-in",
|
||||
"values-is",
|
||||
"values-it",
|
||||
"values-iw",
|
||||
"values-ja",
|
||||
"values-ka",
|
||||
"values-kk",
|
||||
"values-km",
|
||||
"values-kn",
|
||||
"values-ko",
|
||||
"values-ky",
|
||||
"values-lo",
|
||||
"values-lt",
|
||||
"values-lv",
|
||||
"values-mk",
|
||||
"values-ml",
|
||||
"values-mn",
|
||||
"values-mr",
|
||||
"values-ms",
|
||||
"values-my",
|
||||
"values-nb",
|
||||
"values-ne",
|
||||
"values-nl",
|
||||
"values-or",
|
||||
"values-pa",
|
||||
"values-pl",
|
||||
"values-pt",
|
||||
"values-pt-rBR",
|
||||
"values-pt-rPT",
|
||||
"values-ro",
|
||||
"values-si",
|
||||
"values-sk",
|
||||
"values-sl",
|
||||
"values-sq",
|
||||
"values-sr",
|
||||
"values-sv",
|
||||
"values-sw",
|
||||
"values-ta",
|
||||
"values-te",
|
||||
"values-th",
|
||||
"values-tl",
|
||||
"values-tr",
|
||||
"values-uk",
|
||||
"values-ur",
|
||||
"values-uz",
|
||||
"values-vi",
|
||||
"values-zh",
|
||||
"values-zh-rCN",
|
||||
"values-zh-rHK",
|
||||
"values-zh-rTW",
|
||||
"values-zu",
|
||||
"values-watch",
|
||||
]
|
||||
|
||||
for file in files:
|
||||
if os.path.exists(f"{path}/{file}"):
|
||||
os.remove(f"{path}/{file}")
|
||||
shutil.copyfile(blank, f"{path}/{file}")
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Файл mp3 был заменён на пустой: {path}/{file}")
|
||||
for folder in folders:
|
||||
if os.path.exists(f"{path}/{folder}"):
|
||||
shutil.rmtree(f"{path}/{folder}")
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Удалена директория: {path}/{folder}")
|
||||
return True
|
||||
|
||||
return True
|
||||
def do_remove_drawable_files(self, config: Dict[str, Any]):
|
||||
path = "./decompiled/res"
|
||||
folders = [
|
||||
"drawable-en-hdpi",
|
||||
"drawable-en-ldpi",
|
||||
"drawable-en-mdpi",
|
||||
"drawable-en-xhdpi",
|
||||
"drawable-en-xxhdpi",
|
||||
"drawable-en-xxxhdpi",
|
||||
"drawable-ldrtl-hdpi",
|
||||
"drawable-ldrtl-mdpi",
|
||||
"drawable-ldrtl-xhdpi",
|
||||
"drawable-ldrtl-xxhdpi",
|
||||
"drawable-ldrtl-xxxhdpi",
|
||||
"drawable-tr-anydpi",
|
||||
"drawable-tr-hdpi",
|
||||
"drawable-tr-ldpi",
|
||||
"drawable-tr-mdpi",
|
||||
"drawable-tr-xhdpi",
|
||||
"drawable-tr-xxhdpi",
|
||||
"drawable-tr-xxxhdpi",
|
||||
"drawable-watch",
|
||||
"layout-watch",
|
||||
]
|
||||
|
||||
for folder in folders:
|
||||
if os.path.exists(f"{path}/{folder}"):
|
||||
shutil.rmtree(f"{path}/{folder}")
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Удалена директория: {path}/{folder}")
|
||||
return True
|
||||
|
||||
def remove_language_files(config: Dict[str, Any]):
|
||||
path = "./decompiled/res"
|
||||
folders = [
|
||||
"values-af",
|
||||
"values-am",
|
||||
"values-ar",
|
||||
"values-as",
|
||||
"values-az",
|
||||
"values-b+es+419",
|
||||
"values-b+sr+Latn",
|
||||
"values-be",
|
||||
"values-bg",
|
||||
"values-bn",
|
||||
"values-bs",
|
||||
"values-ca",
|
||||
"values-cs",
|
||||
"values-da",
|
||||
"values-de",
|
||||
"values-el",
|
||||
"values-en-rAU",
|
||||
"values-en-rCA",
|
||||
"values-en-rGB",
|
||||
"values-en-rIN",
|
||||
"values-en-rXC",
|
||||
"values-es",
|
||||
"values-es-rGT",
|
||||
"values-es-rUS",
|
||||
"values-et",
|
||||
"values-eu",
|
||||
"values-fa",
|
||||
"values-fi",
|
||||
"values-fr",
|
||||
"values-fr-rCA",
|
||||
"values-gl",
|
||||
"values-gu",
|
||||
"values-hi",
|
||||
"values-hr",
|
||||
"values-hu",
|
||||
"values-hy",
|
||||
"values-in",
|
||||
"values-is",
|
||||
"values-it",
|
||||
"values-iw",
|
||||
"values-ja",
|
||||
"values-ka",
|
||||
"values-kk",
|
||||
"values-km",
|
||||
"values-kn",
|
||||
"values-ko",
|
||||
"values-ky",
|
||||
"values-lo",
|
||||
"values-lt",
|
||||
"values-lv",
|
||||
"values-mk",
|
||||
"values-ml",
|
||||
"values-mn",
|
||||
"values-mr",
|
||||
"values-ms",
|
||||
"values-my",
|
||||
"values-nb",
|
||||
"values-ne",
|
||||
"values-nl",
|
||||
"values-or",
|
||||
"values-pa",
|
||||
"values-pl",
|
||||
"values-pt",
|
||||
"values-pt-rBR",
|
||||
"values-pt-rPT",
|
||||
"values-ro",
|
||||
"values-si",
|
||||
"values-sk",
|
||||
"values-sl",
|
||||
"values-sq",
|
||||
"values-sr",
|
||||
"values-sv",
|
||||
"values-sw",
|
||||
"values-ta",
|
||||
"values-te",
|
||||
"values-th",
|
||||
"values-tl",
|
||||
"values-tr",
|
||||
"values-uk",
|
||||
"values-ur",
|
||||
"values-uz",
|
||||
"values-vi",
|
||||
"values-zh",
|
||||
"values-zh-rCN",
|
||||
"values-zh-rHK",
|
||||
"values-zh-rTW",
|
||||
"values-zu",
|
||||
"values-watch",
|
||||
]
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
actions = [
|
||||
(
|
||||
self.remove_unknown_files,
|
||||
"Удаление неизвестных файлов...",
|
||||
self.do_remove_unknown_files,
|
||||
),
|
||||
(
|
||||
self.remove_drawable_files,
|
||||
"Удаление директорий drawable-xx...",
|
||||
self.do_remove_drawable_files,
|
||||
),
|
||||
(
|
||||
self.compress_png_files,
|
||||
"Сжатие PNG файлов...",
|
||||
self.do_compress_png_files,
|
||||
),
|
||||
(
|
||||
self.remove_language_files,
|
||||
"Удаление языков...",
|
||||
self.do_remove_language_files,
|
||||
),
|
||||
(
|
||||
self.remove_AI_voiceover,
|
||||
"Удаление ИИ озвучки...",
|
||||
self.do_remove_AI_voiceover,
|
||||
),
|
||||
(
|
||||
self.remove_debug_lines,
|
||||
"Удаление дебаг линий...",
|
||||
self.do_remove_debug_lines,
|
||||
),
|
||||
]
|
||||
|
||||
for folder in folders:
|
||||
if os.path.exists(f"{path}/{folder}"):
|
||||
shutil.rmtree(f"{path}/{folder}")
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Удалена директория: {path}/{folder}")
|
||||
return True
|
||||
for enabled, message, action in actions:
|
||||
if enabled:
|
||||
tqdm.write(message)
|
||||
action(base)
|
||||
|
||||
|
||||
def remove_drawable_files(config: Dict[str, Any]):
|
||||
path = "./decompiled/res"
|
||||
folders = [
|
||||
"drawable-en-hdpi",
|
||||
"drawable-en-ldpi",
|
||||
"drawable-en-mdpi",
|
||||
"drawable-en-xhdpi",
|
||||
"drawable-en-xxhdpi",
|
||||
"drawable-en-xxxhdpi",
|
||||
"drawable-ldrtl-hdpi",
|
||||
"drawable-ldrtl-mdpi",
|
||||
"drawable-ldrtl-xhdpi",
|
||||
"drawable-ldrtl-xxhdpi",
|
||||
"drawable-ldrtl-xxxhdpi",
|
||||
"drawable-tr-anydpi",
|
||||
"drawable-tr-hdpi",
|
||||
"drawable-tr-ldpi",
|
||||
"drawable-tr-mdpi",
|
||||
"drawable-tr-xhdpi",
|
||||
"drawable-tr-xxhdpi",
|
||||
"drawable-tr-xxxhdpi",
|
||||
"drawable-watch",
|
||||
"layout-watch",
|
||||
]
|
||||
|
||||
for folder in folders:
|
||||
if os.path.exists(f"{path}/{folder}"):
|
||||
shutil.rmtree(f"{path}/{folder}")
|
||||
if config.get("verbose", False):
|
||||
tqdm.write(f"Удалена директория: {path}/{folder}")
|
||||
return True
|
||||
|
||||
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
if config.remove_unknown_files:
|
||||
tqdm.write(f"Удаление неизвестных файлов...")
|
||||
remove_unknown_files(config, base)
|
||||
|
||||
if config.remove_drawable_files:
|
||||
tqdm.write(f"Удаление директорий drawable-xx...")
|
||||
remove_drawable_files(base)
|
||||
|
||||
if config.compress_png_files:
|
||||
tqdm.write(f"Сжатие PNG файлов...")
|
||||
compress_png_files(base)
|
||||
|
||||
if config.remove_language_files:
|
||||
tqdm.write(f"Удаление языков...")
|
||||
remove_language_files(base)
|
||||
|
||||
if config.remove_AI_voiceover:
|
||||
tqdm.write(f"Удаление ИИ озвучки...")
|
||||
remove_AI_voiceover(base)
|
||||
|
||||
if config.remove_debug_lines:
|
||||
tqdm.write(f"Удаление дебаг линий...")
|
||||
remove_debug_lines(base)
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
+33
-34
@@ -5,44 +5,43 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
|
||||
# imports
|
||||
__author__ = "Kentai Radiquum <radiquum@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
import textwrap
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, Any
|
||||
from typing import Any, Dict
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.smali_parser import (
|
||||
find_smali_method_end,
|
||||
find_smali_method_start,
|
||||
get_smali_lines,
|
||||
replace_smali_method_body,
|
||||
)
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchTemplate
|
||||
from utils.smali_parser import (find_smali_method_end, find_smali_method_start,
|
||||
get_smali_lines, replace_smali_method_body)
|
||||
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig): ...
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
path = "./decompiled/smali_classes2/com/swiftsoft/anixartd/Prefs.smali"
|
||||
replacement = [
|
||||
f"\t{line}\n"
|
||||
for line in textwrap.dedent(
|
||||
"""\
|
||||
.locals 0
|
||||
const/4 p0, 0x1
|
||||
return p0
|
||||
"""
|
||||
).splitlines()
|
||||
]
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
replacement = [f'\t{line}\n' for line in textwrap.dedent("""\
|
||||
.locals 0
|
||||
const/4 p0, 0x1
|
||||
return p0
|
||||
""").splitlines()]
|
||||
lines = get_smali_lines(path)
|
||||
for index, line in enumerate(lines):
|
||||
if line.find("IS_SPONSOR") >= 0:
|
||||
method_start = find_smali_method_start(lines, index)
|
||||
method_end = find_smali_method_end(lines, index)
|
||||
new_content = replace_smali_method_body(
|
||||
lines, method_start, method_end, replacement
|
||||
)
|
||||
|
||||
path = "./decompiled/smali_classes2/com/swiftsoft/anixartd/Prefs.smali"
|
||||
lines = get_smali_lines(path)
|
||||
for index, line in enumerate(lines):
|
||||
if line.find("IS_SPONSOR") >= 0:
|
||||
method_start = find_smali_method_start(lines, index)
|
||||
method_end = find_smali_method_end(lines, index)
|
||||
new_content = replace_smali_method_body(
|
||||
lines, method_start, method_end, replacement
|
||||
)
|
||||
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
file.writelines(new_content)
|
||||
return True
|
||||
with open(path, "w", encoding="utf-8") as file:
|
||||
file.writelines(new_content)
|
||||
return True
|
||||
|
||||
@@ -5,48 +5,52 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
|
||||
# imports
|
||||
__author__ = "Kentai Radiquum <radiquum@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
import os
|
||||
from tqdm import tqdm
|
||||
from lxml import etree
|
||||
from typing import Dict, Any
|
||||
from typing import Any, Dict
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from lxml import etree
|
||||
from pydantic import Field
|
||||
from tqdm import tqdm
|
||||
|
||||
from utils.config import PatchTemplate
|
||||
from utils.smali_parser import get_smali_lines, save_smali_lines
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig): ...
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
attributes = [
|
||||
"paddingTop",
|
||||
"paddingBottom",
|
||||
"paddingStart",
|
||||
"paddingEnd",
|
||||
"layout_width",
|
||||
"layout_height",
|
||||
"layout_marginTop",
|
||||
"layout_marginBottom",
|
||||
"layout_marginStart",
|
||||
"layout_marginEnd",
|
||||
]
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
|
||||
beta_banner_xml = "./decompiled/res/layout/item_beta.xml"
|
||||
if os.path.exists(beta_banner_xml):
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(beta_banner_xml, parser)
|
||||
root = tree.getroot()
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
beta_banner_xml = "./decompiled/res/layout/item_beta.xml"
|
||||
attributes = [
|
||||
"paddingTop",
|
||||
"paddingBottom",
|
||||
"paddingStart",
|
||||
"paddingEnd",
|
||||
"layout_width",
|
||||
"layout_height",
|
||||
"layout_marginTop",
|
||||
"layout_marginBottom",
|
||||
"layout_marginStart",
|
||||
"layout_marginEnd",
|
||||
]
|
||||
|
||||
for attr in attributes:
|
||||
if base.get("verbose", False):
|
||||
tqdm.write(f"set {attr} = 0.0dip")
|
||||
root.set(f"{{{base['xml_ns']['android']}}}{attr}", "0.0dip")
|
||||
if os.path.exists(beta_banner_xml):
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(beta_banner_xml, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
tree.write(
|
||||
beta_banner_xml, pretty_print=True, xml_declaration=True, encoding="utf-8"
|
||||
)
|
||||
for attr in attributes:
|
||||
if base.get("verbose", False):
|
||||
tqdm.write(f"set {attr} = 0.0dip")
|
||||
root.set(f"{{{base['xml_ns']['android']}}}{attr}", "0.0dip")
|
||||
|
||||
return True
|
||||
tree.write(
|
||||
beta_banner_xml,
|
||||
pretty_print=True,
|
||||
xml_declaration=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
+92
-94
@@ -6,116 +6,114 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = -1
|
||||
|
||||
# imports
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
import os
|
||||
from tqdm import tqdm
|
||||
from typing import Any, Dict
|
||||
|
||||
from lxml import etree
|
||||
from typing import Dict, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.config import PatchTemplate
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=-1)
|
||||
package_name: str = Field("com.wowlikon.anixart", description="Название пакета")
|
||||
|
||||
# Patch
|
||||
def rename_dir(src, dst):
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
os.rename(src, dst)
|
||||
def rename_dir(self, src, dst):
|
||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||||
os.rename(src, dst)
|
||||
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
for root, dirs, files in os.walk("./decompiled"):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
for root, dirs, files in os.walk("./decompiled"):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
if os.path.isfile(file_path):
|
||||
try: # Изменяем имя пакета в файлах
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
file_contents = file.read()
|
||||
|
||||
if os.path.isfile(file_path):
|
||||
try: # Изменяем имя пакета в файлах
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
file_contents = file.read()
|
||||
new_contents = file_contents.replace(
|
||||
"com.swiftsoft.anixartd", self.package_name
|
||||
)
|
||||
new_contents = new_contents.replace(
|
||||
"com/swiftsoft/anixartd",
|
||||
self.package_name.replace(".", "/"),
|
||||
).replace(
|
||||
"com/swiftsoft",
|
||||
"/".join(self.package_name.split(".")[:2]),
|
||||
)
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(new_contents)
|
||||
except:
|
||||
pass
|
||||
|
||||
new_contents = file_contents.replace(
|
||||
"com.swiftsoft.anixartd", config.package_name
|
||||
)
|
||||
new_contents = new_contents.replace(
|
||||
"com/swiftsoft/anixartd",
|
||||
config.package_name.replace(".", "/"),
|
||||
).replace(
|
||||
"com/swiftsoft",
|
||||
"/".join(config.package_name.split(".")[:2]),
|
||||
)
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(new_contents)
|
||||
except:
|
||||
pass
|
||||
# Изменяем названия папок
|
||||
if os.path.exists("./decompiled/smali/com/swiftsoft/anixartd"):
|
||||
self.rename_dir(
|
||||
"./decompiled/smali/com/swiftsoft/anixartd",
|
||||
os.path.join(
|
||||
"./decompiled", "smali", self.package_name.replace(".", "/")
|
||||
),
|
||||
)
|
||||
if os.path.exists("./decompiled/smali_classes2/com/swiftsoft/anixartd"):
|
||||
self.rename_dir(
|
||||
"./decompiled/smali_classes2/com/swiftsoft/anixartd",
|
||||
os.path.join(
|
||||
"./decompiled",
|
||||
"smali_classes2",
|
||||
self.package_name.replace(".", "/"),
|
||||
),
|
||||
)
|
||||
if os.path.exists("./decompiled/smali_classes4/com/swiftsoft"):
|
||||
self.rename_dir(
|
||||
"./decompiled/smali_classes4/com/swiftsoft",
|
||||
os.path.join(
|
||||
"./decompiled",
|
||||
"smali_classes4",
|
||||
"/".join(self.package_name.split(".")[:2]),
|
||||
),
|
||||
)
|
||||
|
||||
# Изменяем названия папок
|
||||
if os.path.exists("./decompiled/smali/com/swiftsoft/anixartd"):
|
||||
rename_dir(
|
||||
"./decompiled/smali/com/swiftsoft/anixartd",
|
||||
os.path.join(
|
||||
"./decompiled", "smali", config.package_name.replace(".", "/")
|
||||
),
|
||||
)
|
||||
if os.path.exists("./decompiled/smali_classes2/com/swiftsoft/anixartd"):
|
||||
rename_dir(
|
||||
"./decompiled/smali_classes2/com/swiftsoft/anixartd",
|
||||
os.path.join(
|
||||
"./decompiled",
|
||||
"smali_classes2",
|
||||
config.package_name.replace(".", "/"),
|
||||
),
|
||||
)
|
||||
if os.path.exists("./decompiled/smali_classes4/com/swiftsoft"):
|
||||
rename_dir(
|
||||
"./decompiled/smali_classes4/com/swiftsoft",
|
||||
os.path.join(
|
||||
"./decompiled",
|
||||
"smali_classes4",
|
||||
"/".join(config.package_name.split(".")[:2]),
|
||||
),
|
||||
)
|
||||
# rename_dir(
|
||||
# "./decompiled/smali_classes3/com/swiftsoft/anixartd",
|
||||
# os.path.join(
|
||||
# "./decompiled",
|
||||
# "smali_classes3",
|
||||
# config["new_package_name"].replace(".", "/"),
|
||||
# ),
|
||||
# )
|
||||
|
||||
# rename_dir(
|
||||
# "./decompiled/smali_classes3/com/swiftsoft/anixartd",
|
||||
# os.path.join(
|
||||
# "./decompiled",
|
||||
# "smali_classes3",
|
||||
# config["new_package_name"].replace(".", "/"),
|
||||
# ),
|
||||
# )
|
||||
# Замена названия пакета для smali_classes4
|
||||
for root, dirs, files in os.walk("./decompiled/smali_classes4/"):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
|
||||
# Замена названия пакета для smali_classes4
|
||||
for root, dirs, files in os.walk("./decompiled/smali_classes4/"):
|
||||
for filename in files:
|
||||
file_path = os.path.join(root, filename)
|
||||
if os.path.isfile(file_path):
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
file_contents = file.read()
|
||||
|
||||
if os.path.isfile(file_path):
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
file_contents = file.read()
|
||||
new_contents = file_contents.replace(
|
||||
"com/swiftsoft",
|
||||
"/".join(self.package_name.split(".")[:-1]),
|
||||
)
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(new_contents)
|
||||
except:
|
||||
pass
|
||||
|
||||
new_contents = file_contents.replace(
|
||||
"com/swiftsoft",
|
||||
"/".join(config.package_name.split(".")[:-1]),
|
||||
)
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(new_contents)
|
||||
except:
|
||||
pass
|
||||
# Скрытие входа по Google и VK (НЕ РАБОТАЮТ В МОДАХ)
|
||||
file_path = "./decompiled/res/layout/fragment_sign_in.xml"
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
# Скрытие входа по Google и VK (НЕ РАБОТАЮТ В МОДАХ)
|
||||
file_path = "./decompiled/res/layout/fragment_sign_in.xml"
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
last_linear = root.xpath("//LinearLayout/LinearLayout[4]")[0]
|
||||
last_linear.set(f"{{{base['xml_ns']['android']}}}visibility", "gone")
|
||||
|
||||
last_linear = root.xpath("//LinearLayout/LinearLayout[4]")[0]
|
||||
last_linear.set(f"{{{base['xml_ns']['android']}}}visibility", "gone")
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
+42
-37
@@ -7,56 +7,61 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
from typing import Any, Dict, List
|
||||
|
||||
# imports
|
||||
from lxml import etree
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
from tqdm import tqdm
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.config import PatchTemplate
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
items: List[str] = Field(["home", "discover", "feed", "bookmarks", "profile"], description="Список элементов в панели навигации")
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
file_path = "./decompiled/res/menu/bottom.xml"
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
items: List[str] = Field(
|
||||
["home", "discover", "feed", "bookmarks", "profile"],
|
||||
description="Список элементов в панели навигации",
|
||||
)
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
file_path = "./decompiled/res/menu/bottom.xml"
|
||||
|
||||
# Получение элементов панели навигации
|
||||
items = root.findall("item", namespaces=base['xml_ns'])
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
def get_id_suffix(item):
|
||||
full_id = item.get(f"{{{base['xml_ns']['android']}}}id")
|
||||
return full_id.split("tab_")[-1] if full_id else None
|
||||
# Получение элементов панели навигации
|
||||
items = root.findall("item", namespaces=base["xml_ns"])
|
||||
|
||||
items_by_id = {get_id_suffix(item): item for item in items}
|
||||
existing_order = [get_id_suffix(item) for item in items]
|
||||
def get_id_suffix(item):
|
||||
full_id = item.get(f"{{{base['xml_ns']['android']}}}id")
|
||||
return full_id.split("tab_")[-1] if full_id else None
|
||||
|
||||
# Размещение в новом порядке
|
||||
ordered_items = []
|
||||
for key in config.items:
|
||||
if key in items_by_id:
|
||||
ordered_items.append(items_by_id[key])
|
||||
items_by_id = {get_id_suffix(item): item for item in items}
|
||||
existing_order = [get_id_suffix(item) for item in items]
|
||||
|
||||
# Если есть не указанные в конфиге они помещаются в конец списка
|
||||
extra = [i for i in items if get_id_suffix(i) not in config.items]
|
||||
if extra:
|
||||
tqdm.write("⚠Найдены лишние элементы: " + str([get_id_suffix(i) for i in extra]))
|
||||
ordered_items.extend(extra)
|
||||
# Размещение в новом порядке
|
||||
ordered_items = []
|
||||
for key in self.items:
|
||||
if key in items_by_id:
|
||||
ordered_items.append(items_by_id[key])
|
||||
|
||||
for i in root.findall("item", namespaces=base['xml_ns']):
|
||||
root.remove(i)
|
||||
# Если есть не указанные в конфиге они помещаются в конец списка
|
||||
extra = [i for i in items if get_id_suffix(i) not in self.items]
|
||||
if extra:
|
||||
tqdm.write(
|
||||
"⚠Найдены лишние элементы: " + str([get_id_suffix(i) for i in extra])
|
||||
)
|
||||
ordered_items.extend(extra)
|
||||
|
||||
for item in ordered_items:
|
||||
root.append(item)
|
||||
for i in root.findall("item", namespaces=base["xml_ns"]):
|
||||
root.remove(i)
|
||||
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
for item in ordered_items:
|
||||
root.append(item)
|
||||
|
||||
return True
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
|
||||
return True
|
||||
|
||||
+26
-23
@@ -5,38 +5,41 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
from typing import Any, Dict
|
||||
|
||||
# imports
|
||||
from tqdm import tqdm
|
||||
from lxml import etree
|
||||
from typing import Dict, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.config import PatchTemplate
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig): ...
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
|
||||
file_path = "./decompiled/res/layout/release_info.xml"
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
file_path = "./decompiled/res/layout/release_info.xml"
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
# Список тегов, к которым нужно добавить атрибут
|
||||
tags = ["TextView", "at.blogc.android.views.ExpandableTextView"]
|
||||
# Список тегов, к которым нужно добавить атрибут
|
||||
tags = ["TextView", "at.blogc.android.views.ExpandableTextView"]
|
||||
|
||||
for tag in tags:
|
||||
for element in root.findall(f".//{tag}", namespaces=base["xml_ns"]):
|
||||
# Проверяем, нет ли уже атрибута
|
||||
if f"{{{base['xml_ns']['android']}}}textIsSelectable" not in element.attrib:
|
||||
element.set(f"{{{base['xml_ns']['android']}}}textIsSelectable", "true")
|
||||
for tag in tags:
|
||||
for element in root.findall(f".//{tag}", namespaces=base["xml_ns"]):
|
||||
# Проверяем, нет ли уже атрибута
|
||||
if (
|
||||
f"{{{base['xml_ns']['android']}}}textIsSelectable"
|
||||
not in element.attrib
|
||||
):
|
||||
element.set(
|
||||
f"{{{base['xml_ns']['android']}}}textIsSelectable", "true"
|
||||
)
|
||||
|
||||
# Сохраняем
|
||||
tree.write(file_path, encoding="utf-8", xml_declaration=True, pretty_print=True)
|
||||
# Сохраняем
|
||||
tree.write(file_path, encoding="utf-8", xml_declaration=True, pretty_print=True)
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
+104
-95
@@ -20,119 +20,128 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
|
||||
# imports
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
import shutil
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from lxml import etree
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.config import PatchTemplate
|
||||
from utils.public import insert_after_public
|
||||
|
||||
#Config
|
||||
# Config
|
||||
DEFAULT_MENU = {
|
||||
"Мы в социальных сетях": [
|
||||
{
|
||||
"title": "wowlikon",
|
||||
"description": "Разработчик",
|
||||
"url": "https://t.me/wowlikon",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false"
|
||||
},
|
||||
{
|
||||
"title": "Kentai Radiquum",
|
||||
"description": "Разработчик",
|
||||
"url": "https://t.me/radiquum",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false"
|
||||
},
|
||||
{
|
||||
"title": "Мы в Telegram",
|
||||
"description": "Подпишитесь на канал, чтобы быть в курсе последних новостей.",
|
||||
"url": "https://t.me/http_teapod",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false"
|
||||
}
|
||||
],
|
||||
"Прочее": [
|
||||
{
|
||||
"title": "Помочь проекту",
|
||||
"description": "Вы можете помочь нам с идеями, написанием кода или тестированием.",
|
||||
"url": "https://git.wowlikon.tech/anixart-mod",
|
||||
"icon": "@drawable/ic_custom_crown",
|
||||
"icon_space_reserved": "false"
|
||||
}
|
||||
]
|
||||
"Мы в социальных сетях": [
|
||||
{
|
||||
"title": "wowlikon",
|
||||
"description": "Разработчик",
|
||||
"url": "https://t.me/wowlikon",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false",
|
||||
},
|
||||
{
|
||||
"title": "Kentai Radiquum",
|
||||
"description": "Разработчик",
|
||||
"url": "https://t.me/radiquum",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false",
|
||||
},
|
||||
{
|
||||
"title": "Мы в Telegram",
|
||||
"description": "Подпишитесь на канал, чтобы быть в курсе последних новостей.",
|
||||
"url": "https://t.me/http_teapod",
|
||||
"icon": "@drawable/ic_custom_telegram",
|
||||
"icon_space_reserved": "false",
|
||||
},
|
||||
],
|
||||
"Прочее": [
|
||||
{
|
||||
"title": "Помочь проекту",
|
||||
"description": "Вы можете помочь нам с идеями, написанием кода или тестированием.",
|
||||
"url": "https://git.wowlikon.tech/anixart-mod",
|
||||
"icon": "@drawable/ic_custom_crown",
|
||||
"icon_space_reserved": "false",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
class Config(PatchConfig):
|
||||
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
version: str = Field(" by wowlikon", description="Суффикс версии")
|
||||
menu: Dict[str, List[Dict[str, str]]] = Field(DEFAULT_MENU, description="Меню")
|
||||
|
||||
# Patch
|
||||
def make_category(ns, name, items):
|
||||
cat = etree.Element("PreferenceCategory", nsmap=ns)
|
||||
cat.set(f"{{{ns['android']}}}title", name)
|
||||
cat.set(f"{{{ns['app']}}}iconSpaceReserved", "false")
|
||||
def make_category(self, ns, name, items):
|
||||
cat = etree.Element("PreferenceCategory", nsmap=ns)
|
||||
cat.set(f"{{{ns['android']}}}title", name)
|
||||
cat.set(f"{{{ns['app']}}}iconSpaceReserved", "false")
|
||||
|
||||
for item in items:
|
||||
pref = etree.SubElement(cat, "Preference", nsmap=ns)
|
||||
pref.set(f"{{{ns['android']}}}title", item["title"])
|
||||
pref.set(f"{{{ns['android']}}}summary", item["description"])
|
||||
pref.set(f"{{{ns['app']}}}icon", item["icon"])
|
||||
pref.set(f"{{{ns['app']}}}iconSpaceReserved", item["icon_space_reserved"])
|
||||
for item in items:
|
||||
pref = etree.SubElement(cat, "Preference", nsmap=ns)
|
||||
pref.set(f"{{{ns['android']}}}title", item["title"])
|
||||
pref.set(f"{{{ns['android']}}}summary", item["description"])
|
||||
pref.set(f"{{{ns['app']}}}icon", item["icon"])
|
||||
pref.set(f"{{{ns['app']}}}iconSpaceReserved", item["icon_space_reserved"])
|
||||
|
||||
intent = etree.SubElement(pref, "intent", nsmap=ns)
|
||||
intent.set(f"{{{ns['android']}}}action", "android.intent.action.VIEW")
|
||||
intent.set(f"{{{ns['android']}}}data", item["url"])
|
||||
intent.set(f"{{{ns['app']}}}iconSpaceReserved", item["icon_space_reserved"])
|
||||
intent = etree.SubElement(pref, "intent", nsmap=ns)
|
||||
intent.set(f"{{{ns['android']}}}action", "android.intent.action.VIEW")
|
||||
intent.set(f"{{{ns['android']}}}data", item["url"])
|
||||
intent.set(f"{{{ns['app']}}}iconSpaceReserved", item["icon_space_reserved"])
|
||||
|
||||
return cat
|
||||
return cat
|
||||
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
# Добавление кастомных иконок
|
||||
shutil.copy(
|
||||
"./resources/ic_custom_crown.xml",
|
||||
"./decompiled/res/drawable/ic_custom_crown.xml",
|
||||
)
|
||||
insert_after_public("warning_error_counter_background", "ic_custom_crown")
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
# Добавление кастомных иконок
|
||||
shutil.copy(
|
||||
"./resources/ic_custom_crown.xml",
|
||||
"./decompiled/res/drawable/ic_custom_crown.xml",
|
||||
)
|
||||
insert_after_public("warning_error_counter_background", "ic_custom_crown")
|
||||
|
||||
shutil.copy(
|
||||
"./resources/ic_custom_telegram.xml",
|
||||
"./decompiled/res/drawable/ic_custom_telegram.xml",
|
||||
)
|
||||
insert_after_public("warning_error_counter_background", "ic_custom_telegram")
|
||||
shutil.copy(
|
||||
"./resources/ic_custom_telegram.xml",
|
||||
"./decompiled/res/drawable/ic_custom_telegram.xml",
|
||||
)
|
||||
insert_after_public("warning_error_counter_background", "ic_custom_telegram")
|
||||
|
||||
file_path = "./decompiled/res/xml/preference_main.xml"
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
file_path = "./decompiled/res/xml/preference_main.xml"
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
# Вставка новых пунктов перед последним
|
||||
pos = root.index(root[-1])
|
||||
for section, items in config.menu.items():
|
||||
root.insert(pos, make_category(base["xml_ns"], section, items))
|
||||
pos += 1
|
||||
# Вставка новых пунктов перед последним
|
||||
pos = root.index(root[-1])
|
||||
for section, items in self.menu.items():
|
||||
root.insert(pos, self.make_category(base["xml_ns"], section, items))
|
||||
pos += 1
|
||||
|
||||
# Сохранение
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
# Сохранение
|
||||
tree.write(file_path, pretty_print=True, xml_declaration=True, encoding="utf-8")
|
||||
|
||||
# Добавление суффикса версии
|
||||
filepaths = [
|
||||
"./decompiled/smali_classes2/com/swiftsoft/anixartd/ui/activity/UpdateActivity.smali",
|
||||
"./decompiled/smali_classes2/com/swiftsoft/anixartd/ui/fragment/main/preference/MainPreferenceFragment.smali",
|
||||
]
|
||||
for filepath in filepaths:
|
||||
content = ""
|
||||
with open(filepath, "r", encoding="utf-8") as file:
|
||||
for line in file.readlines():
|
||||
if '"\u0412\u0435\u0440\u0441\u0438\u044f'.encode("unicode_escape").decode() in line:
|
||||
content += line[:line.rindex('"')] + config.version + line[line.rindex('"'):]
|
||||
else:
|
||||
content += line
|
||||
with open(filepath, "w", encoding="utf-8") as file:
|
||||
file.write(content)
|
||||
return True
|
||||
# Добавление суффикса версии
|
||||
filepaths = [
|
||||
"./decompiled/smali_classes2/com/swiftsoft/anixartd/ui/activity/UpdateActivity.smali",
|
||||
"./decompiled/smali_classes2/com/swiftsoft/anixartd/ui/fragment/main/preference/MainPreferenceFragment.smali",
|
||||
]
|
||||
for filepath in filepaths:
|
||||
content = ""
|
||||
with open(filepath, "r", encoding="utf-8") as file:
|
||||
for line in file.readlines():
|
||||
if (
|
||||
'"\u0412\u0435\u0440\u0441\u0438\u044f'.encode(
|
||||
"unicode_escape"
|
||||
).decode()
|
||||
in line
|
||||
):
|
||||
content += (
|
||||
line[: line.rindex('"')]
|
||||
+ self.version
|
||||
+ line[line.rindex('"') :]
|
||||
)
|
||||
else:
|
||||
content += line
|
||||
with open(filepath, "w", encoding="utf-8") as file:
|
||||
file.write(content)
|
||||
return True
|
||||
|
||||
+23
-23
@@ -11,43 +11,43 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
from typing import Any, Dict
|
||||
|
||||
# imports
|
||||
from tqdm import tqdm
|
||||
from lxml import etree
|
||||
from typing import Dict, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.config import PatchTemplate
|
||||
|
||||
#Config
|
||||
DEFAULT_FORMATS = {
|
||||
"share_channel_text": "Канал: «%1$s»\n%2$schannel/%3$d",
|
||||
"share_collection_text": "Коллекция: «%1$s»\n%2$scollection/%3$d",
|
||||
"share_profile_text": "Профиль пользователя «%1$s»\n%2$sprofile/%3$d",
|
||||
"share_release_text": "Релиз: «%1$s»\n%2$srelease/%3$d"
|
||||
"share_release_text": "Релиз: «%1$s»\n%2$srelease/%3$d",
|
||||
}
|
||||
|
||||
class Config(PatchConfig):
|
||||
format: Dict[str, str] = Field(DEFAULT_FORMATS, description="Строки для замены в `strings.xml`")
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
format: Dict[str, str] = Field(
|
||||
DEFAULT_FORMATS, description="Строки для замены в `strings.xml`"
|
||||
)
|
||||
|
||||
file_path = "./decompiled/res/values/strings.xml"
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
file_path = "./decompiled/res/values/strings.xml"
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(file_path, parser)
|
||||
root = tree.getroot()
|
||||
|
||||
# Обновляем значения
|
||||
for string in root.findall("string"):
|
||||
name = string.get("name")
|
||||
if name in config.format:
|
||||
string.text = config.format[name]
|
||||
# Обновляем значения
|
||||
for string in root.findall("string"):
|
||||
name = string.get("name")
|
||||
if name in self.format:
|
||||
string.text = self.format[name]
|
||||
|
||||
# Сохраняем обратно
|
||||
tree.write(file_path, encoding="utf-8", xml_declaration=True, pretty_print=True)
|
||||
# Сохраняем обратно
|
||||
tree.write(file_path, encoding="utf-8", xml_declaration=True, pretty_print=True)
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
@@ -6,33 +6,30 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
from typing import Any, Dict, List
|
||||
|
||||
# imports
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.config import PatchTemplate
|
||||
from utils.public import insert_after_id, insert_after_public
|
||||
from utils.smali_parser import float_to_hex
|
||||
from utils.public import (
|
||||
insert_after_public,
|
||||
insert_after_id,
|
||||
)
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
speeds: List[float] = Field([9.0], description="Список пользовательских скоростей воспроизведения")
|
||||
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
assert float_to_hex(1.5) == "0x3fc00000"
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
speeds: List[float] = Field(
|
||||
[9.0], description="Список пользовательских скоростей воспроизведения"
|
||||
)
|
||||
|
||||
last = "speed75"
|
||||
for speed in config.speeds:
|
||||
insert_after_public(last, f"speed{int(float(speed)*10)}")
|
||||
insert_after_id(last, f"speed{int(float(speed)*10)}")
|
||||
last = f"speed{int(float(speed)*10)}"
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
assert float_to_hex(1.5) == "0x3fc00000"
|
||||
|
||||
return False
|
||||
last = "speed75"
|
||||
for speed in self.speeds:
|
||||
insert_after_public(last, f"speed{int(float(speed)*10)}")
|
||||
insert_after_id(last, f"speed{int(float(speed)*10)}")
|
||||
last = f"speed{int(float(speed)*10)}"
|
||||
|
||||
return False
|
||||
|
||||
+21
-16
@@ -4,7 +4,7 @@
|
||||
|
||||
Каждый патч должен независеть от других патчей и проверять себя при применении. Он не должен вернуть True, если есть проблемы.
|
||||
На данный момент каждый патч должен иметь функцию `apply`, которая принимает на вход конфигурацию и возвращает True или False.
|
||||
И модель `Config`, которая наследуется от `PatchConfig` (поле `enabled` добавлять не нужно).
|
||||
И модель `Config`, которая наследуется от `PatchTemplate` (поле `enabled` добавлять не нужно).
|
||||
Это позволяет упростить конфигурирование и проверять типы данных, и делать значения по умолчанию.
|
||||
При успешном применении патча, функция apply должна вернуть True, иначе False.
|
||||
Ошибка будет интерпретирована как False. С выводом ошибки в консоль.
|
||||
@@ -25,24 +25,29 @@ python ./main.py build --verbose
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0 # Приоритет патча, чем выше, тем раньше он будет применен
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
from typing import Any, Dict, List
|
||||
|
||||
# imports
|
||||
from tqdm import tqdm
|
||||
from typing import Dict, List, Any
|
||||
from pydantic import Field
|
||||
from tqdm import tqdm
|
||||
|
||||
from utils.config import PatchConfig
|
||||
from utils.config import PatchTemplate
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
|
||||
class Patch(PatchTemplate):
|
||||
example: bool = Field(True, description="Пример кастомного параметра")
|
||||
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool: # Анотации типов для удобства, читаемости и поддержки IDE
|
||||
tqdm.write("Вывод информации через tqdm, чтобы не мешать прогресс-бару")
|
||||
tqdm.write("Пример включен" if config.example else "Пример отключен")
|
||||
if base["verbose"]:
|
||||
tqdm.write("Для вывода подробной и отладочной информации используйте флаг --verbose")
|
||||
return True
|
||||
def apply(
|
||||
self, base: Dict[str, Any]
|
||||
) -> bool: # Анотации типов для удобства, читаемости и поддержки IDE
|
||||
priority: int = Field(
|
||||
frozen=True, exclude=True, default=0
|
||||
) # Приоритет патча, чем выше, тем раньше он будет применен
|
||||
tqdm.write("Вывод информации через tqdm, чтобы не мешать прогресс-бару")
|
||||
tqdm.write("Пример включен" if self.example else "Пример отключен")
|
||||
if base["verbose"]:
|
||||
tqdm.write(
|
||||
"Для вывода подробной и отладочной информации используйте флаг --verbose"
|
||||
)
|
||||
return True
|
||||
|
||||
+31
-32
@@ -11,22 +11,20 @@
|
||||
}
|
||||
"""
|
||||
|
||||
priority = 0
|
||||
|
||||
# imports
|
||||
import os
|
||||
__author__ = "wowlikon <wowlikon@gmail.com>"
|
||||
__version__ = "1.0.0"
|
||||
import shutil
|
||||
from pydantic import Field
|
||||
from typing import Dict, Any
|
||||
from utils.config import PatchConfig
|
||||
from utils.smali_parser import (
|
||||
find_and_replace_smali_line,
|
||||
get_smali_lines,
|
||||
save_smali_lines
|
||||
)
|
||||
from typing import Any, Dict
|
||||
|
||||
#Config
|
||||
class Config(PatchConfig):
|
||||
from pydantic import Field
|
||||
|
||||
from utils.config import PatchTemplate
|
||||
from utils.smali_parser import (find_and_replace_smali_line, get_smali_lines,
|
||||
save_smali_lines)
|
||||
|
||||
|
||||
class Patch(PatchTemplate):
|
||||
priority: int = Field(frozen=True, exclude=True, default=0)
|
||||
title: str = Field("Anixarty", description="Заголовок")
|
||||
description: str = Field("Описание", description="Описание")
|
||||
link_text: str = Field("МЫ В TELEGRAM", description="Текст ссылки")
|
||||
@@ -34,23 +32,24 @@ class Config(PatchConfig):
|
||||
skip_text: str = Field("Пропустить", description="Текст кнопки пропуска")
|
||||
title_bg_color: str = Field("#FFFFFF", description="Цвет фона заголовка")
|
||||
|
||||
def apply(self, base: Dict[str, Any]) -> bool:
|
||||
file_path = "./decompiled/smali_classes2/com/swiftsoft/anixartd/ui/activity/MainActivity.smali"
|
||||
# Добавление ресурсов окна первого входа
|
||||
shutil.copy("./resources/avatar.png", "./decompiled/assets/avatar.png")
|
||||
shutil.copy(
|
||||
"./resources/OpenSans-Regular.ttf",
|
||||
"./decompiled/assets/OpenSans-Regular.ttf",
|
||||
)
|
||||
shutil.copytree("./resources/smali_classes4/", "./decompiled/smali_classes4/")
|
||||
|
||||
# Patch
|
||||
def apply(config: Config, base: Dict[str, Any]) -> bool:
|
||||
# Добавление ресурсов окна первого входа
|
||||
shutil.copy("./resources/avatar.png", "./decompiled/assets/avatar.png")
|
||||
shutil.copy(
|
||||
"./resources/OpenSans-Regular.ttf",
|
||||
"./decompiled/assets/OpenSans-Regular.ttf",
|
||||
)
|
||||
shutil.copytree(
|
||||
"./resources/smali_classes4/", "./decompiled/smali_classes4/"
|
||||
)
|
||||
method = "invoke-super {p0}, Lmoxy/MvpAppCompatActivity;->onResume()V"
|
||||
lines = get_smali_lines(file_path)
|
||||
lines = find_and_replace_smali_line(
|
||||
lines,
|
||||
method,
|
||||
method
|
||||
+ "\ninvoke-static {p0}, Lcom/swiftsoft/about/$2;->oooooo(Landroid/content/Context;)Ljava/lang/Object;",
|
||||
)
|
||||
save_smali_lines(file_path, lines)
|
||||
|
||||
file_path = "./decompiled/smali_classes2/com/swiftsoft/anixartd/ui/activity/MainActivity.smali"
|
||||
method = "invoke-super {p0}, Lmoxy/MvpAppCompatActivity;->onResume()V"
|
||||
lines = get_smali_lines(file_path)
|
||||
lines = find_and_replace_smali_line(lines, method, method+"\ninvoke-static {p0}, Lcom/swiftsoft/about/$2;->oooooo(Landroid/content/Context;)Ljava/lang/Object;")
|
||||
save_smali_lines(file_path, lines)
|
||||
|
||||
return True
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user