You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
plugin.video.rumble/main.py

726 lines
23 KiB
Python

# -*- coding: utf-8 -*-
import sys
import re
import os
import xbmc
import xbmcplugin
import xbmcgui
import xbmcaddon
import xbmcvfs
import six
from six.moves import urllib
2 years ago
from resources.general import *
from resources.rumbleUser import rumbleUser
2 years ago
2 years ago
try:
import json
except ImportError:
2 years ago
import simplejson as json
BASE_URL = 'https://rumble.com'
PLUGIN_URL = sys.argv[0]
PLUGIN_ID = int(sys.argv[1])
PLUGIN_NAME = PLUGIN_URL.replace('plugin://','')
2 years ago
ADDON = xbmcaddon.Addon()
ADDON_ICON = ADDON.getAddonInfo('icon')
ADDON_NAME = ADDON.getAddonInfo('name')
HOME_DIR = 'special://home/addons/{0}'.format(PLUGIN_NAME)
2 years ago
RESOURCE_DIR = HOME_DIR + 'resources/'
MEDIA_DIR = RESOURCE_DIR + 'media/'
2 years ago
kodi_version = float(xbmcaddon.Addon('xbmc.addon').getAddonInfo('version')[:4])
date_format = ADDON.getSetting('date_format')
rumbleUser = rumbleUser()
if six.PY2:
favorites = xbmc.translatePath(os.path.join(ADDON.getAddonInfo('profile'), 'favorites.dat'))
2 years ago
else:
favorites = xbmcvfs.translatePath(os.path.join(ADDON.getAddonInfo('profile'), 'favorites.dat'))
def favorites_create():
""" creates favorite directory if doesn't exist """
if six.PY2:
addon_data_path = xbmc.translatePath(ADDON.getAddonInfo('profile'))
else:
addon_data_path = xbmcvfs.translatePath(ADDON.getAddonInfo('profile'))
if os.path.exists(addon_data_path) is False:
os.mkdir(addon_data_path)
xbmc.sleep(1)
def favorites_load( return_string = False ):
""" load favourites from file into variable """
if os.path.exists( favorites ):
fav_str = open( favorites ).read()
if return_string:
return fav_str
if fav_str:
return json.loads( fav_str )
else:
favorites_create()
# nothing to load, return type necessary
if return_string:
return ''
return []
2 years ago
def to_unicode( text, encoding='utf-8', errors='strict' ):
""" Forces text to unicode """
2 years ago
if isinstance(text, bytes):
return text.decode(encoding, errors=errors)
2 years ago
return text
def get_search_string( heading='', message='' ):
""" Ask the user for a search string """
2 years ago
search_string = None
2 years ago
keyboard = xbmc.Keyboard(message, heading)
keyboard.doModal()
2 years ago
if keyboard.isConfirmed():
search_string = to_unicode(keyboard.getText())
2 years ago
return search_string
def home_menu():
""" Creates home menu """
2 years ago
# Search
add_dir( get_string(137), '', 1, MEDIA_DIR + 'search.png', '', '' ,'' )
2 years ago
# Favorites
add_dir( get_string(1036), '', 7, MEDIA_DIR + 'favorite.png', '', '', '' )
if rumbleUser.hasLoginDetails():
# subscriptions
add_dir( 'Subscriptions', BASE_URL + '/subscriptions', 3, MEDIA_DIR + 'favorite.png', '', '', 'other' )
# subscriptions
add_dir( 'Following', BASE_URL + '/', 3, MEDIA_DIR + 'favorite.png', '', '', 'following' )
2 years ago
# News
add_dir( get_string(29916), BASE_URL + '/category/news', 3, MEDIA_DIR + 'news.png', '', '', 'other' )
2 years ago
# Viral
add_dir( get_string(30050), BASE_URL + '/category/viral', 3, MEDIA_DIR + 'viral.png', '', '', 'other' )
2 years ago
# Podcasts
add_dir( get_string(30051), BASE_URL + '/category/podcasts', 3, MEDIA_DIR +'podcast.png','','','other')
2 years ago
# Battle Leaderboard
add_dir( get_string(30052), BASE_URL + '/battle-leaderboard', 3, MEDIA_DIR + 'leader.png', '', '', 'top' )
2 years ago
# Entertainment
add_dir( get_string(30053), BASE_URL + '/category/entertainment', 3, MEDIA_DIR + 'entertaiment.png', '', '', 'other' )
2 years ago
# Sports
add_dir( get_string(19548), BASE_URL + '/category/sports', 3, MEDIA_DIR + 'sports.png', '', '', 'other' )
2 years ago
# Science
add_dir( get_string(29948), BASE_URL + '/category/science', 3, MEDIA_DIR + 'science.png', '', '', 'other' )
2 years ago
# Technology
add_dir( get_string(30054), BASE_URL + '/category/technology', 3, MEDIA_DIR + 'technology.png', '', '', 'other' )
2 years ago
# Vlogs
add_dir( get_string(30055), BASE_URL + '/category/vlogs', 3, MEDIA_DIR + 'vlog.png', '', '', 'other' )
2 years ago
# Settings
add_dir( get_string(5), '', 8, MEDIA_DIR + 'settings.png', '', '', '' )
view_set('WideList')
2 years ago
xbmcplugin.endOfDirectory(PLUGIN_ID, cacheToDisc=False)
# search menu
def search_menu():
# Search Video
add_dir( get_string(30100), BASE_URL + '/search/video?q=', 2, MEDIA_DIR + 'search.png', '', '', 'video' )
2 years ago
# Search Channel
add_dir( get_string(30101), BASE_URL + '/search/channel?q=',2,MEDIA_DIR + 'search.png', '', '', 'channel' )
# Search User
add_dir( get_string(30102), BASE_URL + '/search/channel?q=',2,MEDIA_DIR + 'search.png', '', '', 'user' )
view_set('WideList')
2 years ago
xbmcplugin.endOfDirectory(PLUGIN_ID)
def pagination( url, page, cat, search=False ):
2 years ago
if url > '':
page = int(page)
page_url = url
if page == 1:
2 years ago
if search:
page_url = url + search
elif search and cat == 'video':
page_url = url + search + "&page=" + str( page )
elif cat in {'channel', 'user', 'other' }:
page_url = url + "?page=" + str( page )
amount = list_rumble( page_url, cat )
if amount > 15 and page < 10:
# for next page
page = page + 1
name = get_string(30150) + " " + str( page )
list_item = xbmcgui.ListItem(name)
link_params = {
'url': url,
'mode': '3',
'name': name,
'page': str( page ),
'cat': cat,
}
link = build_url( link_params )
if search and cat == 'video':
link = link + "&search=" + urllib.parse.quote_plus(search)
xbmcplugin.addDirectoryItem(PLUGIN_ID, link, list_item, True)
view_set('WideList')
2 years ago
xbmcplugin.endOfDirectory(PLUGIN_ID)
def get_image( data, image_id ):
""" method to get an image from scraped page's CSS from the image ID """
image_re = re.compile(
"i.user-image--img--id-" + str( image_id ) + ".+?{ background-image: url(.+?);",
re.MULTILINE|re.DOTALL|re.IGNORECASE
).findall(data)
if image_re != []:
2 years ago
image = str(image_re[0]).replace('(', '').replace(')', '')
else:
image = ''
2 years ago
return image
def list_rumble( url, cat ):
amount = 0
headers = None
if 'subscriptions' in url or cat == 'following':
# make sure there is a session
# result is stored in a cookie
rumbleUser.hasSession()
#{ 'cookie': 'u_s=' + ADDON.getSetting('session')}
data = request_get(url, None, headers)
if 'search' in url:
if cat == 'video':
amount = dir_list_create( data, cat, 'video', True, 1 )
else:
amount = dir_list_create( data, cat, 'channel', True )
elif cat in { 'channel', 'user', 'top', 'other' }:
amount = dir_list_create( data, cat, 'video', False, 2 )
elif cat == 'following':
amount = dir_list_create( data, cat, 'following', False, 2 )
return amount
def dir_list_create( data, cat, video_type='video', search = False, play=False ):
""" create and display dir list based upon type """
amount = 0
if video_type == 'video':
videos = re.compile(r'a href=([^\>]+)><div class=\"(?:[^\"]+)\"><img class=\"video-item--img\" src=(https:\/\/.+?) alt=(?:[^\>]+)>(?:<span class=\"video-item--watching\">[^\<]+</span>)?(?:<div class=video-item--overlay-rank>(?:[0-9]+)</div>)?</div><(?:[^\>]+)></span></a><div class=\"video-item--info\"><time class=\"video-item--meta video-item--time\" datetime=(.+?)-(.+?)-(.+?)T(?:.+?) title\=\"(?:[^\"]+)\">(?:[^\<]+)</time><h3 class=video-item--title>(.+?)</h3><address(?:[^\>]+)><a rel=author class=\"(?:[^\=]+)=(.+?)><div class=ellipsis-1>(.+?)</div>', re.MULTILINE|re.DOTALL|re.IGNORECASE).findall(data)
if videos:
amount = len(videos)
for link, img, year, month, day, title, channel_link, channel_name in videos:
if '<svg' in channel_name:
channel_name = channel_name.split('<svg')[0] + " (Verified)"
video_title = '[B]' + title + '[/B]\n[COLOR gold]' + channel_name + ' - [COLOR lime]' + get_date_formatted( date_format, year, month, day ) + '[/COLOR]'
#open get url and open player
add_dir( video_title, BASE_URL + link, 4, str(img), str(img), '', cat, False, True, play, { 'name' : channel_link, 'subscribe': True } )
elif video_type == 'following':
following = re.compile(r'<a class=\"main-menu-item main-menu-item-channel\" title=\"?(?:[^\"]+)\"? href=([^>]+)>\s*<i class=\'user-image (?:user-image--img user-image--img--id-([^\']+)\')?(?:user-image--letter\' data-letter=([a-zA-Z]))?></i>\s*<span class=\"main-menu-item-label main-menu-item-channel-label\">([^<]+)</span>', re.MULTILINE|re.DOTALL|re.IGNORECASE).findall(data)
if following:
amount = len(following)
for link, img_id, img_letter, channel_name in following:
if img_id:
img = str( get_image( data, img_id ) )
else:
img = MEDIA_DIR + 'letters/' + img_letter + '.png'
video_title = '[B]' + channel_name + '[/B]'
#open get url and open player
add_dir( video_title, BASE_URL + link, 3, img, img, '', 'other', True, True, play, { 'name' : link, 'subscribe': False } )
else:
channels = re.compile(r'a href=(.+?)>\s*<div class=\"channel-item--img\">\s*<i class=\'user-image user-image--img user-image--img--id-(.+?)\'></i>\s*</div>\s*<h3 class=channel-item--title>(.+?)</h3>\s*<span class=channel-item--subscribers>(.+?) subscribers</span>',re.DOTALL).findall(data)
if channels:
amount = len(channels)
for link, img_id, channel_name, subscribers in channels:
# split channel and user
if search:
if cat == 'channel':
if '/c/' not in link:
continue
else:
if '/user/' not in link:
continue
if '<svg' in channel_name:
channel_name = channel_name.split('<svg')[0] + " (Verified)"
img = str( get_image( data, img_id ) )
video_title = '[B]' + channel_name + '[/B]\n[COLOR palegreen]' + subscribers + ' [COLOR yellow]' + get_string(30155) + '[/COLOR]'
#open get url and open player
add_dir( video_title, BASE_URL + link, 3, img, img, '', cat, True, True, play, { 'name' : link, 'subscribe': True } )
return amount
2 years ago
def resolver( url ):
""" Resolves a URL for rumble & returns resolved link to video """
2 years ago
# playback options - 0: large to small, 1: small to large, 2: quality select
playback_method = ADDON.getSetting('playbackMethod')
2 years ago
media_url = False
2 years ago
if playback_method == '2':
urls = []
2 years ago
data = request_get(url)
# gets embed id from embed url
embed_id = re.compile(
',\"embedUrl\":\"' + BASE_URL + '/embed/(.*?)\/\",',
re.MULTILINE|re.DOTALL|re.IGNORECASE
).findall(data)
if embed_id:
# use site api to get video urls
# TODO: use as dict / array instead of using regex to get URLs
data = request_get(BASE_URL + '/embedJS/u3/?request=video&ver=2&v=' + embed_id[0])
2 years ago
sizes = [ '1080', '720', '480', '360', 'hls' ]
2 years ago
# reverses array - small to large
if playback_method == '1':
2 years ago
sizes = sizes[::-1]
for quality in sizes:
# get urls for quality
matches = re.compile(
'"' + quality + '".+?url.+?:"(.*?)"',
re.MULTILINE|re.DOTALL|re.IGNORECASE
).findall(data)
2 years ago
if matches:
if playback_method == '2':
2 years ago
urls.append(( quality, matches[0] ))
else:
media_url = matches[0]
2 years ago
break
# quality select
if playback_method == '2':
2 years ago
if len(urls) > 0:
selected_index = xbmcgui.Dialog().select(
2 years ago
'Select Quality', [(sourceItem[0] or '?') for sourceItem in urls]
)
if selected_index != -1:
media_url = urls[selected_index][1]
2 years ago
if media_url:
media_url = media_url.replace('\/', '/')
return media_url
2 years ago
def play_video( name, url, iconimage, play=2 ):
""" method to play video """
# get video link
2 years ago
url = resolver(url)
if url:
# Use HTTP
if ADDON.getSetting('useHTTP') == 'true':
url = url.replace('https://', 'http://', 1)
list_item = xbmcgui.ListItem(name, path=url)
list_item.setArt({'icon': iconimage, 'thumb': iconimage})
if kodi_version > 19.8:
vidtag = list_item.getVideoInfoTag()
vidtag.setTitle(name)
else:
list_item.setInfo(type='video', infoLabels={'Title': name, 'plot': ''})
if play == 1:
xbmc.Player().play(item=url, listitem=list_item)
elif play == 2:
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, list_item)
else:
xbmcgui.Dialog().ok( 'Error', 'Video not found' )
2 years ago
def search_items( url, cat ):
""" Searches rumble """
search_str = get_search_string(heading="Search")
if not search_str:
return False, 0
title = urllib.parse.quote_plus(search_str)
pagination( url, 1, cat, title )
2 years ago
def favorites_show():
""" Displays favorites """
2 years ago
data = favorites_load()
2 years ago
2 years ago
try:
amount = len(data)
if amount > 0:
for i in data:
2 years ago
name = i[0]
url = i[1]
mode = i[2]
iconimage = i[3]
fan_art = i[4]
2 years ago
description = i[5]
cat = i[6]
folder = ( i[7] == 'True' )
2 years ago
play = i[8]
add_dir( name, url, mode, str(iconimage), str(fan_art), str(description), cat, folder, True, int(play) )
view_set('WideList')
2 years ago
xbmcplugin.endOfDirectory(PLUGIN_ID)
else:
xbmcgui.Dialog().ok( get_string(14117), get_string(30155) )
except Exception:
view_set('WideList')
2 years ago
xbmcplugin.endOfDirectory(PLUGIN_ID)
def favorite_add(name, url, fav_mode, iconimage, fanart, description, cat, folder, play):
""" add favorite from name """
data = favorites_load()
data.append((name, url, fav_mode, iconimage, fanart, description, cat, folder, play))
fav_file = open( favorites, 'w' )
fav_file.write(json.dumps(data))
fav_file.close()
notify( get_string(30152), name, iconimage )
2 years ago
def favorite_remove( name ):
""" remove favorite from name """
# TODO: remove via something more unique instead
# TODO: remove via a method that doesn't require to loop through all favorites
data = favorites_load()
if data:
for index in range(len(data)):
if data[index][0] == name:
del data[index]
fav_file = open( favorites, 'w' )
fav_file.write(json.dumps(data))
fav_file.close()
break
notify( get_string(30154), name )
2 years ago
def favorites_import():
""" Due to plugin name change from original fork, the favorites will need to be imported """
if not xbmcgui.Dialog().yesno(
'Import Favorites',
'This will replace the favorites with the plugin.video.rumble.matrix version.\nProceed?',
nolabel = 'Cancel',
yeslabel = 'Ok'
):
return
# no point trying to run this as it didn't exist for python 2
if six.PY2:
notify( 'Favorites Not Found' )
return
# make sure path exists
favorites_create()
#load matrix favourites
rumble_matrix_dir = xbmcvfs.translatePath(os.path.join('special://home/userdata/addon_data/plugin.video.rumble.matrix', 'favorites.dat'))
if os.path.exists(rumble_matrix_dir):
rumble_matrix = open( rumble_matrix_dir ).read()
if rumble_matrix:
fav_file = open( favorites, 'w' )
fav_file.write(rumble_matrix)
fav_file.close()
notify( 'Imported Favorites' )
return
notify( 'Favorites Not Found' )
def login_session_reset():
""" Forces a rumble session reset """
rumbleUser.resetSessionDetails()
notify( 'Session has been reset' )
def subscribe( name, action ):
""" Attempts to (un)subscribe to rumble channel """
# make sure we have a session
if rumbleUser.hasSession():
action_type = False
if '/user/' in name:
name = name.replace( '/user/', '' )
action_type = 'user'
elif '/c/' in name:
name = name.replace( '/c/', '' )
action_type = 'channel'
if action_type:
# subscribe to action
data = rumbleUser.subscribe( action, action_type, name )
# TODO: use data for sanity check
if action == 'subscribe':
notify( 'Subscribed to ' + name )
else:
notify( 'Unubscribed to ' + name )
return True
notify( 'Unable to to perform action' )
return False
def add_dir(name, url, mode, iconimage, fanart, description, cat, folder=True, fav_context=False, play=False, subscribe_context=False):
2 years ago
link_params = {
'url': url,
'mode': str( mode ),
'name': name,
'fanart': fanart,
'iconimage': iconimage,
'description': description,
'cat': cat,
}
2 years ago
context_menu = []
2 years ago
if play:
link_params['play'] = str( play )
link = build_url( link_params )
list_item = xbmcgui.ListItem( name )
2 years ago
if folder:
list_item.setArt({'icon': 'DefaultFolder.png', 'thumb': iconimage})
2 years ago
else:
list_item.setArt({'icon': 'DefaultVideo.png', 'thumb': iconimage})
2 years ago
if play == 2 and mode == 4:
list_item.setProperty('IsPlayable', 'true')
context_menu.append((get_string(30158), 'Action(Queue)'))
if kodi_version > 19.8:
vidtag = list_item.getVideoInfoTag()
vidtag.setMediaType('video')
vidtag.setTitle(name)
vidtag.setPlot(description)
else:
list_item.setInfo(type='Video', infoLabels={'Title': name, 'Plot': description})
2 years ago
if fanart > '':
list_item.setProperty('fanart_image', fanart)
2 years ago
else:
list_item.setProperty('fanart_image', HOME_DIR + 'fanart.jpg')
if rumbleUser.hasLoginDetails():
if subscribe_context:
if subscribe_context['subscribe']:
context_menu.append(('Subscribe to ' + subscribe_context['name'],'RunPlugin(%s)' % build_url( {'mode': '11','name': subscribe_context['name'], 'cat': 'subscribe'} )))
else:
context_menu.append(('Unsubscribe to ' + subscribe_context['name'],'RunPlugin(%s)' % build_url( {'mode': '11','name': subscribe_context['name'], 'cat': 'unsubscribe'} )))
if fav_context:
favorite_str = favorites_load( True )
2 years ago
2 years ago
try:
2 years ago
name_fav = json.dumps(name)
except Exception:
2 years ago
name_fav = name
2 years ago
try:
# checks fav name via string ( I do not like how this is done, so will try redo in future )
if name_fav in favorite_str:
context_menu.append((get_string(30153),'RunPlugin(%s)' % build_url( {'mode': '6','name': name} )))
2 years ago
else:
fav_params = {
'url': url,
'mode': '5',
'name': name,
'fanart': fanart,
'iconimage': iconimage,
'description': description,
'cat': cat,
'folder': str(folder),
'fav_mode': str(mode),
'play': str(play),
}
context_menu.append((get_string(30151),'RunPlugin(%s)' %build_url( fav_params )))
except Exception:
2 years ago
pass
if context_menu:
list_item.addContextMenuItems(context_menu)
xbmcplugin.addDirectoryItem(handle=PLUGIN_ID, url=link, listitem=list_item, isFolder=folder)
2 years ago
2 years ago
def main():
2 years ago
""" main method to start plugin """
2 years ago
params=get_params()
mode=int(params.get( 'mode', 0 ))
page=int(params.get( 'page', 1 ))
play=int(params.get( 'play', 1 ))
fav_mode=int(params.get( 'fav_mode', 0 ))
url = params.get( 'url', None )
if url:
url=urllib.parse.unquote_plus(url)
name = params.get( 'name', None )
if name:
name = urllib.parse.unquote_plus(name)
iconimage=params.get( 'iconimage', None )
if iconimage:
iconimage=urllib.parse.unquote_plus(iconimage)
fanart=params.get( 'fanart', None )
if fanart:
fanart=urllib.parse.unquote_plus(fanart)
description=params.get( 'description', None )
if description:
description=urllib.parse.unquote_plus(description)
subtitle=params.get( 'subtitle', None )
if subtitle:
subtitle=urllib.parse.unquote_plus(subtitle)
cat=params.get( 'cat', None )
if cat:
cat=urllib.parse.unquote_plus(cat)
search=params.get( 'search', None )
if search:
search=urllib.parse.unquote_plus(search)
folder=params.get( 'folder', None )
if folder:
folder=urllib.parse.unquote_plus(folder)
folder=params.get( 'folder', None )
if folder:
folder=urllib.parse.unquote_plus(folder)
if mode==0:
2 years ago
home_menu()
elif mode==1:
search_menu()
elif mode==2:
search_items(url,cat)
elif mode==3:
if search and search is not None:
pagination(url, page, cat, search)
2 years ago
else:
pagination(url, page, cat)
2 years ago
elif mode==4:
play_video(name, url, iconimage, play)
elif mode in [5,6]:
if '\\ ' in name:
2 years ago
name = name.split('\\ ')[1]
if ' - ' in name:
2 years ago
name = name.split(' - ')[0]
if mode == 5:
favorite_add( name, url, fav_mode, iconimage, fanart, description, cat, str(folder), str(play) )
else:
favorite_remove( name )
2 years ago
elif mode==7:
favorites_show()
2 years ago
elif mode==8:
ADDON.openSettings()
elif mode==9:
favorites_import()
elif mode==10:
login_session_reset()
elif mode==11:
subscribe(name, cat)
2 years ago
if __name__ == "__main__":
main()