mirror of
https://github.com/Radiquum/anixart-patcher.git
synced 2025-09-03 17:55:33 +05:00
Compare commits
6 commits
6981ae7d84
...
e12967efaf
Author | SHA1 | Date | |
---|---|---|---|
e12967efaf | |||
2c8af07a67 | |||
2fef0a32b7 | |||
ea5a1a2bed | |||
bf59b2bbae | |||
8269d0d5b6 |
13 changed files with 409 additions and 50 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -7,4 +7,6 @@ decompiled
|
|||
dist
|
||||
__pycache__
|
||||
keystore.jks
|
||||
help
|
||||
help
|
||||
dev_patches
|
||||
dev.config.json
|
19
config.py
19
config.py
|
@ -1,3 +1,4 @@
|
|||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
@ -42,19 +43,27 @@ class Config(TypedDict):
|
|||
xml_ns: ConfigXmlNS
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(prog="anixart patcher")
|
||||
parser.add_argument("--config", help="path to config.json file", default="config.json")
|
||||
parser.add_argument("--no-decompile", action="store_true")
|
||||
parser.add_argument("--no-compile", action="store_true")
|
||||
parser.add_argument("--patch", action="store_true", help="only patch decompiled")
|
||||
parser.add_argument("--sign", action="store_true", help="only sign compiled apk")
|
||||
parser.add_argument("--init", action="store_true", help="init a new patch")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def load_config() -> Config:
|
||||
config = None
|
||||
|
||||
if not os.path.exists("config.json"):
|
||||
if not os.path.exists(args.config):
|
||||
log.exception("file `config.json` is not found!")
|
||||
exit(1)
|
||||
|
||||
with open("./config.json", "r", encoding="utf-8") as file:
|
||||
with open(args.config, "r", encoding="utf-8") as file:
|
||||
config = json.loads(file.read())
|
||||
|
||||
log.setLevel(config.get("log_level", "NOTSET").upper())
|
||||
|
||||
return config
|
||||
|
||||
|
||||
config = load_config()
|
||||
config = load_config()
|
54
main.py
54
main.py
|
@ -1,28 +1,21 @@
|
|||
from scripts.download_tools import check_and_download_all_tools
|
||||
from scripts.select_apk import get_apks, select_apk
|
||||
from scripts.select_patches import apply_patches, get_patches, select_patches
|
||||
from scripts.utils import check_java_version, compile_apk, decompile_apk, sign_apk
|
||||
from config import log, console
|
||||
from scripts.utils import check_java_version, compile_apk, decompile_apk, sign_apk, init_patch
|
||||
from config import args, config, log, console
|
||||
|
||||
from time import time
|
||||
import math
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(prog='anixart patcher')
|
||||
parser.add_argument("--no-decompile", action='store_true')
|
||||
parser.add_argument("--no-compile", action='store_true')
|
||||
|
||||
parser.add_argument("--patch", action='store_true')
|
||||
parser.add_argument("--sign", action='store_true')
|
||||
import yaml
|
||||
|
||||
|
||||
def patch():
|
||||
patches = get_patches()
|
||||
patches = select_patches(patches)
|
||||
statuses = apply_patches(patches)
|
||||
|
||||
statuses_ok = []
|
||||
statuses_err = []
|
||||
|
||||
for status in statuses:
|
||||
if status["status"]:
|
||||
console.print(f"{status['name']}: ✔", style="bold green")
|
||||
|
@ -30,51 +23,58 @@ def patch():
|
|||
else:
|
||||
console.print(f"{status['name']}: ✘", style="bold red")
|
||||
statuses_err.append(status["name"])
|
||||
|
||||
return patches, statuses_ok, statuses_err
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
|
||||
check_and_download_all_tools()
|
||||
check_java_version()
|
||||
|
||||
if args.init:
|
||||
init_patch()
|
||||
exit(0)
|
||||
|
||||
if not args.patch:
|
||||
if not args.patch and not args.sign:
|
||||
apks = get_apks()
|
||||
if not apks:
|
||||
log.fatal(f"apks folder is empty")
|
||||
exit(1)
|
||||
apk = select_apk(apks)
|
||||
if not apk:
|
||||
log.info('cancelled')
|
||||
log.info("cancelled")
|
||||
exit(0)
|
||||
else:
|
||||
elif args.patch:
|
||||
patch()
|
||||
exit(0)
|
||||
|
||||
if args.sign:
|
||||
sign_apk(f"{apk.removesuffix('.apk')}-patched.apk")
|
||||
elif args.sign:
|
||||
apkFileName = None
|
||||
with open(
|
||||
f"{config["folders"]["decompiled"]}/apktool.yml", "r", encoding="utf-8"
|
||||
) as f:
|
||||
data = yaml.load(f.read(), Loader=yaml.Loader)
|
||||
apkFileName = data.get("apkFileName", None)
|
||||
if not apkFileName:
|
||||
log.fatal(
|
||||
f"can't find apk file name in {config['folders']['decompiled']}/apktool.yml"
|
||||
)
|
||||
exit(1)
|
||||
sign_apk(f"{apkFileName.removesuffix('.apk')}-patched.apk")
|
||||
exit(0)
|
||||
|
||||
start_time = time()
|
||||
|
||||
if not args.no_decompile:
|
||||
decompile_apk(apk)
|
||||
|
||||
patches, statuses_ok, statuses_err = patch()
|
||||
|
||||
if not args.no_compile:
|
||||
compile_apk(f"{apk.removesuffix(".apk")}-patched.apk")
|
||||
|
||||
end_time = time()
|
||||
|
||||
if not args.no_compile:
|
||||
sign_apk(f"{apk.removesuffix(".apk")}-patched.apk")
|
||||
|
||||
|
||||
log.info("Finished")
|
||||
log.info(f"install this apk file: {apk.removesuffix(".apk")}-patched-aligned-signed.apk")
|
||||
log.info(
|
||||
f"install this apk file: `{config["folders"]["dist"]}/{apk.removesuffix(".apk")}-patched-aligned-signed.apk`"
|
||||
)
|
||||
log.info(f"used and successful patches: {", ".join(statuses_ok)}")
|
||||
log.info(f"used and unsuccessful patches: {", ".join(statuses_err)}")
|
||||
log.info(f"time taken: {math.floor(end_time - start_time)}s")
|
||||
|
|
16
patches/change_navigation_bar.config.json
Normal file
16
patches/change_navigation_bar.config.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"portrait": [
|
||||
"home",
|
||||
"discover",
|
||||
"feed",
|
||||
"bookmarks",
|
||||
"profile"
|
||||
],
|
||||
"landscape": [
|
||||
"home",
|
||||
"discover",
|
||||
"feed",
|
||||
"bookmarks",
|
||||
"profile"
|
||||
]
|
||||
}
|
52
patches/change_navigation_bar.py
Normal file
52
patches/change_navigation_bar.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
"""Move and replace navigation bar tabs"""
|
||||
|
||||
# patch settings
|
||||
# priority, default: 0
|
||||
priority = 0
|
||||
|
||||
# imports
|
||||
## bundled
|
||||
from typing import TypedDict
|
||||
|
||||
## installed
|
||||
from lxml import etree
|
||||
|
||||
## custom
|
||||
from config import config, log
|
||||
|
||||
|
||||
# Patch
|
||||
class PatchConfig_ChangeNavigationBar(TypedDict):
|
||||
portrait: list[str]
|
||||
landscape: list[str]
|
||||
|
||||
|
||||
allowed_items = ["home", "discover", "feed", "bookmarks", "profile"]
|
||||
|
||||
|
||||
def modify_menu(menu: list[str], path: str) -> None:
|
||||
for item in menu:
|
||||
if item not in allowed_items:
|
||||
log.warning(f"menu item `{item}` is not allowed, removing from list")
|
||||
menu.remove(item)
|
||||
|
||||
root = etree.Element("menu", nsmap={"android": config['xml_ns']['android']})
|
||||
for item in menu:
|
||||
element = etree.SubElement(root, "item")
|
||||
element.set(f"{{{config['xml_ns']['android']}}}icon", f"@drawable/nav_{item}")
|
||||
element.set(f"{{{config['xml_ns']['android']}}}id", f"@id/tab_{item}")
|
||||
element.set(f"{{{config['xml_ns']['android']}}}title", f"@string/{item}")
|
||||
|
||||
tree = etree.ElementTree(root)
|
||||
tree.write(
|
||||
path,
|
||||
pretty_print=True,
|
||||
xml_declaration=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def apply(patch_conf: PatchConfig_ChangeNavigationBar) -> bool:
|
||||
modify_menu(patch_conf["portrait"], f"{config['folders']['decompiled']}/res/menu/bottom.xml")
|
||||
modify_menu(patch_conf["landscape"], f"{config['folders']['decompiled']}/res/menu/navigation_rail_menu.xml")
|
||||
return True
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"new_package_name": "com.radiquum.anixart"
|
||||
"new_package_name": "com.swiftsoft.anixartd"
|
||||
}
|
|
@ -1,3 +1,9 @@
|
|||
{
|
||||
"keep_dirs": ["META-INF", "kotlin"]
|
||||
"remove_language_files": true,
|
||||
"remove_AI_voiceover": true,
|
||||
"remove_debug_lines": true,
|
||||
"remove_drawable_files": false,
|
||||
"remove_unknown_files": false,
|
||||
"remove_unknown_files_keep_dirs": ["META-INF", "kotlin"],
|
||||
"compress_png_files": false
|
||||
}
|
30
patches/compress.md
Normal file
30
patches/compress.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Compress
|
||||
|
||||
Patch is used to compress and remove resources to reduce final apk filesize
|
||||
|
||||
## settings (compress.config.json)
|
||||
|
||||
- remove_unknown_files: true/false - removes files from decompiled/unknown directory
|
||||
- remove_unknown_files_keep_dirs: list[str] - keeps specified directories in decompiled/unknown directory
|
||||
- remove_debug_lines: true/false - removes `.line n` from decompiled smali files
|
||||
- remove_AI_voiceover: true/false - replaces voiceover of anixa character with a blank mp3
|
||||
- compress_png_files: true/false - compresses PNG in decompiled/res directory
|
||||
- remove_drawable_files: true/false - removes some drawable-* from decompiled/res
|
||||
- remove_language_files: true/false - removes all languages except ru and en
|
||||
|
||||
## efficiency
|
||||
|
||||
Tested with 9.0 Beta 7
|
||||
|
||||
diff = original apk bytes - patch apk bytes
|
||||
|
||||
| Setting | Filesize | Diff | % |
|
||||
| :----------- | :-------------------: | :-----------------: | :-: |
|
||||
| None | 17092 bytes - 17.1 MB | - | - |
|
||||
| Compress PNG | 17072 bytes - 17.1 MB | 20 bytes - 0.0 MB | 0.11% |
|
||||
| Remove files | 17020 bytes - 17.0 MB | 72 bytes - 0.1 MB | 0.42% |
|
||||
| Remove draws | 16940 bytes - 16.9 MB | 152 bytes - 0.2 MB | 0.89% |
|
||||
| Remove lines | 16444 bytes - 16.4 MB | 648 bytes - 0.7 MB | 3.79% |
|
||||
| Remove ai vo | 15812 bytes - 15.8 MB | 1280 bytes - 1.3 MB | 7.49% |
|
||||
| Remove langs | 15764 bytes - 15.7 MB | 1328 bytes - 1.3 MB | 7.76% |
|
||||
| All enabled | 13592 bytes - 13.6 MB | 3500 bytes - 4.8 MB | 20.5% |
|
|
@ -18,10 +18,16 @@ from scripts.smali_parser import get_smali_lines, save_smali_lines
|
|||
|
||||
# Patch
|
||||
class PatchConfig_Compress(TypedDict):
|
||||
keep_dirs: list[str]
|
||||
remove_language_files: bool
|
||||
remove_AI_voiceover: bool
|
||||
remove_debug_lines: bool
|
||||
remove_drawable_files: bool
|
||||
remove_unknown_files: bool
|
||||
remove_unknown_files_keep_dirs: list[str]
|
||||
compress_png_files: bool
|
||||
|
||||
|
||||
def remove_files(patch_config: PatchConfig_Compress):
|
||||
def remove_unknown_files(patch_config: PatchConfig_Compress):
|
||||
path = f"{config['folders']['decompiled']}/unknown"
|
||||
|
||||
items = os.listdir(path)
|
||||
|
@ -31,7 +37,7 @@ def remove_files(patch_config: PatchConfig_Compress):
|
|||
os.remove(item_path)
|
||||
log.debug(f"[COMPRESS] removed file: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
if item not in patch_config["keep_dirs"]:
|
||||
if item not in patch_config["remove_unknown_files_keep_dirs"]:
|
||||
shutil.rmtree(item_path)
|
||||
log.debug(f"[COMPRESS] removed directory: {item_path}")
|
||||
|
||||
|
@ -47,7 +53,7 @@ def remove_debug_lines():
|
|||
file_content = get_smali_lines(file_path)
|
||||
new_content = []
|
||||
for line in file_content:
|
||||
if line.find(".line") >= 0 or line.find(".source") >= 0:
|
||||
if line.find(".line") >= 0:
|
||||
continue
|
||||
new_content.append(line)
|
||||
save_smali_lines(file_path, new_content)
|
||||
|
@ -87,7 +93,7 @@ def compress_png(png_path: str):
|
|||
exit(1)
|
||||
|
||||
|
||||
def compress_pngs():
|
||||
def compress_png_files():
|
||||
compressed = []
|
||||
for root, _, files in os.walk(f"{config['folders']['decompiled']}"):
|
||||
if len(files) < 0:
|
||||
|
@ -101,10 +107,190 @@ def compress_pngs():
|
|||
log.debug(f"[COMPRESS] {len(compressed)} pngs have been compressed")
|
||||
|
||||
|
||||
def apply(patch_config: PatchConfig_Compress) -> bool:
|
||||
def remove_AI_voiceover():
|
||||
blank = f"{config['folders']['patches']}/resources/blank.mp3"
|
||||
path = f"{config['folders']['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",
|
||||
]
|
||||
|
||||
remove_files(patch_config)
|
||||
remove_debug_lines()
|
||||
# compress_pngs()
|
||||
for file in files:
|
||||
if os.path.exists(f"{path}/{file}"):
|
||||
os.remove(f"{path}/{file}")
|
||||
shutil.copyfile(blank, f"{path}/{file}")
|
||||
log.debug(f"[COMPRESS] {file} has been replaced with blank.mp3")
|
||||
|
||||
log.debug(f"[COMPRESS] ai voiceover has been removed")
|
||||
|
||||
|
||||
def remove_language_files():
|
||||
path = f"{config['folders']['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 folder in folders:
|
||||
if os.path.exists(f"{path}/{folder}"):
|
||||
shutil.rmtree(f"{path}/{folder}")
|
||||
log.debug(f"[COMPRESS] {folder} has been removed")
|
||||
|
||||
|
||||
def remove_drawable_files():
|
||||
path = f"{config['folders']['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}")
|
||||
log.debug(f"[COMPRESS] {folder} has been removed")
|
||||
|
||||
|
||||
def apply(patch_config: PatchConfig_Compress) -> bool:
|
||||
if patch_config['remove_unknown_files']:
|
||||
log.info("[COMPRESS] removing unknown files")
|
||||
remove_unknown_files(patch_config)
|
||||
|
||||
if patch_config["remove_drawable_files"]:
|
||||
log.info("[COMPRESS] removing drawable-xx dirs")
|
||||
remove_drawable_files()
|
||||
|
||||
if patch_config["compress_png_files"]:
|
||||
log.info("[COMPRESS] compressing PNGs")
|
||||
compress_png_files()
|
||||
|
||||
if patch_config["remove_language_files"]:
|
||||
log.info("[COMPRESS] removing languages")
|
||||
remove_language_files()
|
||||
|
||||
if patch_config["remove_AI_voiceover"]:
|
||||
log.info("[COMPRESS] removing AI voiceover")
|
||||
remove_AI_voiceover()
|
||||
|
||||
if patch_config["remove_debug_lines"]:
|
||||
log.info("[COMPRESS] stripping debug lines")
|
||||
remove_debug_lines()
|
||||
|
||||
return True
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"base_url": "https://anix-api.wah.su/",
|
||||
"base_url": "https://api-s.anixsekai.com/",
|
||||
"values": [
|
||||
{
|
||||
"file_path": "smali_classes2/com/swiftsoft/anixartd/network/api/ConfigApi.smali",
|
||||
|
|
BIN
patches/resources/blank.mp3
Normal file
BIN
patches/resources/blank.mp3
Normal file
Binary file not shown.
|
@ -1,4 +1,5 @@
|
|||
requests
|
||||
lxml
|
||||
rich
|
||||
beaupy
|
||||
beaupy
|
||||
pyyaml
|
|
@ -2,6 +2,7 @@ import os
|
|||
import shutil
|
||||
import subprocess
|
||||
from config import log, config
|
||||
from beaupy import prompt
|
||||
|
||||
|
||||
def check_java_version():
|
||||
|
@ -80,10 +81,16 @@ def compile_apk(apk: str):
|
|||
def sign_apk(apk: str):
|
||||
log.info(f"sign and align apk: `{apk}`")
|
||||
|
||||
if os.path.exists(f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned.apk"):
|
||||
if os.path.exists(
|
||||
f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned.apk"
|
||||
):
|
||||
os.remove(f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned.apk")
|
||||
if os.path.exists(f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned-signed.apk"):
|
||||
os.remove(f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned-signed.apk")
|
||||
if os.path.exists(
|
||||
f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned-signed.apk"
|
||||
):
|
||||
os.remove(
|
||||
f"{config['folders']['dist']}/{apk.removesuffix('.apk')}-aligned-signed.apk"
|
||||
)
|
||||
|
||||
command = ""
|
||||
try:
|
||||
|
@ -111,5 +118,55 @@ def sign_apk(apk: str):
|
|||
stderr=subprocess.PIPE,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log.fatal(f"error of running a command: %s :: %s", command, e.stderr, exc_info=True)
|
||||
log.fatal(
|
||||
f"error of running a command: %s :: %s", command, e.stderr, exc_info=True
|
||||
)
|
||||
exit(1)
|
||||
|
||||
def patch_config_name(patch_name: str) -> str:
|
||||
components = patch_name.split("_")
|
||||
return "PatchConfig_" + "".join(x.title() for x in components)
|
||||
|
||||
def init_patch():
|
||||
if not os.path.exists(config["folders"]["patches"]):
|
||||
log.info(f"creating `patches` folder: {config['folders']['patches']}")
|
||||
os.mkdir(config["folders"]["patches"])
|
||||
|
||||
if not os.path.exists(f"{config['folders']['patches']}/__init__.py"):
|
||||
with open(f"{config['folders']['patches']}/__init__.py", "w") as f:
|
||||
f.write("")
|
||||
|
||||
name = prompt("Patch name: ", lambda x: x.strip().lower().replace(" ", "_"))
|
||||
description = prompt("Patch description: ", lambda x: x.strip())
|
||||
priority = prompt("Patch priority: ", target_type=int, initial_value="0")
|
||||
|
||||
patch_content = f"""\"\"\"{description}\"\"\"
|
||||
|
||||
# patch settings
|
||||
# priority, default: {priority}
|
||||
priority = {priority}
|
||||
|
||||
# imports
|
||||
## bundled
|
||||
from typing import TypedDict
|
||||
|
||||
## custom
|
||||
from config import config, log
|
||||
|
||||
|
||||
# Patch
|
||||
class {patch_config_name(name)}(TypedDict):
|
||||
pass
|
||||
|
||||
|
||||
def apply(patch_conf: {patch_config_name(name)}) -> bool:
|
||||
log.info("patch `{name}` applied, nothing changed")
|
||||
return True
|
||||
"""
|
||||
|
||||
with open(f"{config['folders']['patches']}/{name}.py", "w", encoding="utf-8") as f:
|
||||
f.write(patch_content)
|
||||
with open(f"{config['folders']['patches']}/{name}.config.json", "w", encoding="utf-8") as f:
|
||||
f.write("{}")
|
||||
|
||||
log.info(f"patch `{name}` created")
|
Loading…
Add table
Add a link
Reference in a new issue