Skip to content

Commit f0e5886

Browse files
committed
download from the Kodinerds repo
1 parent ecc8305 commit f0e5886

File tree

6 files changed

+146
-160
lines changed

6 files changed

+146
-160
lines changed

addon.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<addon id="script.kodinerds.android.update" name="Kodinerds Android Installer" version="1.0.0+matrix.1" provider-name="Maven">
2+
<addon id="script.kodinerds.android.update" name="KN APK Downloader" version="1.1.0+matrix.1" provider-name="Maven">
33
<requires>
44
<import addon="xbmc.python" version="3.0.0"/>
55
<import addon="script.module.six" version="1.0.0"/>
@@ -8,11 +8,11 @@
88
<extension point="xbmc.python.script" library="default.py"/>
99
<extension point="xbmc.service" library="service.py" start="login"/>
1010
<extension point="xbmc.addon.metadata">
11+
<summary lang="de">Downloader für Maven's Kodi-Builds</summary>
1112
<license>GNU GENERAL PUBLIC LICENSE. Version 3, June 2007</license>
1213
<platform>android</platform>
1314
<assets>
1415
<icon>resources/images/icon.png</icon>
15-
<fanart>resources/images/fanart.jpg</fanart>
1616
</assets>
1717
</extension>
1818
</addon>

default.py

Lines changed: 93 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
# -*- coding: utf-8 -*-
1717

1818
from datetime import timedelta as datetime_timedelta
19-
from json import loads as json_loads
19+
from json import dumps as json_dumps, loads as json_loads
2020
from os.path import join as os_path_join, sep as os_path_sep
21-
from re import compile as re_compile, search as re_search
21+
from re import compile as re_compile, findall as re_findall
2222
from simplecache import SimpleCache
2323
from six import PY2
2424
from six.moves import urllib
@@ -37,193 +37,199 @@
3737
from xbmcvfs import translatePath as xbmcvfs_translatePath
3838

3939
# Plugin Info
40-
ADDON_ID = 'script.kodinerds.android.update'
40+
ADDON_ID = 'script.kodinerds.android.update'
4141
REAL_SETTINGS = xbmcaddon_Addon(id=ADDON_ID)
42-
ADDON_NAME = REAL_SETTINGS.getAddonInfo('name')
43-
SETTINGS_LOC = '/storage/emulated/0/download'
44-
ADDON_PATH = REAL_SETTINGS.getAddonInfo('path')
42+
ADDON_NAME = REAL_SETTINGS.getAddonInfo('name')
43+
SETTINGS_LOC = '/storage/emulated/0/download'
44+
ADDON_PATH = REAL_SETTINGS.getAddonInfo('path')
4545
ADDON_VERSION = REAL_SETTINGS.getAddonInfo('version')
46-
ICON = REAL_SETTINGS.getAddonInfo('icon')
47-
LANGUAGE = REAL_SETTINGS.getLocalizedString
48-
49-
## GLOBALS ##
50-
TIMEOUT = 15
51-
MIN_VER = 5 #Minimum Android Version Compatible with Kodi
52-
MAIN = [
53-
{'Matrix': 'https://www.dropbox.com/sh/0n37s3j3iybww9q/AABN74pzb3CMJBiI76r4jZ48a?dl=0'},
54-
{'Matrix-FireTV': 'https://www.dropbox.com/sh/7n9tnp70jtjwegt/AAD2VUyUSJEq_iRyfxoHUTjHa?dl=0'},
55-
{'Nexus': 'https://www.dropbox.com/sh/5fjkqnuvwa7z3ml/AACcKF7nZJyQrlc-mCb_k3A8a?dl=0'},
56-
{'Nexus-FireTV': 'https://www.dropbox.com/sh/nmgbffz13vqtpza/AABawAkbvlsb4Z7HrfDFXjxwa?dl=0'}
46+
ICON = REAL_SETTINGS.getAddonInfo('icon')
47+
LANGUAGE = REAL_SETTINGS.getLocalizedString
48+
49+
# # GLOBALS ##
50+
TIMEOUT = 15
51+
MIN_VER = 5 # Minimum Android Version Compatible with Kodi
52+
BASE_URL = 'https://repo.kodinerds.net/index.php'
53+
MAIN = [
54+
{'label': 'Matrix', 'url': '{}?action=list&scope=all&version=matrix/'.format(BASE_URL), 'regex': 'download=(.*net\.kodinerds\.maven\.kodi\.arm.*)"'},
55+
{'label': 'Matrix-FireTV', 'url': '{}?action=list&scope=all&version=matrix/'.format(BASE_URL), 'regex': 'download=(.*net\.kodinerds\.maven\.kodi\.firetv\.arm.*)"'},
56+
{'label': 'Nexus', 'url': '{}?action=list&scope=all&version=nexus/'.format(BASE_URL), 'regex': 'download=(.*net\.kodinerds\.maven\.kodi20\.arm.*)"'},
57+
{'label': 'Nexus-FireTV', 'url': '{}?action=list&scope=all&version=nexus/'.format(BASE_URL), 'regex': 'download=(.*net\.kodinerds\.maven\.kodi20\.firetv\.arm.*)"'}
5758
]
58-
DEBUG = REAL_SETTINGS.getSetting('Enable_Debugging') == 'true'
59-
CLEAN = REAL_SETTINGS.getSetting('Disable_Maintenance') == 'false'
60-
VERSION = REAL_SETTINGS.getSetting('Version')
61-
CUSTOM = (REAL_SETTINGS.getSetting('Custom_Manager') or 'com.android.documentsui')
62-
FMANAGER = {0:'com.android.documentsui',1:CUSTOM}[int(REAL_SETTINGS.getSetting('File_Manager'))]
59+
DEBUG = REAL_SETTINGS.getSetting('Enable_Debugging') == 'true'
60+
CLEAN = REAL_SETTINGS.getSetting('Disable_Maintenance') == 'false'
61+
VERSION = REAL_SETTINGS.getSetting('Version')
62+
CUSTOM = (REAL_SETTINGS.getSetting('Custom_Manager') or 'com.android.documentsui')
63+
FMANAGER = {0:'com.android.documentsui', 1:CUSTOM}[int(REAL_SETTINGS.getSetting('File_Manager'))]
6364

6465

6566
def log(msg, level=xbmc_LOGDEBUG):
6667
if DEBUG == False and level != xbmc_LOGERROR: return
6768
if level == xbmc_LOGERROR: msg += ', {0}'.format(traceback_format_exc())
6869
xbmc_log('[{0}-{1}] {2}'.format(ADDON_ID, ADDON_VERSION, msg), level)
6970

71+
7072
def selectDialog(label, items, pselect=-1, uDetails=True):
7173
select = xbmcgui_Dialog().select(label, items, preselect=pselect, useDetails=uDetails)
7274
if select >= 0: return select
7375
return None
7476

77+
7578
socket_setdefaulttimeout(TIMEOUT)
79+
80+
7681
class Installer(object):
82+
83+
7784
def __init__(self):
7885
self.myMonitor = xbmc_Monitor()
7986
self.cache = SimpleCache()
8087
if not self.chkVersion(): return
8188
self.buildMain('')
82-
83-
89+
90+
8491
def disable(self, build):
8592
xbmcgui_Dialog().notification(ADDON_NAME, VERSION, ICON, 8000)
86-
if not xbmcgui_Dialog().yesno(ADDON_NAME, LANGUAGE(30011).format(build), LANGUAGE(30012)): return False
93+
if not xbmcgui_Dialog().yesno(ADDON_NAME, LANGUAGE(30011).format(build), LANGUAGE(30012)): return False
8794
xbmc_executeJSONRPC('{"jsonrpc": "2.0", "method":"Addons.SetAddonEnabled","params":{"addonid":"{0}","enabled":false}, "id": 1}'.format(ADDON_ID))
8895
xbmcgui_Dialog().notification(ADDON_NAME, LANGUAGE(30009), ICON, 4000)
8996
return False
90-
91-
97+
98+
9299
def chkVersion(self):
93-
try:
100+
try:
94101
build = int(re_compile('Android (\d+)').findall(VERSION)[0])
95102
except: build = MIN_VER
96103
if build >= MIN_VER: return True
97104
else: return self.disable(build)
98-
99105

100-
def getAPKs(self, url):
106+
107+
def getAPKs(self, url):
101108
log('getAPKs: path = {0}'.format(url))
102109
try:
103110
cacheResponse = self.cache.get('{0}.openURL, url = {1}'.format(ADDON_NAME, url))
104111
if not cacheResponse:
105112
cacheResponse = dict(entries=list())
106113
if url == '':
107114
for item in MAIN:
108-
for key in item.keys():
109-
entry = dict()
110-
entry.update(dict(tag='folder'))
111-
entry.update(dict(name=key))
112-
entry.update(dict(path_display=item[key]))
113-
cacheResponse.get('entries').append(entry)
115+
entry = dict()
116+
entry.update(dict(tag='folder'))
117+
entry.update(dict(name=item.get('label')))
118+
entry.update(dict(url=item.get('url')))
119+
entry.update(dict(regex=item.get('regex')))
120+
cacheResponse.get('entries').append(entry)
114121
else:
115-
request = urllib.request.Request(url)
116-
string_json = re_search('responseReceived\("(.*)"\)}\)', urllib.request.urlopen(request, timeout = TIMEOUT).read().decode('utf-8'))
117-
if string_json:
118-
json_entries = json_loads(string_json.group(1).replace('\\', '')).get('entries')
119-
for json_entry in json_entries:
120-
entry = dict()
121-
href = json_entry.get('href')
122-
if json_entry.get('is_dir'):
123-
entry.update(dict(tag='folder'))
124-
else:
122+
regex = url.rsplit('/', 1)[1].split('=', 1)[1]
123+
if regex:
124+
request = urllib.request.Request(url)
125+
html = urllib.request.urlopen(request, timeout=TIMEOUT).read().decode('utf-8')
126+
matches = re_findall(regex, html)
127+
if matches:
128+
for match in matches:
129+
entry = dict()
125130
entry.update(dict(tag='file'))
126-
entry.update(dict(size=json_entry.get('bytes')))
127-
href = href.replace('dl=0', 'dl=1')
128-
entry.update(dict(name=json_entry.get('filename')))
129-
entry.update(dict(path_display=href))
130-
cacheResponse.get('entries').append(entry)
131+
entry.update(dict(name=match.rsplit('/', 1)[1]))
132+
entry.update(dict(url=BASE_URL))
133+
entry.update(dict(data='{{"c_item[]": "download={}"}}'.format(match)))
134+
cacheResponse.get('entries').append(entry)
135+
cacheResponse.update(dict(entries=sorted(cacheResponse.get('entries'), key=lambda k: k['name'], reverse=True)))
131136

132137
self.cache.set('{0}.openURL, url = {1}'.format(ADDON_NAME, url), cacheResponse, expiration=datetime_timedelta(minutes=5))
133138
return cacheResponse
134139
except Exception as e:
135140
log('openURL Failed! {0}'.format(e), xbmc_LOGERROR)
136141
xbmcgui_Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000)
137142
return None
138-
139-
143+
144+
140145
def buildItems(self, path):
141146
entries = self.getAPKs(path).get('entries', {})
142147
if entries is None or len(entries) == 0: return
143148
for entry in entries:
144149
if entry.get('tag') == 'file' and entry.get('name').endswith('.apk'):
145150
label = entry.get('name')
146-
li = xbmcgui_ListItem(label, '{0} {1:.02f} MB'.format(label.split('.apk')[1], entry.get('size') / 1024 / 1024).replace('.', ','), path=entry.get('path_display'))
151+
li = xbmcgui_ListItem(label, label.split('.apk')[1], path=entry.get('url'))
152+
li.setProperty('data', entry.get('data'))
147153
li.setArt({'icon': ICON})
148154
yield (li)
149155
elif entry.get('tag') == 'folder':
150156
label = entry.get('name')
151-
li = xbmcgui_ListItem(label, path=entry.get('path_display'))
157+
li = xbmcgui_ListItem(label, path='{}&regex={}'.format(entry.get('url'), entry.get('regex')))
152158
li.setArt({'icon': ICON})
153159
yield (li)
154-
155-
160+
161+
156162
def buildMain(self, path):
157163
log('buildMain')
158-
items = list(self.buildItems(path))
164+
items = list(self.buildItems(path))
159165
if len(items) == 0: return
160166
else: select = selectDialog(ADDON_NAME, items)
161-
if select is None or select < 0: return #return on cancel.
162-
newURL = items[select].getPath()
163-
if newURL.find('.apk') > 0:
167+
if select is None or select < 0: return # return on cancel.
168+
if items[select].getProperty('data'):
164169
dest = xbmcvfs_translatePath(os_path_join(SETTINGS_LOC, items[select].getLabel()))
165170
REAL_SETTINGS.setSetting("LastPath", dest)
166-
return self.downloadAPK(items[select].getPath(), dest)
171+
return self.downloadAPK(items[select].getPath(), dest, items[select].getProperty('data'))
167172
else:
168-
self.buildMain(newURL)
169-
173+
self.buildMain(items[select].getPath())
174+
170175

171176
def fileExists(self, dest):
172177
if xbmcvfs_exists(dest):
173178
if not xbmcgui_Dialog().yesno(ADDON_NAME, '{0}: {1}'.format(LANGUAGE(30004), dest.rsplit(os_path_sep, 1)[-1]), nolabel=LANGUAGE(30005), yeslabel=LANGUAGE(30006)): return True
174179
elif CLEAN and xbmcvfs_exists(dest): self.deleleAPK(dest)
175180
return False
176-
177-
181+
182+
178183
def deleleAPK(self, path):
179184
count = 0
180-
#some file systems don't release the file lock instantly.
185+
# some file systems don't release the file lock instantly.
181186
while not self.myMonitor.abortRequested() and count < 3:
182187
count += 1
183-
if self.myMonitor.waitForAbort(1): return
184-
try:
188+
if self.myMonitor.waitForAbort(1): return
189+
try:
185190
if xbmcvfs_delete(path): return
186191
except: pass
187-
188-
189-
def downloadAPK(self, url, dest):
192+
193+
194+
def downloadAPK(self, url, dest, data):
190195
if self.fileExists(dest): return self.installAPK(dest)
191196
start_time = time_time()
192197
dia = xbmcgui_DialogProgress()
193198
fle = dest.rsplit(os_path_sep, 1)[1]
194199
dia.create(ADDON_NAME, LANGUAGE(30002).format(fle))
195-
try: urllib.request.urlretrieve(url, dest, lambda nb, bs, fs: self.pbhook(nb, bs, fs, dia, start_time, fle))
200+
data = urllib.parse.urlencode(json_loads(data)).encode()
201+
try: urllib.request.urlretrieve(url, dest, lambda nb, bs, fs: self.pbhook(nb, bs, fs, dia, start_time, fle), data)
196202
except Exception as e:
197203
dia.close()
198204
xbmcgui_Dialog().notification(ADDON_NAME, LANGUAGE(30001), ICON, 4000)
199205
log('downloadAPK, Failed! ({0}) {1}'.format(url, e), xbmc_LOGERROR)
200206
return self.deleleAPK(dest)
201207
dia.close()
202208
return self.installAPK(dest)
203-
204-
209+
210+
205211
def pbhook(self, numblocks, blocksize, filesize, dia, start_time, fle):
206-
try:
207-
percent = min(numblocks * blocksize * 100 / filesize, 100)
208-
currently_downloaded = float(numblocks) * blocksize / (1024 * 1024)
209-
kbps_speed = numblocks * blocksize / (time_time() - start_time)
212+
try:
213+
percent = min(numblocks * blocksize * 100 / filesize, 100)
214+
currently_downloaded = float(numblocks) * blocksize / (1024 * 1024)
215+
kbps_speed = numblocks * blocksize / (time_time() - start_time)
210216
if kbps_speed > 0: eta = int((filesize - numblocks * blocksize) / kbps_speed)
211-
else: eta = 0
212-
kbps_speed = kbps_speed / 1024
217+
else: eta = 0
218+
kbps_speed = kbps_speed / 1024
213219
if eta < 0: eta = divmod(0, 60)
214220
else: eta = divmod(eta, 60)
215-
total = (float(filesize) / (1024 * 1024))
216-
label = '[B]Downloading: [/B] {0}'.format(os_path_join(SETTINGS_LOC, fle))
217-
label2 = '{0:.02f} MB of {1:.02f} MB'.format(currently_downloaded,total)
221+
total = (float(filesize) / (1024 * 1024))
222+
label = '[B]Downloading: [/B] {0}'.format(os_path_join(SETTINGS_LOC, fle))
223+
label2 = '{0:.02f} MB of {1:.02f} MB'.format(currently_downloaded, total)
218224
label2 += ' | [B]Speed:[/B] {0:.02f} Kb/s'.format(kbps_speed)
219225
label2 += ' | [B]ETA:[/B] {0:02d}:{1:02d}'.format(eta[0], eta[1])
220226
dia.update(int(percent), '{0} {1}'.format(label, label2.replace('.', ',')))
221227
except Exception('Download Failed'): dia.update(100)
222228
if dia.iscanceled(): raise Exception('Download Canceled')
223-
224-
229+
230+
225231
def installAPK(self, apkfile):
226232
xbmc_executebuiltin('StartAndroidActivity({0},,,"content://{1}")'.format(FMANAGER, apkfile))
227233

228234

229-
if __name__ == '__main__': Installer()
235+
if __name__ == '__main__': Installer()

resources/images/fanart.jpg

-80.3 KB
Binary file not shown.

resources/images/icon.png

-56.9 KB
Loading

select.py

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
1-
# Copyright (C) 2020 Team-Kodi
2-
#
3-
# This program is free software: you can redistribute it and/or modify
4-
# it under the terms of the GNU General Public License as published by
5-
# the Free Software Foundation, either version 3 of the License, or
6-
# (at your option) any later version.
7-
#
8-
# This program is distributed in the hope that it will be useful,
9-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11-
# GNU General Public License for more details.
12-
#
13-
# You should have received a copy of the GNU General Public License
14-
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15-
#
161
# -*- coding: utf-8 -*-
172

183
from traceback import format_exc as traceback_format_exc
@@ -22,32 +7,38 @@
227
from xbmcvfs import listdir as xbmcvfs_listdir
238

249
# Plugin Info
25-
ADDON_ID = 'script.kodinerds.android.update'
10+
ADDON_ID = 'script.kodinerds.android.update'
2611
REAL_SETTINGS = xbmcaddon_Addon(id=ADDON_ID)
27-
ADDON_NAME = REAL_SETTINGS.getAddonInfo('name')
12+
ADDON_NAME = REAL_SETTINGS.getAddonInfo('name')
2813
ADDON_VERSION = REAL_SETTINGS.getAddonInfo('version')
29-
ICON = REAL_SETTINGS.getAddonInfo('icon')
30-
LANGUAGE = REAL_SETTINGS.getLocalizedString
14+
ICON = REAL_SETTINGS.getAddonInfo('icon')
15+
LANGUAGE = REAL_SETTINGS.getLocalizedString
16+
17+
# # GLOBALS ##
18+
DEBUG = REAL_SETTINGS.getSetting('Enable_Debugging') == 'true'
19+
CUSTOM = REAL_SETTINGS.getSetting('Custom_Manager')
3120

32-
## GLOBALS ##
33-
DEBUG = REAL_SETTINGS.getSetting('Enable_Debugging') == 'true'
34-
CUSTOM = REAL_SETTINGS.getSetting('Custom_Manager')
3521

3622
def log(msg, level=xbmc_LOGDEBUG):
3723
if DEBUG == False and level != xbmc_LOGERROR: return
3824
if level == xbmc_LOGERROR: msg += ', {0}'.format(traceback_format_exc())
3925
xbmc_log('[{0}-{1}] {2}'.format(ADDON_ID, ADDON_VERSION, msg), level)
4026

27+
4128
def selectDialog(label, items, pselect=-1, uDetails=False):
4229
select = xbmcgui_Dialog().select(label, items, preselect=pselect, useDetails=uDetails)
4330
if select >= 0: return select
4431
return None
45-
32+
33+
4634
class Select(object):
35+
36+
4737
def __init__(self):
48-
items = xbmcvfs_listdir('androidapp://sources/apps/')[1]
49-
select = selectDialog(LANGUAGE(30020),items)
50-
if select is None or select < 0: return #return on cancel.
38+
items = xbmcvfs_listdir('androidapp://sources/apps/')[1]
39+
select = selectDialog(LANGUAGE(30020), items)
40+
if select is None or select < 0: return # return on cancel.
5141
REAL_SETTINGS.setSetting('Custom_Manager', '{0}'.format(items[select]))
5242

53-
if __name__ == '__main__': Select()
43+
44+
if __name__ == '__main__': Select()

0 commit comments

Comments
 (0)