├── .dockerignore ├── .gitignore ├── CHANGELOG ├── Dockerfile ├── LICENSE ├── README.md ├── __init__.py ├── data └── sessions │ └── .keep ├── deployment ├── nginx │ └── sample.conf └── supervisor │ └── sample.conf ├── kitana.py ├── plugins └── SassCompilerPlugin.py ├── requirements.txt ├── requirements_win32.txt ├── static ├── fonts │ ├── open-iconic.eot │ ├── open-iconic.otf │ ├── open-iconic.svg │ ├── open-iconic.ttf │ └── open-iconic.woff ├── img │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── background.jpg │ ├── browserconfig.xml │ ├── fan.png │ ├── fan.psd │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── favicon.svg │ ├── manifest.json │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ └── ms-icon-70x70.png ├── js │ └── auth.js └── sass │ ├── base.sass │ ├── bootstrap │ ├── _alert.scss │ ├── _badge.scss │ ├── _breadcrumb.scss │ ├── _button-group.scss │ ├── _buttons.scss │ ├── _card.scss │ ├── _carousel.scss │ ├── _close.scss │ ├── _code.scss │ ├── _custom-forms.scss │ ├── _dropdown.scss │ ├── _forms.scss │ ├── _functions.scss │ ├── _grid.scss │ ├── _images.scss │ ├── _input-group.scss │ ├── _jumbotron.scss │ ├── _list-group.scss │ ├── _media.scss │ ├── _mixins.scss │ ├── _modal.scss │ ├── _nav.scss │ ├── _navbar.scss │ ├── _pagination.scss │ ├── _popover.scss │ ├── _print.scss │ ├── _progress.scss │ ├── _reboot.scss │ ├── _root.scss │ ├── _spinners.scss │ ├── _tables.scss │ ├── _toasts.scss │ ├── _tooltip.scss │ ├── _transitions.scss │ ├── _type.scss │ ├── _utilities.scss │ ├── _variables.scss │ ├── bootstrap-grid.scss │ ├── bootstrap-reboot.scss │ ├── bootstrap.scss │ ├── mixins │ │ ├── _alert.scss │ │ ├── _background-variant.scss │ │ ├── _badge.scss │ │ ├── _border-radius.scss │ │ ├── _box-shadow.scss │ │ ├── _breakpoints.scss │ │ ├── _buttons.scss │ │ ├── _caret.scss │ │ ├── _clearfix.scss │ │ ├── _deprecate.scss │ │ ├── _float.scss │ │ ├── _forms.scss │ │ ├── _gradients.scss │ │ ├── _grid-framework.scss │ │ ├── _grid.scss │ │ ├── _hover.scss │ │ ├── _image.scss │ │ ├── _list-group.scss │ │ ├── _lists.scss │ │ ├── _nav-divider.scss │ │ ├── _pagination.scss │ │ ├── _reset-text.scss │ │ ├── _resize.scss │ │ ├── _screen-reader.scss │ │ ├── _size.scss │ │ ├── _table-row.scss │ │ ├── _text-emphasis.scss │ │ ├── _text-hide.scss │ │ ├── _text-truncate.scss │ │ ├── _transition.scss │ │ └── _visibility.scss │ ├── utilities │ │ ├── _align.scss │ │ ├── _background.scss │ │ ├── _borders.scss │ │ ├── _clearfix.scss │ │ ├── _display.scss │ │ ├── _embed.scss │ │ ├── _flex.scss │ │ ├── _float.scss │ │ ├── _overflow.scss │ │ ├── _position.scss │ │ ├── _screenreaders.scss │ │ ├── _shadows.scss │ │ ├── _sizing.scss │ │ ├── _spacing.scss │ │ ├── _stretched-link.scss │ │ ├── _text.scss │ │ └── _visibility.scss │ └── vendor │ │ └── _rfs.scss │ ├── darkly │ ├── _bootswatch.scss │ └── _variables.scss │ ├── iconic │ └── css │ │ └── open-iconic-bootstrap.scss │ └── main.sass ├── templates ├── base.jinja2 ├── index.jinja2 ├── messages.jinja2 ├── nav.jinja2 ├── plugins.jinja2 ├── servers.jinja2 ├── settings.jinja2 └── token.jinja2 ├── tools ├── __init__.py ├── cache.py └── urls.py └── util ├── __init__.py ├── argparse.py ├── messages.py ├── sessions.py ├── str_util.py ├── update.py └── win32.py /.dockerignore: -------------------------------------------------------------------------------- 1 | # Saved sessions 2 | data/sessions/* 3 | # Compiled files 4 | static/css/* 5 | __pycache__ 6 | # Unused by runtime 7 | .* 8 | README.md 9 | LICENSE* 10 | Dockerfile* 11 | CHANGELOG 12 | # Windows 13 | util/win32.py 14 | requirements_win32.txt 15 | # Virtual Environment 16 | venv 17 | Lib 18 | Scripts 19 | Include 20 | share 21 | tcl -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # IPython 77 | profile_default/ 78 | ipython_config.py 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | .dmypy.json 111 | dmypy.json 112 | 113 | .idea/ 114 | static/css 115 | static/img/icon.svg -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.4.3-2 2 | - update dependency: cherrypy 18.8.0>18.6.1 (thanks @alcroito) 3 | 4 | 0.4.3-1 5 | - fix cross-platform building (buildx) 6 | - update dependency: cryptography 3.3.2-><3.5 7 | 8 | 0.4.3 9 | - make docker image leaner (reduced to 10% of its original size; thanks @BrutuZ 10 | - update dependencies: 11 | cherrypy 18.6.0->18.6.1 12 | requests 2.25.1->2.26.0 13 | libsass 0.20.1->0.21.0 14 | jinja2 2.11.3->3.0.3 15 | PyGithub 1.54.1->1.55 16 | certifi 2020.12.5->2021.10.8 17 | cffi 1.14.5->1.15.0 18 | 19 | 0.4.2 20 | - check connection to PMS in server detection 21 | 22 | 0.4.1 23 | - handle plugin errors more explicitly 24 | - try to avoid TooManyRedirects 25 | - update dependencies; pin cryptography on non-rust version; update Dockerfile 26 | 27 | 0.4.0-2 28 | - rediscover PMS when SSL error occurred 29 | - update Dockerfile 30 | 31 | 0.4.0 32 | - Require Python 3.7 33 | 34 | 0.3.3-1 35 | - downgrade dependency pyGitHub to 1.54 as its previous requirement couldn't be satisfied in 0.3.3 for Python 3.5 36 | - last Python 3.5 release 37 | 38 | 0.3.3 39 | - update dependencies 40 | - use python 3.5 explicitly 41 | - support -A allowing a global Plex token to be supplied via command line or environment variable name, fully supporting "no-login"-mode without the need to authenticate. 42 | - possibly fix #50 43 | 44 | 0.3.2 45 | - update dependencies 46 | - support relay connections 47 | 48 | 0.3.1-5 49 | - fix restart icon/glyph to better represent what it does (#32) 50 | - fix inexistant httpsRequired (#33) 51 | 52 | 0.3.1-3 53 | - fix proxy base handling (#28) 54 | 55 | 0.3.1-2 56 | - add title (thanks @Cyb3rGh05t) 57 | 58 | 0.3.1-1 59 | - fix running mode detection 60 | 61 | 0.3.1 62 | - finalize plugin settings handler 63 | - add plugin restart functionality 64 | 65 | 0.3.0 66 | - don't error on inexistant self.connections 67 | - update bootstrap to 4.4.1 68 | - add iconic 1.1.0 69 | - upgrade cherrypy to 18.5.0 70 | - update pyGitHub to 1.45 71 | - add first plugin settings handler 72 | 73 | 0.2.0 74 | - support proxying url download from title2; fixes #19 75 | - remove specific Dockerfile.armhf; update dockerignore; upgrade cherrypy to 18.4.0, libsass to 0.19.4, pyGitHub to 1.44.1 76 | 77 | 0.1.9 78 | - only allow owned servers by default; can be disabled 79 | 80 | 0.1.8-2 81 | - properly set device name on plex auth (esp. docker) 82 | 83 | 0.1.8-1 84 | - add distribution namespace to cookies (standalone/git/docker) 85 | - update version parsing 86 | 87 | 0.1.8 88 | - update dependencies 89 | - fix #17; alert the user about wrong credentials instead of erroring out 90 | - fix #8; support redirects (title2 in MediaContainer) - fixes SZ's get my logs 91 | 92 | 0.1.7 93 | - update dependencies 94 | 95 | 0.1.6 96 | - remove future-fstrings requirement 97 | - add Dockerfile for armhf (#10, thanks @Raph521) 98 | - change default timeout to 15 (was 5) 99 | - change default plextv timeout to 25 (was 15) 100 | - try creating the data/sessions folder if it isn't already there (#10) 101 | 102 | 0.1.5 103 | - resolve display issues introduced with 0.1.4 104 | - fix usage with single Devices or single Connection entries 105 | - update requests to 2.20.0 (security issue) 106 | - mask server names and domains/addresses in logs 107 | - more logging 108 | 109 | 0.1.4 110 | - plugins: show true (visible) item count 111 | 112 | 0.1.3 113 | - improve (video) plugin handling 114 | - default shadow-assets to off on win32 115 | - docker support approved on win32 116 | 117 | 0.1.2 118 | - better fix for redirect to /token 119 | - add option to specify plugin language (default: en) 120 | - support and fixes for win32 121 | - fix sass compilation 122 | - correctly serve static files 123 | - add CTRL-C handler 124 | - disable autoreloader 125 | 126 | - support WebTools (including launching WebTools) 127 | - ship empty data/sessions folder 128 | 129 | 0.1.1 130 | - fix redirect to /token 131 | 132 | 0.1.0 133 | - initial release -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Python runtime as a parent image 2 | FROM python:3.8-alpine 3 | 4 | # Set the working directory to /app 5 | WORKDIR /app 6 | 7 | # Add Kitana files not filtered by .dockerignore to the image 8 | COPY . . 9 | 10 | # still meh. do we want to drop armv7? 11 | ARG CRYPTOGRAPHY_DONT_BUILD_RUST=1 12 | 13 | # We chain the following steps to create a single layer, reducing image size 14 | # - Install packages needed to run and compile 15 | # - Install and compile required Python packgages 16 | # - Remove packages needed only for compiling 17 | RUN apk add -U --repository=http://dl-cdn.alpinelinux.org/alpine/v3.13/main \ 18 | gcc g++ musl-dev openssl-dev libffi-dev cargo build-base \ 19 | libstdc++ \ 20 | && pip install --trusted-host pypi.python.org -r requirements.txt \ 21 | && apk del -r --purge \ 22 | gcc g++ musl-dev openssl-dev libffi-dev cargo build-base \ 23 | && rm /var/cache/apk/* 24 | 25 | # Expose the port 26 | EXPOSE 31337 27 | 28 | # Store session tokens here 29 | VOLUME /app/data 30 | 31 | # Run kitana.py when the container launches 32 | ENTRYPOINT ["python", "kitana.py"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Icon based on http://www.malagatravelguide.net/ 2 | 3 | MIT License 4 | 5 | Copyright (c) 2018 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | -------------------------------------------------------------------------------- /data/sessions/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/data/sessions/.keep -------------------------------------------------------------------------------- /deployment/nginx/sample.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | server { 4 | listen 80; 5 | server_name testserver.com; 6 | proxy_redirect off; 7 | 8 | location ^~ /kitana/pms_asset { 9 | expires 1M; 10 | access_log off; 11 | add_header Cache-Control "public"; 12 | proxy_pass http://127.0.0.1:31337; 13 | include proxy.conf; 14 | } 15 | 16 | location ^~ /kitana/static/ { 17 | # this is the poor man's approach; ./kitana has to exist in *root*; 18 | # if root is /peter, /peter/kitana/static has to exist for this simple config to wok. 19 | root /ROOT_WITHOUT_SLASH_LOWERCASE_KITANA/; 20 | 21 | # versioned static rewrite 22 | rewrite "^(\/kitana\/static\/(css|js|img)\/)(\w+)\.\w{7}\.(\w{2,4})$" $1$3.$4 last; 23 | } 24 | 25 | location /kitana/ { 26 | proxy_pass http://127.0.0.1:31337; 27 | include proxy.conf; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /deployment/supervisor/sample.conf: -------------------------------------------------------------------------------- 1 | [program:kitana] 2 | command=/folder/kitana/kitana.py -B 127.0.0.1:31337 -p /kitana -P ; the program (relative uses PATH, can take args) 3 | numprocs=1 ; number of processes copies to start (def 1) 4 | directory=/folder/kitana ; directory to cwd to before exec (def no cwd) 5 | autostart=true ; start at supervisord start (default: true) 6 | autorestart=true ; retstart at unexpected quit (default: true) 7 | startsecs=10 ; number of secs prog must stay running (def. 1) 8 | startretries=12 ; max # of serial start failures (default 3) 9 | stopsignal=QUIT ; signal used to kill process (default TERM) 10 | user=www ; setuid to this UNIX account to run the program 11 | group=www 12 | -------------------------------------------------------------------------------- /plugins/SassCompilerPlugin.py: -------------------------------------------------------------------------------- 1 | import cherrypy 2 | from cherrypy.process import wspbus, plugins 3 | from pathlib import Path 4 | import sass 5 | import os 6 | import shutil 7 | 8 | __author__ = "Randy Yang (https://www.gitlab.com/randyyaj)" 9 | __license__ = "MIT" 10 | __version__ = "0.1.0" 11 | 12 | 13 | class SassCompilerPlugin(plugins.SimplePlugin): 14 | """ 15 | Custom Sass Compiler Plugin for cherrypy. Uses libsass to compile. 16 | 17 | Searches the whole project for any sass directories and creates a css 18 | directory in the same location with the compiled sass to css files. 19 | 20 | Register this plugin after the cherrypy.engine.start() block in your 21 | webservice or place it in your main function in your app. 22 | """ 23 | def __init__(self, bus): 24 | plugins.SimplePlugin.__init__(self, bus) 25 | 26 | def start(self): 27 | self.bus.log('Starting SassCompilerPlugin') 28 | self.bus.subscribe("compile_sass", self.compile_sass) 29 | 30 | def stop(self): 31 | self.bus.log('Stopping SassCompilerPlugin') 32 | self.bus.unsubscribe("compile_sass", self.compile_sass) 33 | 34 | def compile_sass(self): 35 | cherrypy.log("Starting Sass Compile") 36 | for (dirpath, dirnames, filenames) in os.walk(os.getcwd()): 37 | if dirpath.endswith('{}sass'.format(os.sep)): 38 | css_directory = Path(str(Path(dirpath).parent) + "{}css".format(os.sep)) 39 | 40 | if not css_directory.exists(): 41 | Path.mkdir(css_directory) 42 | else: 43 | shutil.rmtree(str(css_directory)) 44 | Path.mkdir(css_directory) 45 | 46 | cherrypy.log('{}: Compiling {} to {}'.format(self.__class__.__name__, dirpath, str(css_directory))) 47 | sass.compile(dirname=(dirpath, str(css_directory)), output_style='compressed') 48 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography<3.5 2 | cherrypy==18.8.0 3 | requests==2.26.0 4 | xmltodict==0.12.0 5 | libsass==0.21.0 6 | jinja2==3.0.3 7 | PyGithub==1.55 8 | furl==2.1.3 9 | ndg-httpsclient==0.5.1 10 | certifi==2022.12.7 11 | cffi==1.15.0 -------------------------------------------------------------------------------- /requirements_win32.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | pywin32>=223 3 | -------------------------------------------------------------------------------- /static/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/fonts/open-iconic.eot -------------------------------------------------------------------------------- /static/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/fonts/open-iconic.otf -------------------------------------------------------------------------------- /static/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /static/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/fonts/open-iconic.woff -------------------------------------------------------------------------------- /static/img/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/android-icon-144x144.png -------------------------------------------------------------------------------- /static/img/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/android-icon-192x192.png -------------------------------------------------------------------------------- /static/img/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/android-icon-36x36.png -------------------------------------------------------------------------------- /static/img/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/android-icon-48x48.png -------------------------------------------------------------------------------- /static/img/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/android-icon-72x72.png -------------------------------------------------------------------------------- /static/img/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/android-icon-96x96.png -------------------------------------------------------------------------------- /static/img/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-114x114.png -------------------------------------------------------------------------------- /static/img/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-120x120.png -------------------------------------------------------------------------------- /static/img/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-144x144.png -------------------------------------------------------------------------------- /static/img/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-152x152.png -------------------------------------------------------------------------------- /static/img/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-180x180.png -------------------------------------------------------------------------------- /static/img/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-57x57.png -------------------------------------------------------------------------------- /static/img/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-60x60.png -------------------------------------------------------------------------------- /static/img/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-72x72.png -------------------------------------------------------------------------------- /static/img/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-76x76.png -------------------------------------------------------------------------------- /static/img/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon-precomposed.png -------------------------------------------------------------------------------- /static/img/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/apple-icon.png -------------------------------------------------------------------------------- /static/img/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/background.jpg -------------------------------------------------------------------------------- /static/img/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /static/img/fan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/fan.png -------------------------------------------------------------------------------- /static/img/fan.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/fan.psd -------------------------------------------------------------------------------- /static/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/favicon-16x16.png -------------------------------------------------------------------------------- /static/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/favicon-32x32.png -------------------------------------------------------------------------------- /static/img/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/favicon-96x96.png -------------------------------------------------------------------------------- /static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/favicon.ico -------------------------------------------------------------------------------- /static/img/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /static/img/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /static/img/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/ms-icon-144x144.png -------------------------------------------------------------------------------- /static/img/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/ms-icon-150x150.png -------------------------------------------------------------------------------- /static/img/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/ms-icon-310x310.png -------------------------------------------------------------------------------- /static/img/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pannal/Kitana/287a8b778385d1ced921e0250665883891022951/static/img/ms-icon-70x70.png -------------------------------------------------------------------------------- /static/js/auth.js: -------------------------------------------------------------------------------- 1 | 2 | // borrowed from: https://github.com/Tautulli/Tautulli/blob/master/data/interfaces/default/js/script.js 3 | 4 | function PopupCenter(url, title, w, h) { 5 | // Fixes dual-screen position Most browsers Firefox 6 | var dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : window.screenX; 7 | var dualScreenTop = window.screenTop != undefined ? window.screenTop : window.screenY; 8 | 9 | var width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width; 10 | var height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height; 11 | 12 | var left = ((width / 2) - (w / 2)) + dualScreenLeft; 13 | var top = ((height / 2) - (h / 2)) + dualScreenTop; 14 | var newWindow = window.open(url, title, 'scrollbars=yes, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left); 15 | 16 | // Puts focus on the newWindow 17 | if (window.focus) { 18 | newWindow.focus(); 19 | } 20 | 21 | return newWindow; 22 | } 23 | 24 | function encodeData(data) { 25 | return Object.keys(data).map(function(key) { 26 | return [key, data[key]].map(encodeURIComponent).join("="); 27 | }).join("&"); 28 | } 29 | 30 | var plex_oauth_window = null; 31 | const plex_oauth_loader = '' + 64 | '
' + 65 | '
' + 66 | '' + 67 | '
' + 68 | 'Redirecting to the Plex login page...' + 69 | '
' + 70 | '
'; 71 | 72 | function closePlexOAuthWindow() { 73 | if (plex_oauth_window) { 74 | plex_oauth_window.close(); 75 | } 76 | } 77 | 78 | getPlexOAuthPin = function () { 79 | var deferred = $.Deferred(); 80 | //console.log(x_plex_headers); 81 | 82 | $.ajax({ 83 | url: 'https://plex.tv/api/v2/pins?strong=true', 84 | type: 'POST', 85 | headers: x_plex_headers, 86 | success: function(data) { 87 | var oauth_params = { 88 | 'clientID': x_plex_headers['X-Plex-Client-Identifier'], 89 | 'context[device][product]': x_plex_headers['X-Plex-Product'], 90 | 'context[device][version]': x_plex_headers['X-Plex-Version'], 91 | 'context[device][platform]': x_plex_headers['X-Plex-Platform'], 92 | 'context[device][platformVersion]': x_plex_headers['X-Plex-Platform-Version'], 93 | 'context[device][device]': x_plex_headers['X-Plex-Device'], 94 | 'context[device][deviceName]': x_plex_headers['X-Plex-Device-Name'], 95 | 'context[device][model]': x_plex_headers['X-Plex-Model'], 96 | 'context[device][screenResolution]': x_plex_headers['X-Plex-Device-Screen-Resolution'], 97 | 'context[device][layout]': 'desktop', 98 | 'code': data.code 99 | }; 100 | console.log(oauth_params); 101 | plex_oauth_window.location = 'https://app.plex.tv/auth/#!?' + encodeData(oauth_params); 102 | console.log(oauth_params); 103 | deferred.resolve({pin: data.id, code: data.code}); 104 | }, 105 | error: function() { 106 | closePlexOAuthWindow(); 107 | deferred.reject(); 108 | } 109 | }); 110 | return deferred; 111 | }; 112 | 113 | var polling = null; 114 | 115 | function PlexOAuth(success, error, pre) { 116 | if (typeof pre === "function") { 117 | pre() 118 | } 119 | clearTimeout(polling); 120 | closePlexOAuthWindow(); 121 | plex_oauth_window = PopupCenter('', 'Plex-OAuth', 600, 700); 122 | $(plex_oauth_window.document.body).html(plex_oauth_loader); 123 | 124 | getPlexOAuthPin().then(function (data) { 125 | const pin = data.pin; 126 | const code = data.code; 127 | var keep_polling = true; 128 | 129 | (function poll() { 130 | polling = setTimeout(function () { 131 | $.ajax({ 132 | url: 'https://plex.tv/api/v2/pins/' + pin, 133 | type: 'GET', 134 | headers: x_plex_headers, 135 | success: function (data) { 136 | if (data.authToken){ 137 | keep_polling = false; 138 | closePlexOAuthWindow(); 139 | if (typeof success === "function") { 140 | success(data.authToken) 141 | } 142 | } 143 | }, 144 | error: function () { 145 | keep_polling = false; 146 | closePlexOAuthWindow(); 147 | if (typeof error === "function") { 148 | error() 149 | } 150 | }, 151 | complete: function () { 152 | if (keep_polling){ 153 | poll(); 154 | } else { 155 | clearTimeout(polling); 156 | } 157 | }, 158 | timeout: 1000 159 | }); 160 | }, 1000); 161 | })(); 162 | }, function () { 163 | closePlexOAuthWindow(); 164 | if (typeof error === "function") { 165 | error() 166 | } 167 | }); 168 | } -------------------------------------------------------------------------------- /static/sass/base.sass: -------------------------------------------------------------------------------- 1 | @import bootstrap/functions 2 | @import darkly/variables 3 | 4 | body 5 | background-color: #0e0e0e 6 | color: white 7 | font-family: 'Open Sans', sans-serif 8 | margin: 0 9 | 10 | $vertBreaker: 992px 11 | $belowBig: 1920px 12 | $maxWidth: 1140px 13 | 14 | #content 15 | left: 0 16 | right: 0 17 | z-index: 0 18 | margin-top: 16px 19 | 20 | &.index, &.settings 21 | .plugin_content 22 | display: flex 23 | flex-direction: column-reverse 24 | 25 | //@media (max-width: $vertBreaker) 26 | padding: 16px 8px 27 | 28 | $bgOpacity: opacity(30%) 29 | #content:before 30 | content: "" 31 | position: fixed 32 | left: 0 33 | right: 0 34 | top: 0 35 | z-index: -1 36 | display: block 37 | width: 100% 38 | height: 100% 39 | -webkit-filter: $bgOpacity 40 | -moz-filter: $bgOpacity 41 | -o-filter: $bgOpacity 42 | -ms-filter: $bgOpacity 43 | filter: $bgOpacity 44 | background-repeat: no-repeat 45 | background-size: cover 46 | 47 | @media (max-width: $belowBig) 48 | background-repeat: repeat-y 49 | background-size: 100% auto 50 | 51 | 52 | #content 53 | max-width: $maxWidth 54 | a, a:link, a:visited 55 | color: $link-color 56 | 57 | a:hover, a:active 58 | color: darken($link-color, 25%) 59 | text-decoration: none 60 | 61 | a:active 62 | color: darken($link-color, 50%) 63 | 64 | div 65 | box-sizing: border-box 66 | height: auto 67 | 68 | ul 69 | list-style: none 70 | margin: 0 71 | padding: 0 72 | 73 | li 74 | width: 100% 75 | background-color: $dark 76 | //border: 1px solid #4e4e4e 77 | margin-bottom: 1% 78 | position: relative 79 | box-sizing: border-box 80 | border-radius: 4px 81 | 82 | @media (max-width: $vertBreaker) 83 | margin-bottom: 2% 84 | 85 | &:hover 86 | background-color: $gray-900 87 | border-bottom-right-radius: 0 88 | border-top-right-radius: 0 89 | 90 | > a 91 | .thumb img 92 | opacity: 1 93 | 94 | > a 95 | display: flex 96 | align-content: stretch 97 | flex-wrap: nowrap 98 | justify-content: flex-start 99 | align-items: stretch 100 | flex-direction: row 101 | 102 | &:hover 103 | color: $link-color 104 | 105 | @media (max-width: $vertBreaker) 106 | //flex-direction: column 107 | flex-wrap: wrap 108 | 109 | div 110 | flex: 1 1 45% 111 | padding: 1% 112 | -ms-word-break: break-word 113 | word-break: break-word 114 | 115 | @media (max-width: $vertBreaker) 116 | flex: 0 0 100% 117 | display: block 118 | padding: 2% 119 | 120 | .thumb 121 | flex: 0 0 10% 122 | min-width: 10% 123 | padding: 0 124 | img 125 | min-width: 10% 126 | max-width: 100% 127 | height: auto 128 | opacity: 0.8 129 | @media (max-width: $vertBreaker) 130 | display: none 131 | 132 | .title 133 | font-size: 1.2em 134 | flex-shrink: 0 135 | 136 | .summary 137 | color: $gray-400 138 | font-size: 1em 139 | flex-shrink: 1 140 | 141 | @media (max-width: $vertBreaker) 142 | padding-top: 0 143 | 144 | .card 145 | max-width: 50% 146 | margin: 0 auto 147 | @media (max-width: $vertBreaker) 148 | max-width: 100% 149 | 150 | 151 | #servers li, #plugins .plugin 152 | cursor: pointer 153 | 154 | &.no-click 155 | background-color: $dark 156 | cursor: default 157 | &:hover 158 | background-color: $dark 159 | 160 | #plugins .plugin 161 | img 162 | max-width: 80px 163 | height: auto 164 | -------------------------------------------------------------------------------- /static/sass/bootstrap/_alert.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Base styles 3 | // 4 | 5 | .alert { 6 | position: relative; 7 | padding: $alert-padding-y $alert-padding-x; 8 | margin-bottom: $alert-margin-bottom; 9 | border: $alert-border-width solid transparent; 10 | @include border-radius($alert-border-radius); 11 | } 12 | 13 | // Headings for larger alerts 14 | .alert-heading { 15 | // Specified to prevent conflicts of changing $headings-color 16 | color: inherit; 17 | } 18 | 19 | // Provide class for links that match alerts 20 | .alert-link { 21 | font-weight: $alert-link-font-weight; 22 | } 23 | 24 | 25 | // Dismissible alerts 26 | // 27 | // Expand the right padding and account for the close button's positioning. 28 | 29 | .alert-dismissible { 30 | padding-right: $close-font-size + $alert-padding-x * 2; 31 | 32 | // Adjust close link position 33 | .close { 34 | position: absolute; 35 | top: 0; 36 | right: 0; 37 | padding: $alert-padding-y $alert-padding-x; 38 | color: inherit; 39 | } 40 | } 41 | 42 | 43 | // Alternate styles 44 | // 45 | // Generate contextual modifier classes for colorizing the alert. 46 | 47 | @each $color, $value in $theme-colors { 48 | .alert-#{$color} { 49 | @include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /static/sass/bootstrap/_badge.scss: -------------------------------------------------------------------------------- 1 | // Base class 2 | // 3 | // Requires one of the contextual, color modifier classes for `color` and 4 | // `background-color`. 5 | 6 | .badge { 7 | display: inline-block; 8 | padding: $badge-padding-y $badge-padding-x; 9 | @include font-size($badge-font-size); 10 | font-weight: $badge-font-weight; 11 | line-height: 1; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | @include border-radius($badge-border-radius); 16 | @include transition($badge-transition); 17 | 18 | @at-root a#{&} { 19 | @include hover-focus() { 20 | text-decoration: none; 21 | } 22 | } 23 | 24 | // Empty badges collapse automatically 25 | &:empty { 26 | display: none; 27 | } 28 | } 29 | 30 | // Quick fix for badges in buttons 31 | .btn .badge { 32 | position: relative; 33 | top: -1px; 34 | } 35 | 36 | // Pill badges 37 | // 38 | // Make them extra rounded with a modifier to replace v3's badges. 39 | 40 | .badge-pill { 41 | padding-right: $badge-pill-padding-x; 42 | padding-left: $badge-pill-padding-x; 43 | @include border-radius($badge-pill-border-radius); 44 | } 45 | 46 | // Colors 47 | // 48 | // Contextual variations (linked badges get darker on :hover). 49 | 50 | @each $color, $value in $theme-colors { 51 | .badge-#{$color} { 52 | @include badge-variant($value); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /static/sass/bootstrap/_breadcrumb.scss: -------------------------------------------------------------------------------- 1 | .breadcrumb { 2 | display: flex; 3 | flex-wrap: wrap; 4 | padding: $breadcrumb-padding-y $breadcrumb-padding-x; 5 | margin-bottom: $breadcrumb-margin-bottom; 6 | @include font-size($breadcrumb-font-size); 7 | list-style: none; 8 | background-color: $breadcrumb-bg; 9 | @include border-radius($breadcrumb-border-radius); 10 | } 11 | 12 | .breadcrumb-item { 13 | // The separator between breadcrumbs (by default, a forward-slash: "/") 14 | + .breadcrumb-item { 15 | padding-left: $breadcrumb-item-padding; 16 | 17 | &::before { 18 | display: inline-block; // Suppress underlining of the separator in modern browsers 19 | padding-right: $breadcrumb-item-padding; 20 | color: $breadcrumb-divider-color; 21 | content: escape-svg($breadcrumb-divider); 22 | } 23 | } 24 | 25 | // IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built 26 | // without `