diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1f946cd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+/modules.json
+/venv/**
+/.idea/**
+
+/cache/**
+/out/**
+
diff --git a/build.py b/build.py
new file mode 100755
index 0000000..e3d7822
--- /dev/null
+++ b/build.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python3
+# -*- coding:utf-8 -*-
+###
+# File: build.py
+# Project: Kodi-Module-Generator
+# File Created: Monday, 10th May 2021 8:52:42 pm
+# Author: Josh.5 (jsunnex@gmail.com)
+# -----
+# Last Modified: Monday, 10th May 2021 11:34:57 pm
+# Modified By: Josh.5 (jsunnex@gmail.com)
+###
+
+# Requirements = `python3 -m pip install pipgrip`
+
+import json
+import os
+import shutil
+import subprocess
+
+template_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'template')
+out_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'out')
+cache_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'cache')
+
+
+def install_module(module_dir, module_name, version):
+ module_lib_dir = os.path.abspath(os.path.join(module_dir, 'lib'))
+ if not module_lib_dir:
+ os.makedirs(module_lib_dir)
+
+ # First install local
+ cmd = 'python3 -m pip install --user {}=={}'.format(module_name, version)
+ subprocess.call(cmd, shell=True)
+
+ # Then install to module directory
+ cmd = 'python3 -m pip install --ignore-installed --no-dependencies --target={} {}=={}'.format(module_lib_dir,
+ module_name,
+ version)
+ subprocess.call(cmd, shell=True)
+
+
+def update_module_data(module_dir, module_name, module_deps_list):
+ # Read data from module
+ res = subprocess.check_output('python3 -m pip show {}'.format(module_name), shell=True)
+ module_version = ''
+ module_author = ''
+ module_author_email = ''
+ module_license = ''
+ module_summary = ''
+ module_website = 'https://pypi.org/project/{}/'.format(module_name)
+ for line in res.splitlines():
+ x = str(line.decode("utf-8"))
+ print(x)
+ if x.startswith('Version:'):
+ module_version = x.split(': ')[1].strip()
+ elif x.startswith('Author:'):
+ module_author = x.split(': ')[1].strip()
+ elif x.startswith('Author-email:'):
+ module_author_email = x.split(': ')[1].strip()
+ elif x.startswith('License:'):
+ module_license = x.split(': ')[1].strip()
+ elif x.startswith('Summary:'):
+ module_summary = x.split(': ')[1].strip()
+
+ module_xml = os.path.join(module_dir, 'addon.xml')
+ # Read in the file
+ with open(module_xml, 'r') as file:
+ file_data = file.read()
+
+ # Replace the target string with data from pip show
+ if module_author_email:
+ module_author = '{} ({})'.format(module_author, module_author_email)
+ file_data = file_data.replace('PYTHON_MODULE_NAME', module_name)
+ file_data = file_data.replace('PYTHON_MODULE_VERSION', module_version)
+ file_data = file_data.replace('PYTHON_MODULE_AUTHOR', module_author)
+ file_data = file_data.replace('PYTHON_MODULE_LICENSE', module_license)
+ file_data = file_data.replace('PYTHON_MODULE_SUMMARY', module_summary)
+ file_data = file_data.replace('PYTHON_MODULE_DESCRIPTION', module_summary)
+ file_data = file_data.replace('PYTHON_MODULE_WEBSITE', module_website)
+
+ # Append dependencies list
+ template = ' '
+ deps_to_add = ""
+ for dep in module_deps_list:
+ line = template.format(dep.get('name'), dep.get('version'))
+ deps_to_add = "{}\n{}".format(deps_to_add, line)
+ file_data = file_data.replace('PYTHON_MODULE_DEPENDENCIES', deps_to_add)
+
+ # Write the file out again
+ with open(module_xml, 'w') as file:
+ file.write(file_data)
+
+
+def sanitize_module(module_name):
+ module_dir = os.path.join(out_dir, 'script.module.{}'.format(module_name))
+ sanitize_script = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'sanitize.sh')
+ cmd = '{} {}'.format(sanitize_script, module_dir)
+ subprocess.call(cmd, shell=True)
+
+
+def build_script_module(module_name, version, module_deps):
+ module_dir = os.path.join(out_dir, 'script.module.{}'.format(module_name))
+ if not os.path.exists(module_dir):
+ os.makedirs(module_dir)
+
+ if not os.path.exists(os.path.join(module_dir, 'addon.xml')):
+ shutil.copy(os.path.join(template_dir, 'addon.xml'), os.path.join(module_dir, 'addon.xml'))
+
+ if not os.path.exists(os.path.join(module_dir, 'icon.png')):
+ shutil.copy(os.path.join(template_dir, 'icon.png'), os.path.join(module_dir, 'icon.png'))
+
+ # Install modules
+ install_module(module_dir, module_name, version)
+
+ # Sanitize module
+ sanitize_module(module_name)
+
+ # Write addon.xml
+ update_module_data(module_dir, module_name, module_deps)
+
+
+def process_module_deps_list(deps_list):
+ for dep in deps_list:
+ module_name = dep.get('name')
+ module_version = dep.get('version')
+ child_deps = dep.get('dependencies', [])
+
+ # Build module for each item found in depends list
+ process_module_deps_list(child_deps)
+
+ # Build module
+ print("Building Kodi Python module for {} v{}".format(module_name, module_version))
+ build_script_module(module_name, module_version, child_deps)
+
+
+def create_dep_list(module, version):
+ deps_cache = os.path.join(cache_dir, 'deps-{}-{}.json'.format(module, version))
+
+ if not os.path.exists(deps_cache):
+ print("Fetching Dependencies list for {} v{}".format(module, version))
+ cmd = 'pipgrip --tree --json unmanic'.format(module, version)
+ res = subprocess.check_output(cmd, shell=True)
+ decoded = str(res.decode("utf-8"))
+ data = json.loads(decoded)
+
+ with open(deps_cache, 'w') as file:
+ json.dump(data, file, indent=2)
+
+ with open(deps_cache, 'r') as file:
+ deps_list = json.load(file)
+
+ return deps_list
+
+
+def run():
+ if not os.path.exists(template_dir):
+ os.makedirs(template_dir)
+ if not os.path.exists(out_dir):
+ os.makedirs(out_dir)
+ if not os.path.exists(cache_dir):
+ os.makedirs(cache_dir)
+
+ try:
+ with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'modules.json'), 'r') as file:
+ modules = json.load(file)
+ except Exception as e:
+ print("No modules configured... - {}".format(str(e)))
+ modules = []
+
+ for module_data in modules:
+ original_module = module_data.get('module')
+ version = module_data.get('version')
+ deps_list = create_dep_list(original_module, version)
+ process_module_deps_list(deps_list)
+
+
+if __name__ == '__main__':
+ run()
diff --git a/sanitize.sh b/sanitize.sh
new file mode 100755
index 0000000..ac9ac8d
--- /dev/null
+++ b/sanitize.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+
+###
+# File: sanitize.sh
+# Project: Kodi-Module-Generator
+# File Created: Monday, 10th May 2021 11:02:32 pm
+# Author: Josh.5 (jsunnex@gmail.com)
+# -----
+# Last Modified: Monday, 10th May 2021 11:10:23 pm
+# Modified By: Josh.5 (jsunnex@gmail.com)
+###
+
+
+
+addon_root=${@}
+lib_dir="${addon_root}/lib"
+
+
+print_step(){
+ ten=" "
+ spaces="${ten}${ten}${ten}${ten}${ten}${ten}${ten}${ten}${ten}"
+ message=" - ${@}"
+ message="${message:0:70}${spaces:0:$((70 - ${#message}))}"
+ echo -ne "${message}"
+}
+print_sub_step(){
+ ten=" "
+ spaces="${ten}${ten}${ten}${ten}${ten}${ten}${ten}${ten}${ten}"
+ message=" - ${@}"
+ message="${message:0:70}${spaces:0:$((70 - ${#message}))}"
+ echo -ne "${message}"
+}
+mark_step(){
+ RED="\e[31m"
+ GREEN="\e[32m"
+ ENDCOLOR="\e[0m"
+ status_message=""
+ [[ ${1} == 'failed' ]] && status_message="${RED}[FAILED]${ENDCOLOR}"
+ [[ ${1} == 'success' ]] && status_message="${GREEN}[SUCCESS]${ENDCOLOR}"
+ echo -e "${status_message}"
+}
+
+
+# Remove everything except the Python source
+remove_extensions=(
+ "ans"
+ "bz2"
+ "db"
+ "dll"
+ "exe"
+ "gz"
+ "mo"
+ "pyc"
+ "pyo"
+ "so"
+ "xbt"
+ "xpr"
+)
+print_step "Cleaning out unnecessary binary files:"
+mark_step
+for ext in "${remove_extensions[@]}"; do
+ print_sub_step "Delete all '*.${ext}' files..."
+ find "${lib_dir}" -type f -iname "*.${ext}" -delete
+ [[ $? > 0 ]] && mark_step failed && exit 1
+ mark_step success
+done
+print_step "Cleaning out Python cache directories"
+find "${lib_dir}" -type d -name "__pycache__" -exec rm -rf {} +
+[[ $? > 0 ]] && mark_step failed && exit 1
+mark_step success
+print_step "Cleaning out Python 'dist-info' directories"
+find "${lib_dir}" -type d -name "*.dist-info" -exec rm -rf {} +
+[[ $? > 0 ]] && mark_step failed && exit 1
+mark_step success
+
+# Remove Python bin
+print_step "Removing 'bin' Python directory"
+rm -rf "${lib_dir}/bin"
+[[ $? > 0 ]] && mark_step failed && exit 1
+mark_step success
+
+# Ensure all files are not executable
+print_step "Ensure all items in the lib directory are not executable"
+find "${lib_dir}" -type f -exec chmod a-x {} +
+[[ $? > 0 ]] && mark_step failed && exit 1
+mark_step success
+
+exit 0
diff --git a/template/addon.xml b/template/addon.xml
new file mode 100644
index 0000000..8fafb3b
--- /dev/null
+++ b/template/addon.xml
@@ -0,0 +1,23 @@
+
+
+
+ PYTHON_MODULE_DEPENDENCIES
+
+
+
+ PYTHON_MODULE_SUMMARY
+ PYTHON_MODULE_DESCRIPTION
+ PYTHON_MODULE_LICENSE
+ all
+ PYTHON_MODULE_WEBSITE
+ PYTHON_MODULE_WEBSITE
+
+ icon.png
+
+
+
\ No newline at end of file
diff --git a/template/icon.png b/template/icon.png
new file mode 100644
index 0000000..893dda0
Binary files /dev/null and b/template/icon.png differ