summarylogtreecommitdiffstats
path: root/util.py
blob: 7468aa47c693231508384c9c135f2128136946b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env python3
"""
vscodium-prod-patcher by fnrir

I'm tired of having multiple versions of different "patchers" for different
versions of VSCodium. So fuck it. This one is configurable and universal.

I might add backups later. rn i'm tired

trans rights are human rights

polska gurom

GitHub: <https://github.com/fnr1r>
Matrix: @fnrir:matrix.org
Mastodon: <https://tech.lgbt/@fnrir>
Mail: fnr1r0@protonmail.com
Credit Card: 5809820978480085 Date: 06/21 CVC: 420
IP address: [::1]
"""

import json
import os
from pathlib import Path
import sys
import toml
from typing import Any

ENCODING = "UTF-8"

NAME = "vscodium-prod-patcher"

HOOKS_DIR = Path("/etc/pacman.d/hooks")
HOOK_FILE = HOOKS_DIR / f"98-{NAME}-action.hook"

HOOK_TEMPLATE = """[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
{targets}

[Action]
Description = [{name}] VSCodium installation hook
Exec = /usr/share/{name}/util.py patch
When = PostTransaction
NeedsTargets
"""
HOOK_TARGET_TEMPLATE = "Target = {pkg}"

DATA_DIR = Path("/usr/share") / NAME
CONFIG_PATH = DATA_DIR / "config.toml"

CONFIG_TEMPLATE = {
    "packages": {},
    "patch": {
        "extensions_source": "openvsx",
        "extra_features": False,
        "use_xdg": False,
    }
}

EXTENSIONS_OPENVSX_GALLERY = {
    "serviceUrl": "https://open-vsx.org/vscode/gallery",
    "itemUrl": "https://open-vsx.org/vscode/item",
}
EXTENSIONS_OPENVSX_TRUSTED = ["https://open-vsx.org"]
EXTENSIONS_MS_GALLERY = {
    "serviceUrl": "https://marketplace.visualstudio.com/_apis/public/gallery",
    "cacheUrl": "https://vscode.blob.core.windows.net/gallery/index",
    "itemUrl": "https://marketplace.visualstudio.com/items"
}

COMMAND = ""
CONFIG: dict[str, Any] = []

def einfo(*args, **kwargs):
    print("::", *args, **kwargs)

def json_load(path: Path):
    with open(path, "rt", encoding=ENCODING) as file:
        return json.load(file)

def json_save(path: Path, obj: Any, *args, **kwargs):
    with open(path, "wt", encoding=ENCODING) as file:
        json.dump(obj, file, *args, **kwargs)

def toml_load(path: Path):
    with open(path, "rt", encoding=ENCODING) as file:
        return toml.load(file)

def load_config():
    try:
        return toml_load(CONFIG_PATH)
    except FileNotFoundError:
        return CONFIG_TEMPLATE

def argparse():
    global CONFIG
    CONFIG = load_config()
    global COMMAND
    COMMAND = sys.argv[1]

def install_hook():
    packages: dict[str, str] = CONFIG["packages"]
    if not packages:
        if HOOK_FILE.exists():
            os.remove(HOOK_FILE)
        einfo(
            "No VSCodium package defined.",
            f"Try to configure {NAME} by creating",
            CONFIG_PATH,
        )
        return
    targets = "\n".join([
        HOOK_TARGET_TEMPLATE.format(pkg=pkg)
        for pkg in packages.keys()
    ])
    hook_contents = HOOK_TEMPLATE.format(
        name=NAME, targets=targets,
    )
    HOOKS_DIR.mkdir(parents=True, exist_ok=True)
    with open(HOOK_FILE, "wt", encoding=ENCODING) as file:
        file.write(hook_contents)

def patch_features(product: dict[str, Any], config: dict[str, Any]):
    try:
        extra_features = config["extra_features"]
    except KeyError:
        return
    if not extra_features:
        return
    patch_path = DATA_DIR / "features-patch.json"
    patch_data = json_load(patch_path)
    for key in patch_data.keys():
        product[key] = patch_data[key]

def patch_data_dir(product: dict[str, Any], config: dict[str, Any]):
    try:
        use_xdg = config["use_xdg"]
    except KeyError:
        return
    if not use_xdg:
        return
    product["dataFolderName"] = ".local/share/vscodium"

def patch_marketplace(product: dict[str, Any], config: dict[str, Any]):
    try:
        marketplace = config["extensions_source"]
    except KeyError:
        return
    gallery = {}
    domains_remove = False
    match marketplace:
        case "openvsx":
            gallery = EXTENSIONS_OPENVSX_GALLERY
        case "microsoft":
            gallery = EXTENSIONS_MS_GALLERY
            domains_remove = True
        case _:
            einfo("Invalid marketplace:", marketplace)
            return
    if gallery:
        product["extensionsGallery"] = gallery
    tdkey = "linkProtectionTrustedDomains"
    if domains_remove:
        cur_domains: list[str] = product[tdkey]
        for domain in EXTENSIONS_OPENVSX_TRUSTED:
            cur_domains.remove(domain)
        if not cur_domains:
            product.pop(tdkey)
        else:
            product[tdkey] = cur_domains

def patch_pkg(pkg: str, editor_path: Path, config: dict[str, Any]):
    product_path = editor_path / "resources/app/product.json"
    product = json_load(product_path)
    # Patch 1: Features
    patch_features(product, config)
    # Patch 2: Data dir
    patch_data_dir(product, config)
    # Patch 3: Marketplace
    patch_marketplace(product, config)
    json_save(product_path, product, indent=2)

def patch_pkgs():
    changed_packages = [
        line.strip()
        for line in sys.stdin
    ]
    if not changed_packages:
        return
    packages: dict[str, str] = CONFIG["packages"]
    changed_packages = [
        pkg
        for pkg in changed_packages
        if pkg in packages.keys()
    ]
    config = CONFIG["patch"]
    for pkg in changed_packages:
        einfo("Patching", pkg)
        patch_pkg(pkg, Path(packages[pkg]), config)

def main():
    argparse()
    match COMMAND:
        case "hook":
            install_hook()
        case "patch":
            patch_pkgs()
        case _:
            sys.exit(1)

if __name__ == "__main__":
    main()