92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
DEFAULT_SCRIPT = ROOT / "services" / "neko" / "provisioning" / "youtube-studio-auto-dismiss.user.js"
|
|
DEFAULT_OUTPUT = ROOT / "services" / "neko" / "provisioning" / "tampermonkey-provisioning.json"
|
|
GREASYFORK_URL = "https://greasyfork.org/scripts/557378-youtube-studio-auto-dismiss/code/YouTube%20Studio%20Auto%20Dismiss.user.js"
|
|
|
|
|
|
def tm_sha256(text: str) -> str:
|
|
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
|
|
|
|
|
def tm_hash(value: Any, seen: list[int] | None = None) -> str:
|
|
if seen is None:
|
|
seen = []
|
|
if value is None:
|
|
return tm_sha256("object:null")
|
|
if isinstance(value, dict):
|
|
ident = id(value)
|
|
if ident in seen:
|
|
raise ValueError("Found circular structure")
|
|
seen.append(ident)
|
|
try:
|
|
parts = [tm_hash(value[key], seen) for key in sorted(value)]
|
|
finally:
|
|
seen.pop()
|
|
return tm_sha256("".join(parts))
|
|
if isinstance(value, list):
|
|
ident = id(value)
|
|
if ident in seen:
|
|
raise ValueError("Found circular structure")
|
|
seen.append(ident)
|
|
try:
|
|
parts = [tm_hash(item, seen) for item in value]
|
|
finally:
|
|
seen.pop()
|
|
return tm_sha256("".join(parts))
|
|
if isinstance(value, bool):
|
|
return tm_sha256(f"boolean:{str(value).lower()}")
|
|
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
return tm_sha256(f"number:{value}")
|
|
if isinstance(value, str):
|
|
return tm_sha256(f"string:{value}")
|
|
raise TypeError(f"Unsupported provisioning value: {type(value)!r}")
|
|
|
|
|
|
def build_payload(script_text: str) -> dict[str, Any]:
|
|
return {
|
|
"version": "1",
|
|
"scripts": [
|
|
{
|
|
"name": "YouTube Studio Auto Dismiss",
|
|
"file_url": GREASYFORK_URL,
|
|
"source": script_text,
|
|
"enabled": True,
|
|
"position": 1,
|
|
}
|
|
],
|
|
"settings": {
|
|
"runtime_content_mode": "content",
|
|
},
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Render Tampermonkey provisioning JSON and hash.")
|
|
parser.add_argument("--script", type=Path, default=DEFAULT_SCRIPT)
|
|
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
|
|
parser.add_argument("--write", action="store_true", help="Write the rendered JSON to --output.")
|
|
args = parser.parse_args()
|
|
|
|
script_text = args.script.read_text(encoding="utf-8").strip() + "\n"
|
|
payload = build_payload(script_text)
|
|
rendered = json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
|
|
if args.write:
|
|
args.output.write_text(rendered, encoding="utf-8")
|
|
print(rendered, end="")
|
|
print(f"hash=1:{tm_hash(payload)}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|