package-helpers-cmxsl/helpers/DATA/firefox/ui_branding/wallpaper-replace.py
2026-05-10 19:05:22 -06:00

163 lines
6 KiB
Python

# Copyright (C) 2026 Luis Guzman <ark@switnet.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
import json
import hashlib
import os
import time
import argparse
import sys
def calculate_sha256_hash(file_path):
"""
Calculates the SHA-256 hash of a physical file.
"""
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def build_step(config_path, output_path):
"""
Step 1: Reads config.json, processes the data, and creates build.json.
"""
print(f"--- Step 1: Building standalone artifact ---")
if not os.path.exists(config_path):
print(f"ERROR: Configuration file '{config_path}' not found.")
sys.exit(1)
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
build_data = {}
for item in config.get('replacements',):
item_id = item.get('id_to_replace')
image_path = item.get('local_image_path')
theme = item.get('theme', '').strip()
if not image_path or not os.path.exists(image_path):
print(f"WARNING: Local image not found at '{image_path}' for ID {item_id}. Skipping.")
continue
file_size_bytes = os.path.getsize(image_path)
sha256_hash = calculate_sha256_hash(image_path)
# Get the real file name (e.g., 'trisquel-aramo.webp')
real_file_name = os.path.basename(image_path)
# Create the "disguised" file name to bypass Mozilla's strict schema validation
disguised_file_name = real_file_name.replace('.webp', '.avif')
# Create a 100% schema-compliant entry masking the webp as an avif
build_data[item_id] = {
"title": real_file_name.replace('.webp', ''),
"theme": theme,
"attachment": {
"hash": sha256_hash,
"size": file_size_bytes,
"filename": disguised_file_name,
"location": f"main-workspace/newtab-wallpapers-v2/{disguised_file_name}",
"mimetype": "image/avif" # Masking the mime type for the validator
}
}
# Check if the wallpaper is an "abe" one, to position logo accordingly
if "abe" in real_file_name.lower():
build_data[item_id]["background_position"] = "bottom right"
# Make sure dark theme is used on trisquel-ecne & trisquel-aramo images
if "trisquel-ecne" in real_file_name.lower() or "trisquel-aramo" in real_file_name.lower():
build_data[item_id]["theme"] = "dark"
print(f"Processed: {real_file_name} -> Masked as: {disguised_file_name} -> ID: {item_id}")
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(build_data, f, indent=2, ensure_ascii=False)
print(f"\nSUCCESS: Generated standalone artifact at '{output_path}'.")
def replace_step(source_path, target_path):
"""
Step 2: Injects the build.json data into the target Firefox JSON database.
"""
print(f"\n--- Step 2: Injecting into target JSON ---")
if not os.path.exists(source_path):
print(f"ERROR: Build source file '{source_path}' not found.")
sys.exit(1)
if not os.path.exists(target_path):
print(f"ERROR: Target JSON file '{target_path}' not found.")
sys.exit(1)
with open(source_path, 'r', encoding='utf-8') as f:
build_data = json.load(f)
with open(target_path, 'r', encoding='utf-8') as f:
target_db = json.load(f)
current_timestamp = int(time.time() * 1000)
modified_count = 0
for item in target_db.get('data',):
item_id = item.get('id')
if item_id in build_data:
new_data = build_data[item_id]
# Wipe old custom keys if they exist from previous tests
item.pop('wallpaperUrl', None)
# Apply schema-compliant data
item['attachment'] = new_data['attachment']
item['title'] = new_data['title']
item['schema'] = current_timestamp
item['last_modified'] = current_timestamp
if new_data.get('theme'):
item['theme'] = new_data['theme']
# Apply background_position if it exists in build_data
if new_data.get('background_position'):
item['background_position'] = new_data['background_position']
print(f"Updated ID: {item_id} with file '{new_data['attachment']['filename']}'")
modified_count += 1
if modified_count > 0:
target_db['timestamp'] = current_timestamp
with open(target_path, 'w', encoding='utf-8') as f:
json.dump(target_db, f, indent=2, ensure_ascii=False)
print(f"\nSUCCESS: Modified {modified_count} entries in '{target_path}'.")
else:
print("\nWARNING: No matching IDs found in the target JSON to replace.")
def main():
parser = argparse.ArgumentParser(description="Firefox Newtab Wallpaper Injection Tool")
parser.add_argument('--config', type=str, nargs='?', const='./config.json', help="Step 1: Path to config.json.")
parser.add_argument('--replace', type=str, help="Step 2: Path to target Firefox JSON file to be modified.")
parser.add_argument('--source', type=str, default='./build.json', help="Step 2: Path to the standalone build.json.")
args = parser.parse_args()
if not args.config and not args.replace:
parser.print_help()
sys.exit(1)
if args.config: build_step(args.config, './build.json')
if args.replace: replace_step(args.source, args.replace)
if __name__ == "__main__":
main()