diff --git a/deemix-pyweb.py b/deemix-pyweb.py new file mode 100644 index 0000000..3668bb9 --- /dev/null +++ b/deemix-pyweb.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QDialog, QVBoxLayout +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage, QWebEngineProfile +from PyQt5.QtCore import QUrl, pyqtSignal +from PyQt5.QtGui import QIcon + +import json +import webbrowser + +from threading import Thread, Lock, Semaphore +import sys +import os.path as path +from os import makedirs +from time import sleep +from server import run_server +from http.client import HTTPConnection +from deemix.utils.localpaths import getConfigFolder + +server_lock = Lock() + +class LoginWindow(QDialog): + class CustomPage(QWebEnginePage): + def acceptNavigationRequest(self, url, type, main): + if url.toString() == "https://www.deezer.com/": + url = QUrl('https://www.deezer.com/ajax/gw-light.php?method=user.getArl&input=3&api_version=1.0&api_token=null') + self.setUrl(url) + return False + return super().acceptNavigationRequest(url, type, main) + + def __init__(self, parent): + super().__init__(parent) + self.webview = QWebEngineView() + profile = QWebEngineProfile(self.webview) + profile.setPersistentCookiesPolicy(QWebEngineProfile.NoPersistentCookies) + self.page = self.CustomPage(profile, self.webview) + self.page.loadFinished.connect(self.checkURL) + self.webview.setPage(self.page) + self.webview.setUrl(QUrl("https://deezer.com/login")) + layout = QVBoxLayout() + layout.addWidget(self.webview) + self.setLayout(layout) + self.arl = None + self.exec_() + + def checkURL(self, ok): + url = self.webview.url().toString() + if 'user.getArl' in url: + sleep(1) + self.webview.page().toPlainText(self.saveARL) + + def saveARL(self, body): + if body.startswith("{"): + self.arl = json.loads(body)['results'] + self.accept() + +class MainWindow(QMainWindow): + selectDownloadFolder_trigger = pyqtSignal() + appLogin_trigger = pyqtSignal() + + class MainWebpage(QWebEnginePage): + def __init__(self, parent): + super().__init__(parent) + actions = [0,1,2,3,10,11,12,13,14,31,16,19,25,26,28,30,32] + for a in actions: + self.action(a).setVisible(False) + + class ExternalWebpage(QWebEnginePage): + def __init__(self, parent): + super().__init__(parent) + self.urlChanged.connect(self.open_browser) + + def open_browser(self, url): + page = self.sender() + webbrowser.open(url.toString(), 2, True) + page.deleteLater() + + def createWindow(self, _type): + page = None + if _type == QWebEnginePage.WebBrowserTab: + page = self.ExternalWebpage(self) + return page + + def __init__(self, title, url, x=None, y=None, w=800, h=600): + super().__init__() + self.resize(w, h) + self.setWindowTitle(title) + self.setWindowIcon(QIcon(path.join(appDir, 'icon.ico'))) + self.setMinimumSize(800, 600) + self.webview = QWebEngineView() + self.page = self.MainWebpage(self.webview) + self.page.loadFinished.connect(self.finishLoading) + self.webview.setPage(self.page) + self.setCentralWidget(self.webview) + self.url = url + + self.downloadFolder = None + self.selectDownloadFolder_trigger.connect(self.selectDownloadFolder) + self._selectDownloadFolder_semaphore = Semaphore(0) + + self.arl = None + self.appLogin_trigger.connect(self.appLogin) + self._appLogin_semaphore = Semaphore(0) + + if x is not None and y is not None: + self.move(x, y) + else: + center = QApplication.desktop().availableGeometry().center() - self.rect().center() + self.move(center.x(), center.y()) + + def showWindow(self): + self.webview.setUrl(QUrl(self.url)) + self.show() + + def selectDownloadFolder(self): + filename = QFileDialog.getExistingDirectory(self, "Select Download Folder", options=QFileDialog.ShowDirsOnly) + self.downloadFolder = filename + self._selectDownloadFolder_semaphore.release() + + def appLogin(self): + loginWindow = LoginWindow(self) + self.arl = loginWindow.arl + self._appLogin_semaphore.release() + loginWindow.page.deleteLater() + loginWindow.webview.deleteLater() + loginWindow.deleteLater() + + def closeEvent(self, event): + x = int(self.x()) + y = int(self.y()) + w = int(self.width()) + h = int(self.height()) + if x < 0: x = 0 + if y < 0: y = 0 + with open(path.join(configFolder, '.UIposition'), 'w') as f: + f.write("|".join([str(x),str(y),str(w),str(h)])) + event.accept() + + def finishLoading(self, ok): + if ok: self.webview.page().runJavaScript("window.dispatchEvent(new Event('pywebviewready'))") + +def url_ok(url, port): + try: + conn = HTTPConnection(url, port) + conn.request('GET', '/') + r = conn.getresponse() + return r.status == 200 + except: + print("Server not started") + return False + +def save_position(): + window = webview.windows[0] + x = int(window.x) + y = int(window.y) + w = int(window.width) + h = int(window.height) + if x < 0: x = 0 + if y < 0: y = 0 + with open(path.join(configFolder, '.UIposition'), 'w') as f: + f.write("|".join([str(x),str(y),str(w),str(h)])) + +def get_position(): + if path.isfile(path.join(configFolder, '.UIposition')): + try: + with open(path.join(configFolder, '.UIposition'), 'r') as f: + (x,y,w,h) = f.read().strip().split("|") + x = int(x) + y = int(y) + w = int(w) + h = int(h) + if x < 0: x = 0 + if y < 0: y = 0 + except: + x = None + y = None + w = 800 + h = 600 + else: + x = None + y = None + w = 800 + h = 600 + return (x,y,w,h) + +if __name__ == '__main__': + url = "127.0.0.1" + port = 6595 + if len(sys.argv) >= 2: + try: + port = int(sys.argv[1]) + except ValueError: + pass + portable = None + server = False + appDir = path.dirname(path.realpath(__file__)) + if '--portable' in sys.argv: + portable = path.join(appDir, 'config') + if '--server' in sys.argv or '-s' in sys.argv: + server = True + + if not server: + configFolder = portable or getConfigFolder() + x,y,w,h = get_position() + app = QApplication([]) + window = MainWindow('deemix', 'http://'+url+':'+str(port), x,y,w,h) + t = Thread(target=run_server, args=(port, url, portable, window)) + else: + t = Thread(target=run_server, args=(port, url, portable)) + t.daemon = True + t.start() + + if not server: + while not url_ok(url, port): + sleep(1) + window.showWindow() + app.exec_() + conn = HTTPConnection(url, port) + conn.request('GET', '/shutdown') + t.join() diff --git a/deemix_gui.spec b/deemix-pyweb.spec similarity index 90% rename from deemix_gui.spec rename to deemix-pyweb.spec index d17e52c..4d4d25a 100644 --- a/deemix_gui.spec +++ b/deemix-pyweb.spec @@ -7,7 +7,7 @@ block_cipher = None sys.modules['FixTk'] = None -a = Analysis(['deemix_gui.py'], +a = Analysis(['deemix-pyweb.py'], binaries=[], datas=[('webui/public', 'webui/public')], hiddenimports=['engineio.async_drivers.threading', 'pkg_resources.py2_warn'], @@ -27,7 +27,7 @@ if sys.platform.startswith('darwin'): a.zipfiles, a.datas, [], - name='deemix_gui', + name='deemix-pyweb', debug=False, bootloader_ignore_signals=False, strip=False, @@ -37,7 +37,7 @@ if sys.platform.startswith('darwin'): console=False, icon=f"icon.icns") app = BUNDLE(exe, - name='deemix_gui.app', + name='deemix-pyweb.app', icon="icon.icns", bundle_identifier=None) else: @@ -45,7 +45,7 @@ else: a.scripts, [], exclude_binaries=True, - name='deemix_gui', + name='deemix-pyweb', debug=False, bootloader_ignore_signals=False, strip=False, @@ -59,4 +59,4 @@ else: strip=False, upx=True, upx_exclude=[], - name='deemix_gui') + name='deemix-pyweb') diff --git a/deemix_gui.py b/deemix_gui.py deleted file mode 100644 index ad8475c..0000000 --- a/deemix_gui.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -import webview - -from threading import Thread, Lock -import sys -import os.path as path -from os import makedirs -from time import sleep -from server import run_server -from http.client import HTTPConnection -from deemix.utils.localpaths import getConfigFolder - -server_lock = Lock() - -def url_ok(url, port): - try: - conn = HTTPConnection(url, port) - conn.request('GET', '/') - r = conn.getresponse() - return r.status == 200 - except: - print("Server not started") - return False - -def save_position(): - window = webview.windows[0] - x = int(window.x) - y = int(window.y) - w = int(window.width) - h = int(window.height) - if x < 0: x = 0 - if y < 0: y = 0 - with open(path.join(configFolder, '.UIposition'), 'w') as f: - f.write("|".join([str(x),str(y),str(w),str(h)])) - -if __name__ == '__main__': - url = "127.0.0.1" - port = 6595 - if len(sys.argv) >= 2: - try: - port = int(sys.argv[1]) - except ValueError: - pass - portable = None - server = False - if '--portable' in sys.argv: - portable = path.join(path.dirname(path.realpath(__file__)), 'config') - if '--server' in sys.argv or '-s' in sys.argv: - server = True - - t = Thread(target=run_server, args=(port, url, portable)) - t.daemon = True - t.start() - - if not server: - while not url_ok(url, port): - sleep(1) - if portable: - configFolder = portable - else: - configFolder = getConfigFolder() - - if path.isfile(path.join(configFolder, '.UIposition')): - try: - with open(path.join(configFolder, '.UIposition'), 'r') as f: - (x,y,w,h) = f.read().strip().split("|") - x = int(x) - y = int(y) - w = int(w) - h = int(h) - if x < 0: x = 0 - if y < 0: y = 0 - except: - x = None - y = None - w = 800 - h = 600 - else: - x = None - y = None - w = 800 - h = 600 - window = webview.create_window('deemix', 'http://'+url+':'+str(port), - x=x, y=y, width=w, height=h, text_select=True) - window.closing += save_position - if sys.platform == "win32": - from webview.platforms.cef import settings - cacheFolder = path.join(configFolder, 'cefCache') - makedirs(cacheFolder, exist_ok=True) - settings.update({ - 'persist_session_cookies': True, - 'cache_path': cacheFolder - }) - webview.start(gui='cef') - elif sys.platform == "linux": - webview.start(gui='qt') - else: - webview.start() - conn = HTTPConnection(url, port) - conn.request('GET', '/shutdown') - t.join() diff --git a/requirements.txt b/requirements.txt index 6d417dd..f9d207c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ deemix>=1.2.7 flask flask-socketio -pywebview>=3.3.2 -cefpython3; platform_system == "Windows" -pywebview[qt]>=3.3.2; platform_system == "Linux" +pyqt5 +pyqtwebengine diff --git a/server.py b/server.py index 0f87022..68e573c 100644 --- a/server.py +++ b/server.py @@ -25,6 +25,7 @@ from engineio.payload import Payload Payload.max_decode_packets = 500 app = None +gui = None class CustomFlask(Flask): jinja_options = Flask.jinja_options.copy() @@ -288,59 +289,33 @@ def openDownloadsFolder(): @socketio.on('selectDownloadFolder') def selectDownloadFolder(): - try: - import webview - result = webview.windows[0].create_file_dialog(webview.FOLDER_DIALOG, allow_multiple=False) + if gui: + gui.selectDownloadFolder_trigger.emit() + gui._selectDownloadFolder_semaphore.acquire() + result = gui.downloadFolder if result: emit('downloadFolderSelected', result) - except: - print("Can't open folder selection, you're not running pywebview") + else: + print("Can't open folder selection, you're not running the gui") @socketio.on('applogin') def applogin(): - try: - import webview - global loginWindow - if not loginWindow: - @copy_current_request_context - def get_ARL(): - global loginWindow - window = webview.windows[loginWindow] - loginWindow = False - window.loaded -= get_ARL - arl = json.loads(window.get_elements("body")[0]['innerText'])['results'] - window.destroy() - emit('applogin_arl', arl) - @copy_current_request_context - def check_URL(): - global loginWindow - window = webview.windows[loginWindow] - try: - url = window.get_current_url() - except: - url = "https://www.deezer.com/us/login" - if not "/login" in url: - window.loaded -= check_URL - window.loaded += get_ARL - window.load_url('https://www.deezer.com/ajax/gw-light.php?method=user.getArl&input=3&api_version=1.0&api_token=null') - @copy_current_request_context - def on_close(): - global loginWindow - if loginWindow: - loginWindow = False - if not session['dz'].logged_in: - window = webview.create_window('Login into your deezer account', "https://deezer.com/login") - loginWindow = len(webview.windows)-1 - window.loaded += check_URL - window.closed += on_close - else: - emit('logged_in', {'status': 2, 'user': session['dz'].user}) - except: - print("Can't open folder selection, you're not running pywebview") + if gui: + if not session['dz'].logged_in: + gui.appLogin_trigger.emit() + gui._appLogin_semaphore.acquire() + if gui.arl: + emit('applogin_arl', gui.arl) + gui.arl = None + else: + emit('logged_in', {'status': 2, 'user': session['dz'].user}) + else: + print("Can't open login page, you're not running the gui") -def run_server(port, host="127.0.0.1", portable=None): - global app +def run_server(port, host="127.0.0.1", portable=None, mainWindow=None): + global app, gui app = deemix(portable) + gui = mainWindow print("Starting server at http://" + host + ":" + str(port)) socketio.run(server, host=host, port=port)