2022-02-02 17:49:48 +01:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Publish a ZIP file to the Kodi repository. """
# Based on code from https://github.com/xbmc/kodi-addon-submitter
from __future__ import absolute_import , division , unicode_literals
import logging
import os
import shutil
import subprocess
import sys
import time
import xml . etree . ElementTree as ET
from pprint import pformat
from tempfile import TemporaryDirectory
from zipfile import ZipFile
import requests
_LOGGER = logging . getLogger ( __name__ )
GH_REPO = ' repo-plugins '
GH_USERNAME = os . getenv ( ' GH_USERNAME ' )
GH_TOKEN = os . getenv ( ' GH_TOKEN ' )
GH_EMAIL = os . getenv ( ' EMAIL ' )
def get_addon_info ( xml : str ) :
""" Parse the passed addon.xml file and extract some information. """
tree = ET . fromstring ( xml )
return {
' id ' : tree . get ( ' id ' ) ,
' name ' : tree . get ( ' name ' ) ,
' version ' : tree . get ( ' version ' ) ,
' description ' : tree . find ( " ./extension[@point= ' xbmc.addon.metadata ' ]/description " ) . text ,
' news ' : tree . find ( " ./extension[@point= ' xbmc.addon.metadata ' ]/news " ) . text ,
' python ' : tree . find ( " ./requires/import[@addon= ' xbmc.python ' ] " ) . get ( ' version ' ) ,
' source ' : tree . find ( " ./extension[@point= ' xbmc.addon.metadata ' ]/source " ) . text ,
}
def user_fork_exists ( repo , gh_username , gh_token ) :
""" Check if the user has a fork of the repository on Github. """
resp = requests . get (
' https://api.github.com/repos/ {} / {} ' . format (
gh_username ,
repo
) ,
headers = { ' Accept ' : ' application/vnd.github.v3+json ' } ,
params = {
' type ' : ' all '
} ,
auth = ( gh_username , gh_token )
)
resp_json = resp . json ( )
return resp . ok and resp_json . get ( ' fork ' )
def create_personal_fork ( repo , gh_username , gh_token ) :
""" Create a personal fork for the official repo on GitHub. """
resp = requests . post (
' https://api.github.com/repos/xbmc/ {} /forks ' . format (
repo
) ,
headers = { ' Accept ' : ' application/vnd.github.v3+json ' } ,
auth = ( gh_username , gh_token )
)
if resp . ok :
elapsed_time = 0
while elapsed_time < 5 * 60 :
if not user_fork_exists ( repo , gh_username , gh_token ) :
time . sleep ( 20 )
elapsed_time + = 20
else :
return
raise Exception ( " Timeout waiting for fork creation exceeded " )
raise Exception ( ' GitHub API error: {} \n {} ' . format ( resp . status_code , resp . text ) )
def shell ( * args ) :
""" Execute a shell command. """
subprocess . run ( args , check = True )
def create_addon_branch ( repo , branch , source , addon_info , gh_username , gh_token , gh_email ) :
""" Create and addon branch in your fork of the respective addon repo. """
cur_dir = os . getcwd ( )
os . chdir ( ' dist ' )
local_branch_name = ' {} @ {} ' . format ( addon_info [ ' id ' ] , branch )
if os . path . isdir ( repo ) :
# We already have a checked out repo locally, update this with upstream code
os . chdir ( repo )
shell ( ' git ' , ' reset ' , ' --hard ' ) # Remove all local changes
shell ( ' git ' , ' remote ' , ' set-branches ' , ' --add ' , ' upstream ' , branch ) # Make sure the upstream branch exists
shell ( ' git ' , ' fetch ' , ' -f ' , ' upstream ' , branch ) # Fetch upstream
else :
# Clone the upstream repo
2022-07-07 15:52:36 +02:00
shell ( ' git ' , ' clone ' , ' --branch ' , branch , ' --origin ' , ' upstream ' , ' --single-branch ' , ' https://github.com/xbmc/ {} .git ' . format ( repo ) )
2022-02-02 17:49:48 +01:00
os . chdir ( repo )
# Create local branch
shell ( ' git ' , ' checkout ' , ' -B ' , local_branch_name , ' upstream/ {} ' . format ( branch ) )
# Remove current code
if os . path . isdir ( addon_info [ ' id ' ] ) :
shutil . rmtree ( addon_info [ ' id ' ] , ignore_errors = False )
# Add new code
shutil . copytree ( source , addon_info [ ' id ' ] )
shell ( ' git ' , ' add ' , ' -- ' , addon_info [ ' id ' ] )
shell ( ' git ' , ' status ' )
# Create a commit with the new code
shell ( ' git ' , ' config ' , ' user.name ' , gh_username )
shell ( ' git ' , ' config ' , ' user.email ' , gh_email )
shell ( ' git ' , ' commit ' , ' -m ' , ' [ {} ] {} ' . format ( addon_info [ ' id ' ] , addon_info [ ' version ' ] ) )
# Push branch to fork
shell ( ' git ' , ' push ' , ' -f ' , ' https:// {} : {} @github.com/ {} / {} .git ' . format ( gh_username , gh_token , gh_username , repo ) , local_branch_name )
# Restore working directory
os . chdir ( cur_dir )
def create_pull_request ( repo , branch , addon_info , gh_username , gh_token ) :
""" Create a pull request in the official repo on GitHub. """
local_branch_name = ' {} @ {} ' . format ( addon_info [ ' id ' ] , branch )
# Check if pull request already exists.
resp = requests . get (
' https://api.github.com/repos/xbmc/ {} /pulls ' . format ( repo ) ,
params = {
' head ' : ' {} : {} ' . format ( gh_username , local_branch_name ) ,
' base ' : branch ,
} ,
headers = { ' Accept ' : ' application/vnd.github.v3+json ' } ,
auth = ( gh_username , gh_token )
)
if resp . status_code == 200 and not resp . json ( ) :
# Create a new Pull Request
template = """ ### Description
- * * General * *
- Add - on name : { name }
- Add - on ID : { id }
- Version number : { version }
- Kodi / repository version : { kodi_repo_branch }
- * * Code location * *
- URL : { source }
{ description }
### What's new
{ news }
### Checklist:
- [ X ] My code follows the [ add - on rules ] ( http : / / kodi . wiki / view / Add - on_rules ) and [ piracy stance ] ( http : / / kodi . wiki / view / Official : Forum_rules #Piracy_Policy) of this project.
- [ X ] I have read the [ CONTRIBUTING ] ( https : / / github . com / xbmc / repo - plugins / blob / master / CONTRIBUTING . md ) document
- [ X ] Each add - on submission should be a single commit with using the following style : [ plugin . video . foo ] v1 .0 .0
"""
pr_body = template . format (
name = addon_info [ ' name ' ] ,
id = addon_info [ ' id ' ] ,
version = addon_info [ ' version ' ] ,
kodi_repo_branch = branch ,
source = addon_info [ ' source ' ] ,
description = addon_info [ ' description ' ] ,
news = addon_info [ ' news ' ]
)
resp = requests . post (
' https://api.github.com/repos/xbmc/ {} /pulls ' . format ( repo ) ,
json = {
' title ' : ' [ {} ] {} ' . format ( local_branch_name , addon_info [ ' version ' ] ) ,
' head ' : ' {} : {} ' . format ( gh_username , local_branch_name ) ,
' base ' : branch ,
' body ' : pr_body ,
' maintainer_can_modify ' : True ,
} ,
headers = { ' Accept ' : ' application/vnd.github.v3+json ' } ,
auth = ( gh_username , gh_token )
)
if resp . status_code != 201 :
raise Exception ( ' GitHub API error: {} \n {} ' . format ( resp . status_code , pformat ( resp . json ( ) ) ) )
elif resp . status_code == 200 and resp . json ( ) :
_LOGGER . info ( ' Pull request in {} for {} : {} already exists. ' . format ( branch , gh_username , local_branch_name ) )
else :
raise Exception ( ' Unexpected GitHub error: {} \n {} ' . format ( resp . status_code , pformat ( resp . json ( ) ) ) )
if __name__ == ' __main__ ' :
filenames = sys . argv [ 1 : ]
2022-07-07 15:52:36 +02:00
# Fork the repo if the user does not have a personal repo fork
if not user_fork_exists ( GH_REPO , GH_USERNAME , GH_TOKEN ) :
create_personal_fork ( GH_REPO , GH_USERNAME , GH_TOKEN )
2022-02-02 17:49:48 +01:00
2022-07-07 15:52:36 +02:00
for filename in filenames :
2022-02-02 17:49:48 +01:00
with TemporaryDirectory ( ) as extract_dir :
with ZipFile ( filename ) as z :
# Look for addon.xml in zip and load the details
xmlfile = next ( f . filename for f in z . filelist if f . filename . endswith ( ' addon.xml ' ) )
addon_info = get_addon_info ( z . read ( xmlfile ) . decode ( ' utf-8 ' ) )
if addon_info [ ' python ' ] != ' 3.0.0 ' :
branch = ' leia '
else :
branch = ' matrix '
# Extract the ZIP file to the extract_dir
z . extractall ( extract_dir )
# Checkout the fork locally and create a branch with our new code from the extract_dir
create_addon_branch ( GH_REPO , branch , os . path . join ( extract_dir , addon_info [ ' id ' ] ) , addon_info , GH_USERNAME , GH_TOKEN , GH_EMAIL )
# Create pull request
create_pull_request ( GH_REPO , branch , addon_info , GH_USERNAME , GH_TOKEN )