-
Notifications
You must be signed in to change notification settings - Fork 0
/
addon.py
792 lines (629 loc) · 26.7 KB
/
addon.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
#!/usr/bin/env python
from __future__ import absolute_import, division, unicode_literals
import json
import os
import sys
import traceback
from datetime import datetime, date, timedelta
from kodi_six import xbmc, xbmcaddon, xbmcgui, xbmcplugin, py2_encode, py2_decode
from sqlite3 import Error as DBError
from resources.lib.constants import *
from resources.lib.database import CacheDatabase, PublicationData, MediaData, TranslationData, Ignore
from resources.lib.scrapper import JwOrgParser, unescape
Q = Query
M = Mode
try:
from urllib.error import HTTPError
from urllib.parse import parse_qs, urlencode
from urllib.request import urlopen
except ImportError:
from urllib2 import HTTPError, urlopen
from urlparse import parse_qs as _parse_qs
from urllib import urlencode as _urlencode
# Py2: urlencode only accepts byte strings
def urlencode(query):
# Dict[str, str] -> str
return py2_decode(_urlencode({py2_encode(param): py2_encode(arg) for param, arg in query.items()}))
# Py2: even if parse_qs accepts unicode, the return makes no sense
def parse_qs(qs):
# str -> Dict[str, List[str]]
return {py2_decode(param): [py2_decode(a) for a in args]
for param, args in _parse_qs(py2_encode(qs)).items()}
# Py2: When using str, we mean unicode string
str = unicode
class NotFoundError(Exception):
"""Raised when getting a 404"""
pass
def log(msg, level=xbmc.LOGDEBUG):
"""Write to log file"""
for line in msg.splitlines():
xbmc.log(addon.getAddonInfo('id') + ': ' + line, level)
def notification(msg, icon=xbmcgui.NOTIFICATION_ERROR):
"""Show a GUI notification"""
xbmcgui.Dialog().notification(addon.getAddonInfo('name'), msg, icon=icon)
def get_json(url, exit_on_404=True):
# type: (str, bool) -> dict
"""Fetch JSON data from an URL and return it as a dict"""
log('opening ' + url, xbmc.LOGINFO)
try:
# urlopen returns bytes
data = urlopen(url).read().decode('utf-8')
# Catches URLError, HTTPError, SSLError ...
except IOError as e:
# Pass on 404 for handling by someone else
if isinstance(e, HTTPError) and e.code == 404 and not exit_on_404:
log('404 not found, ignoring')
raise NotFoundError
log(traceback.format_exc(), level=xbmc.LOGERROR)
notification(S.CONNECTION_ERROR)
exit(1)
raise # to make PyCharm happy
return json.loads(data)
def getpubmedialinks_json(pubdata, alllangs=False, exit_on_404=True):
"""Make a request to JW API and return JSON as a dict"""
assert pubdata.pub
query = dict(output='json',
fileformat='MP3',
pub=pubdata.pub,
issue=pubdata.issue,
booknum=pubdata.booknum,
langwritten=pubdata.lang,
txtCMSLang=pubdata.lang,
alllangs=int(alllangs))
# Remove empty queries
query = {key: value for key, value in query.items() if value is not None}
return get_json(PUBMEDIA_API + '?' + urlencode(query), exit_on_404=exit_on_404)
def request_to_self(mode, pubdata=None, pub=None, year=None, lang=None, langname=None, track=None):
# type: (str, PublicationData, str, int, str, str, int) -> str
"""Return a string with an URL request to the add-on itself
Arguments override values from pubdata.
:param pubdata: Grab info from this object
:param mode: Should be one of the M constants
:param pub: Publication code
:param year: Year (for magazines)
:param lang: Language code
:param langname: Language name (visible in settings)
:param track: Track number (for direct playback)
"""
query = {Q.MODE: mode,
Q.PUB: pub,
Q.LANG: lang,
Q.LANG_NAME: langname,
Q.YEAR: year,
Q.TRACK: track}
# Overwrite empty values with values from pubdata
# Note: do not include language in the request, unless explicitly specified
# This will enable "viewed status" in Kodi to work for all languages
if pubdata:
query.update({
Q.PUB: pub or pubdata.pub,
Q.ISSUE: pubdata.issue,
Q.BOOKNUM: pubdata.booknum})
# Remove empty queries
query = {key: value for key, value in query.items() if value is not None}
# argv[0] is path to the plugin
return str(sys.argv[0]) + '?' + urlencode(query)
def get_translation(key):
"""Quick way to get a translated string from the cache"""
try:
search = TranslationData(key=key, lang=global_language)
result = next(cache.trans.select(search))
return result.string
except StopIteration:
return None
def update_translations(lang):
"""Download a jw.org web page and save some extracted strings to cache"""
# If there are any translations for the current language in the cache, do nothing
if any(cache.trans.select(TranslationData(lang=lang))):
return
progressbar = xbmcgui.DialogProgress()
progressbar.create('', S.TRANS_UPDATE)
progressbar.update(50)
try:
url = '{}?docid={}&wtlocale={}'.format(FINDER_API, DOCID_MAGAZINES, lang)
log('scrapping translations from ' + url)
# urlopen returns bytes
# Set high timeout, because AWS blocks requests from urllib for a while
response = urlopen(url, timeout=30).read().decode('utf-8')
translations = JwOrgParser.parse(response)
for key, value in translations.items():
cache.trans.delete(TranslationData(key=key, lang=lang))
cache.trans.insert(TranslationData(key=key, string=value, lang=lang))
finally:
progressbar.close()
def download_pub_data(pubdata):
# type: (PublicationData) -> ()
"""Download and cache publication metadata
Return publication and list of contained media
"""
try:
j = getpubmedialinks_json(pubdata, exit_on_404=False)
except NotFoundError:
# Cache the failure... yes, that's right, so we don't retry for a while
cache.publ.delete(pubdata)
failed_pub = PublicationData.copy(pubdata)
failed_pub.failed = datetime.now()
cache.publ.insert(failed_pub)
raise
# Remove old publication metadata
cache.publ.delete(pubdata)
# For bible index page: remove all bible books' metadata
# if pubdata.booknum == 0:
# bible = PublicationData.copy(pubdata)
# bible.booknum = Ignore
# cache.publ.delete(bible)
# Store new publication metadata
# Note: opening a publication will refresh its metadata so that will deal with deprecated entries
new_pub = PublicationData.copy(pubdata)
title = j['pubName']
if j.get('formattedDate'):
title += ' ' + j['formattedDate']
new_pub.title = unescape(title)
# Don't save Bible icons - they are ugly atm
if pubdata.pub not in ('bi12', 'nwt'):
new_pub.icon = j.get('pubImage', {}).get('url')
# Don't save Bible books' metadata, it's refreshed each time so there's no need
if pubdata.booknum is None or pubdata.booknum == 0:
cache.publ.insert(new_pub)
media_list = []
sub_pub_list = []
try:
for j_file in j['files'][pubdata.lang]['MP3']:
try:
# Make a list of media metadata
if j_file.get('mimetype') == 'audio/mpeg':
m = MediaData.copy(pubdata)
m.url = j_file['file']['url']
m.title = unescape(j_file['title'])
m.duration = j_file.get('duration')
m.track = int(j_file.get('track'))
media_list.append(m)
# For the bible index page: make a list of the bible books' metadata
elif pubdata.booknum == 0:
sub_pub = PublicationData.copy(pubdata)
sub_pub.title = unescape(j_file['title'])
sub_pub.booknum = int(j_file['booknum'])
# We could store it in the database, but since it refreshes every time, that's not necessary
# cache.publ.insert(sub_pub)
sub_pub_list.append(sub_pub)
except KeyError:
pass
except KeyError:
pass
return new_pub, sub_pub_list or media_list
def get_pub_data(pubdata):
# type: (PublicationData) -> ()
"""Get publication metadata from cache (download if needed)"""
# Check for previous records
try:
cached_pub = next(cache.publ.select(pubdata))
if cached_pub.failed is None:
return cached_pub
# Has failed within the last 24 hours
elif datetime.now() < cached_pub.failed + timedelta(days=1):
log('ignoring previously failed publication')
raise NotFoundError
except StopIteration:
pass
# Refresh
pub, media = download_pub_data(pubdata)
return pub
class MenuItem(object):
"""A general menu item (folder)"""
is_folder = True
def __init__(self, url, title, icon=None, fanart=None):
self.url = url
self.title = title
self.icon = icon
self.fanart = fanart
def listitem(self):
"""Create a Kodi listitem from the metadata"""
try:
# offscreen is a Kodi v18 feature
# We wont't be able to change the listitem after running .addDirectoryItem()
# But load time for this function is cut down by 93% (!)
li = xbmcgui.ListItem(self.title, offscreen=True)
except TypeError:
li = xbmcgui.ListItem(self.title)
# setArt can be kinda slow, so don't run if it's empty
if self.icon or self.fanart:
li.setArt(dict(icon=self.icon, poster=self.icon, fanart=self.fanart))
return li
def add_item_in_kodi(self, total=0):
"""Adds this as a directory item in Kodi"""
# totalItems doesn't seem to have any effect in Estuary skin, but let's keep it anyway
xbmcplugin.addDirectoryItem(handle=addon_handle, url=self.url, listitem=self.listitem(),
isFolder=self.is_folder, totalItems=total)
class PublicationItem(MenuItem):
"""A folder that represents a publication
At the moment, it's virtually identical to a MenuItem
"""
def __init__(self, pubdata):
# type: (PublicationData) -> None
super(PublicationItem, self).__init__(
url=request_to_self(M.OPEN, pubdata=pubdata),
title=pubdata.title,
icon=pubdata.icon
)
class MediaItem(MenuItem):
"""A playable article (audio file)"""
is_folder = False
def __init__(self, mediadata):
# type: (MediaData) -> None
self.pubdata = PublicationData.copy(mediadata)
self.track = mediadata.track
self.duration = mediadata.duration
self.resolved_url = mediadata.url
super(MediaItem, self).__init__(
url=request_to_self(M.OPEN, pubdata=self.pubdata, track=self.track),
title=mediadata.title
)
def listitem(self):
li = super(MediaItem, self).listitem()
li.setInfo('music', dict(duration=self.duration, title=self.title))
# For some reason needed for listitems that will open xbmcplugin.setResolvedUrl
li.setProperty('isPlayable', 'true')
# Other language action
# Note: RunPlugin opens as a background process
action = 'RunPlugin(' + request_to_self(M.LANGUAGES, pubdata=self.pubdata, track=self.track) + ')'
li.addContextMenuItems([(S.PLAY_LANG, action)])
return li
def listitem_with_resolved_url(self):
li = self.listitem()
li.setPath(self.resolved_url)
return li
def top_level_page():
"""The main menu"""
if addon.getSetting(SettingID.STARTUP_MSG) == 'true':
dialog = xbmcgui.Dialog()
try:
dialog.textviewer(S.THEO_WARN, S.DISCLAIMER) # Kodi v16
except AttributeError:
dialog.ok(S.THEO_WARN, S.DISCLAIMER)
addon.setSetting(SettingID.STARTUP_MSG, 'false')
# Auto set language, if it has never been set and Kodi is configured for something else then English
isolang = xbmc.getLanguage(xbmc.ISO_639_1)
if not addon.getSetting(SettingID.LANG_HIST) and isolang != 'en':
try:
# Search for matching language, save setting (and update translations)
language_dialog(preselect=isolang)
# Reload for this instance
global global_language
global_language = addon.getSetting(SettingID.LANG) or 'E'
except StopIteration:
# No suitable language was found, just write something to history, so this check won't run again
addon.setSetting(SettingID.LANG_HIST, 'E')
fanart = os.path.join(addon.getAddonInfo('path'), addon.getAddonInfo('fanart'))
MenuItem(
url=request_to_self(M.BIBLE),
title=T.BIBLE or S.BIBLE,
icon=ICON_BIBLE,
fanart=fanart
).add_item_in_kodi()
MenuItem(
url=request_to_self(M.MAGAZINES),
title=T.MAGAZINES or S.MAGAZINES,
icon=ICON_WATCHTOWER,
fanart=fanart
).add_item_in_kodi()
MenuItem(
url=request_to_self(M.BOOKS),
title=T.BOOKS or S.BOOKS,
icon=ICON_BOOKS,
fanart=fanart
).add_item_in_kodi()
xbmcplugin.endOfDirectory(addon_handle)
def bible_page():
"""Bible menu"""
success = False
for bible in 'bi12', 'nwt':
try:
request = PublicationData(pub=bible, booknum=0, lang=global_language)
pub = get_pub_data(request)
PublicationItem(pub).add_item_in_kodi()
success = True
except NotFoundError:
pass
if not success:
xbmcgui.Dialog().ok('', S.NOT_AVAIL)
# Note: return will prevent Kodi from creating an empty folder view
return
xbmcplugin.endOfDirectory(addon_handle)
def magazine_page(pub=None, year=None):
# type: (str, int) -> None
"""Browse magazines
With no arguments, display a list of magazines.
:param pub: Display a list of years for this magazine.
:param year: Display a list of issues from this year.
"""
# Magazine list
if not pub:
MenuItem(
url=request_to_self(M.MAGAZINES, pub='g'),
title=T.AWAKE or S.AWAKE,
icon=ICON_AWAKE
).add_item_in_kodi()
MenuItem(
url=request_to_self(M.MAGAZINES, pub='wp'),
title=T.WT or S.WT,
icon=ICON_WATCHTOWER
).add_item_in_kodi()
MenuItem(
url=request_to_self(M.MAGAZINES, pub='w'),
title=T.WT_STUDY or S.WT_STUDY,
icon=ICON_WATCHTOWER
).add_item_in_kodi()
# Simplified only existed in a few languages
if global_language in ('E', 'F', 'I', 'T', 'S'):
MenuItem(
url=request_to_self(M.MAGAZINES, pub='ws'),
title=T.WT_SIMPLE or S.WT_SIMPLE,
icon=ICON_WATCHTOWER
).add_item_in_kodi()
# Year list
elif not year:
# 2008 was the first year of recordings in English
# Other languages are different, but we'll just have to try and fail
max_year = date.today().year + 1
ranges = {'w': range(2008, max_year),
'wp': range(2008, max_year),
'ws': range(2013, 2018), # first english: 2013-08-15, last 2018-12
'g': range(2008, max_year)}
for year in sorted(ranges[pub], reverse=True):
MenuItem(
url=request_to_self(M.MAGAZINES, pub=pub, year=year),
title=str(year)
).add_item_in_kodi()
# Issue list
else:
# Determine release dates
if year == date.today().year:
max_month = date.today().month + 1
ranges = {'w': range(1, max_month),
'wp': range(1, 4, max_month),
'g': range(3, 4, max_month)}
issues = ['{}{:02}'.format(year, month) for month in ranges[pub]]
elif year >= 2018:
ranges = {'w': range(1, 13),
'wp': (1, 5, 9),
'g': (3, 7, 11)}
issues = ['{}{:02}'.format(year, month) for month in ranges[pub]]
elif year >= 2016:
ranges = {'w': range(1, 13),
'ws': range(1, 13),
'wp': range(1, 13, 2), # odd months
'g': range(2, 13, 2)} # even months
issues = ['{}{:02}'.format(year, month) for month in ranges[pub]]
else:
days = {'wp': '01',
'w': '15',
'ws': '15',
'g': ''}
issues = ['{}{:02}{}'.format(year, month, days[pub]) for month in range(1, 13)]
success = False
for issue in issues:
try:
request = PublicationData(pub, issue=issue, lang=global_language)
get_pub_data(request)
PublicationItem(next(cache.publ.select(request))).add_item_in_kodi(total=len(issues))
success = True
except NotFoundError:
pass
if not success:
xbmcgui.Dialog().ok('', S.NOT_AVAIL)
# Note: return will prevent Kodi from creating an empty folder view
return
xbmcplugin.endOfDirectory(addon_handle)
def pub_content_page(pubdata):
# type: (PublicationData) -> None
"""Browse any publication"""
if pubdata.booknum == 0:
# Always get a refreshed bible index page
pub, content = download_pub_data(pubdata)
items = map(PublicationItem, content)
else:
xbmcplugin.setContent(addon_handle, 'songs')
pub, media_list = download_pub_data(pubdata)
items = [MediaItem(m) for m in sorted(media_list, key=lambda x: x.track)]
for item in items:
item.add_item_in_kodi()
xbmcplugin.endOfDirectory(addon_handle)
def books_page():
"""Display all cached books"""
items = [PublicationItem(result)
for result in cache.publ.select(PublicationData(lang=global_language))
if result.pub not in ('g', 'w', 'wp', 'ws', 'nwt', 'bi12')
if result.failed is None]
for b in sorted(items, key=lambda x: x.title):
b.add_item_in_kodi()
MenuItem(
url=request_to_self(M.ADD_BOOKS),
title=S.ADD_MORE
).add_item_in_kodi()
xbmcplugin.endOfDirectory(addon_handle)
def add_books_dialog(auto=False):
"""Try to add a bunch of publications, or enter one manually"""
books = 'bh bhs bt cf cl fg fy gt hf hl ia jd jl jr jy kr la lc lfb ll lr lv lvs mb my rj rr th yb10 yb11 ' \
'yb12 yb13 yb14 yb15 yb16 yb17 yc ypq'.split()
if auto or xbmcgui.Dialog().yesno(S.AUTO_SCAN, S.SCAN_QUESTION):
progressbar = xbmcgui.DialogProgress()
progressbar.create(S.AUTO_SCAN)
try:
for i in range(len(books)):
if progressbar.iscanceled():
break
progressbar.update(i * 100 // len(books), S.SCANNING + ' ' + books[i])
try:
get_pub_data(PublicationData(pub=books[i], lang=global_language))
except NotFoundError:
pass
else:
xbmcgui.Dialog().ok(S.AUTO_SCAN, S.SCAN_DONE)
finally:
progressbar.close()
else:
code = xbmcgui.Dialog().input(S.ENTER_PUB)
if code:
try:
get_pub_data(PublicationData(pub=code, lang=global_language))
xbmcgui.Dialog().ok('', S.PUB_ADDED)
except NotFoundError:
xbmcgui.Dialog().ok('', S.WRONG_CODE)
def language_dialog(pubdata=None, track=None, preselect=None):
# type: (PublicationData, int, str) -> None
"""Show a dialog window with languages
:param pubdata: Search available language for this publication, instead of globally (needs track)
:param track: Dialog will play this track instead of setting language (needs pubdata)
:param preselect: ISO language code to search for and set as global language
"""
progressbar = xbmcgui.DialogProgress()
progressbar.create('', S.LOADING_LANG)
progressbar.update(1)
try:
# Get language data in the form of (lang, name)
if pubdata:
data = getpubmedialinks_json(pubdata, alllangs=True)
# Note: data['languages'] is a dict
languages = [(code, data['languages'][code]['name']) for code in data['languages']]
# Sort by name (list is provided sorted by code)
languages.sort(key=lambda x: x[1])
else:
# Note: the list from jw.org is already sorted by ['name']
data = get_json(LANGUAGE_API)
if preselect:
for l in data['languages']:
if l.get('symbol') == preselect:
log('autoselecting language: {}'.format(l['langcode']))
set_language_action(l['langcode'], l['name'] + ' / ' + l['vernacularName'])
return
else:
raise StopIteration
else:
# Note: data['languages'] is a list
languages = [(l['langcode'], l['name'] + ' / ' + l['vernacularName'])
for l in data['languages']]
# Get the languages matching the ones from history and put them first
history = addon.getSetting(SettingID.LANG_HIST).split()
languages = [l for l in languages if l[0] in history] + languages
dialog_strings = []
dialog_actions = []
for code, name in languages:
dialog_strings.append(name)
if pubdata:
request = request_to_self(M.PLAY, pubdata=pubdata, lang=code, track=track)
# Opens normally, like from a folder view
dialog_actions.append('PlayMedia(' + request + ')')
else:
request = request_to_self(M.SET_LANG, lang=code, langname=name)
# RunPlugin opens in the background
dialog_actions.append('RunPlugin(' + request + ')')
finally:
progressbar.close()
selection = xbmcgui.Dialog().select('', dialog_strings)
if selection >= 0:
xbmc.executebuiltin(dialog_actions[selection])
def set_language_action(lang, printable_name=None):
"""Save a language setting"""
addon.setSetting(SettingID.LANG, lang)
addon.setSetting(SettingID.LANG_NAME, printable_name or lang)
save_language_history(lang)
if lang != 'E' and enable_scrapper:
update_translations(lang)
def save_language_history(lang):
"""Save a language code first in history"""
history = addon.getSetting(SettingID.LANG_HIST).split()
history = [lang] + [h for h in history if h != lang]
history = history[0:5]
addon.setSetting(SettingID.LANG_HIST, ' '.join(history))
def play_track(pubdata, track, resolve=False):
# type: (PublicationData, int, bool) -> None
"""Start playback of a track in a publication"""
try:
pub, media_list = download_pub_data(pubdata)
item = next(MediaItem(m) for m in media_list if m.track == track)
if resolve:
xbmcplugin.setResolvedUrl(addon_handle, True, item.listitem_with_resolved_url())
else:
pl = xbmc.PlayList(xbmc.PLAYLIST_MUSIC)
pl.clear()
pl.add(item.resolved_url, item.listitem())
xbmc.Player().play(pl)
except (NotFoundError, StopIteration):
xbmcgui.Dialog().ok('', S.NOT_AVAIL)
addon_handle = int(sys.argv[1]) # needed for gui
addon = xbmcaddon.Addon() # needed for info
global_language = addon.getSetting(SettingID.LANG) or 'E'
enable_scrapper = addon.getSetting(SettingID.SCRAPPER) == 'true'
addon_dir = xbmc.translatePath(addon.getAddonInfo('profile'))
try:
os.makedirs(addon_dir) # needed first run
except OSError:
pass
cache_path = os.path.join(addon_dir, 'cache.db')
# Special class that will lookup its values in Kodi's language file
S = LocalizedStringID(addon.getLocalizedString)
# Tested in Kodi 18: disables all viewtypes except list, and there will be no icons in the list
xbmcplugin.setContent(addon_handle, 'files')
# The awkward way Kodi passes arguments to the add-on...
# argv[2] is a URL query string, probably passed by request_to_self()
# example: ?mode=play&media=ThisVideo
args = parse_qs(sys.argv[2][1:])
# parse_qs puts the values in a list, so we grab the first value for each key
args = {k: v[0] for k, v in args.items()}
arg_mode = args.get(Q.MODE)
# Do this before connecting to database
if arg_mode == M.CLEAN_CACHE:
if xbmcgui.Dialog().yesno(S.CLEAN_CACHE, S.CLEAN_QUESTION):
if os.path.exists(cache_path):
os.remove(cache_path)
xbmcgui.Dialog().ok(S.CLEAN_CACHE, S.CACHE_CLEANED)
try:
log('cache database: ' + cache_path)
cache = CacheDatabase(cache_path)
# Special class that will lookup its values in the database of scrapped translations
if enable_scrapper:
T = ScrappedStringID(get_translation)
else:
# This will return None for all lookups
T = ScrappedStringID(lambda x: None)
arg_pub = PublicationData(pub=args.get(Q.PUB),
issue=args.get(Q.ISSUE),
lang=args.get(Q.LANG) or global_language,
booknum=args.get(Q.BOOKNUM) and int(args[Q.BOOKNUM]))
if arg_mode is None:
top_level_page()
elif arg_mode == M.BIBLE:
bible_page()
elif arg_mode == M.MAGAZINES:
magazine_page(pub=args.get(Q.PUB), year=args.get(Q.YEAR) and int(args[Q.YEAR]))
elif arg_mode == M.BOOKS:
books_page()
elif arg_mode == M.ADD_BOOKS:
add_books_dialog()
elif arg_mode == M.LANGUAGES:
if arg_pub.pub:
language_dialog(pubdata=arg_pub, track=int(args[Q.TRACK]))
else:
language_dialog()
elif arg_mode == M.SET_LANG:
set_language_action(args[Q.LANG], args.get(Q.LANG_NAME))
elif arg_mode == M.OPEN:
if Q.TRACK in args:
play_track(arg_pub, int(args[Q.TRACK]), resolve=True)
else:
pub_content_page(arg_pub)
elif arg_mode == M.PLAY:
save_language_history(arg_pub.lang)
play_track(arg_pub, int(args[Q.TRACK]))
elif arg_mode == M.CLEAN_CACHE:
# Since translations was removed with the cache, update them now
if global_language != 'E' and enable_scrapper:
update_translations(global_language)
# Note: no need to close database, due to how sqlite works
# Only point in closing a connection would be to free memory
# but this script runs and exits, so there's no point in that
except DBError:
log('unknown database error', level=xbmc.LOGERROR)
log(traceback.format_exc(), level=xbmc.LOGERROR)
notification(S.DB_ERROR)
exit(1)