Compare commits

...

39 Commits

Author SHA1 Message Date
JonnyWong16
356f64cac0 v2.5.2 2020-07-01 19:49:44 -07:00
JonnyWong16
f77f289125 Move GitHub sponsor first 2020-07-01 15:53:08 -07:00
JonnyWong16
280257477a Revert "Change shebang to python3"
This reverts commit cd8a899521.
2020-07-01 14:59:18 -07:00
JonnyWong16
660141cb16 Try various python versions in startup script 2020-07-01 14:43:35 -07:00
JonnyWong16
cd8a899521 Change shebang to python3 2020-07-01 14:43:04 -07:00
JonnyWong16
cb577c51b8 v2.5.2-beta 2020-06-27 15:04:06 -07:00
JonnyWong16
1c395ab10c Patch SameSite support into cookies
* Python 2.7 is missing SameSite cookie attribute
2020-06-27 15:01:16 -07:00
JonnyWong16
07d7170e49 v2.5.1-beta 2020-06-26 18:37:07 -07:00
JonnyWong16
88e23627fd Fix typo 2020-06-25 19:12:10 -07:00
JonnyWong16
48f846da40 Expire the previous JWT on update if HTTP root is set
* Required for Tautulli/Tautulli-Issues#255
2020-06-24 14:04:07 -07:00
JonnyWong16
ff887d9948 Remove unnecessary x_plex_headers from 805d45b 2020-06-23 20:07:45 -07:00
JonnyWong16
617b0d6fd9 Set JWT cookie path to HTTP root
* Fixes Tautulli/Tautulli-Issues#255
2020-06-23 20:00:50 -07:00
JonnyWong16
805d45bd33 Don't overwrite PMS_UUID when fetching a new token 2020-06-23 19:47:01 -07:00
JonnyWong16
fef428202f Start Tautulli using different user in Docker container 2020-06-21 12:38:27 -07:00
JonnyWong16
40fd82febd Only change Docker permissions if PUID/PGID exists 2020-06-21 10:38:26 -07:00
JonnyWong16
45f0001da5 Fix Docker permissions if pre-existing PUID/PGID 2020-06-21 09:58:29 -07:00
JonnyWong16
c7a3e1e3bf Change Docker default PUID and PGID 2020-06-21 00:27:48 -07:00
JonnyWong16
9dd8cc9e49 Fix Docker container not using PUID and PGID environment variables 2020-06-20 23:51:29 -07:00
JonnyWong16
d252d4cd2d Update Publish Docker workflow 2020-06-20 23:51:21 -07:00
JonnyWong16
bc1328040c Update Publish Release workflow 2020-06-20 23:51:20 -07:00
JonnyWong16
82919d3c1d Fix indent in MacOS postinstall script 2020-06-20 23:51:19 -07:00
JonnyWong16
7c801c2f5e Add flag for offical mobile app 2020-06-20 16:16:35 -07:00
JonnyWong16
9a932aea12 Fix text wrapping on user player stats 2020-06-20 15:03:51 -07:00
JonnyWong16
5696e75abe Add LG platform icon 2020-06-20 15:03:32 -07:00
JonnyWong16
efb3f748c2 Improve app registration instructions 2020-06-20 11:36:54 -07:00
JonnyWong16
450b3865a8 Validate OneSignal Player ID when registering device 2020-06-20 10:59:55 -07:00
JonnyWong16
970667adca Only allow temporary device token access to register app 2020-06-20 10:58:49 -07:00
JonnyWong16
89307dad01 Show missing pyobjc module message on MacOS menu bar setting 2020-06-14 15:22:58 -07:00
JonnyWong16
451feda86b Rename system tray to menu bar on MacOS 2020-06-14 14:59:37 -07:00
JonnyWong16
4d241fac48 Try import rumps 2020-06-14 14:52:55 -07:00
JonnyWong16
4390f5cbc8 Check for Foundation module for MacOS system track icon
* Fixes Tautulli/Tautulli-Issues#249
2020-06-13 14:36:47 -07:00
JonnyWong16
7f9d46eac3 Fix Cloudinary upload for Python 2 2020-06-03 20:41:57 -07:00
JonnyWong16
d0f28883aa Remove ability to login using Plex username / password.
* Only login using Plex OAuth
2020-06-02 17:28:24 -07:00
JonnyWong16
48203e64a9 Improve test browser notifications 2020-06-01 22:55:59 -07:00
JonnyWong16
42b17ca495 Change default recently added notification delay to 300s 2020-06-01 16:44:01 -07:00
JonnyWong16
d8080fe506 Fix creating self-signed certificates on Python 3
* Fixes Tautulli/Tautulli-Issues#248
2020-06-01 16:40:25 -07:00
JonnyWong16
be910e24f7 Update release workflow
* Update joncloud/makensis-action@v1.2
2020-05-31 15:35:55 -07:00
JonnyWong16
ce6d70f6fd Fix CHANGELOG.md 2020-05-31 15:29:40 -07:00
JonnyWong16
827e05e4d7 Update release workflow
* Update joncloud/makensis-action@v2
2020-05-31 15:29:23 -07:00
28 changed files with 252 additions and 97 deletions

View File

@@ -7,3 +7,4 @@ package
pylintrc
*.md
!CHANGELOG*.md
start.bat

View File

@@ -7,6 +7,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Prepare
id: prepare
run: |
@@ -29,16 +32,24 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
uses: crazy-max/ghaction-docker-buildx@v3
with:
version: latest
buildx-version: latest
- name: Checkout Code
uses: actions/checkout@v2.1.0
- name: Cache Docker Layers
id: cache
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Docker Buildx (no push)
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--cache-to "type=local,dest=/tmp/.buildx-cache" \
--platform ${{ steps.prepare.outputs.docker_platforms }} \
--output "type=image,push=false" \
--build-arg "TAG=${{ steps.prepare.outputs.tag }}" \
@@ -59,6 +70,7 @@ jobs:
if: success()
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--platform ${{ steps.prepare.outputs.docker_platforms }} \
--output "type=image,push=true" \
--build-arg "TAG=${{ steps.prepare.outputs.tag }}" \
@@ -79,5 +91,5 @@ jobs:
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
job: ${{ github.workflow }}
title: ${{ github.workflow }}
nofail: true

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2.1.0
uses: actions/checkout@v2
- name: Set Release Version
id: get_version
@@ -28,13 +28,13 @@ jobs:
echo $GITHUB_SHA > version.txt
- name: Set Up Python
uses: actions/setup-python@v1.2.0
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Cache Dependencies
id: cache_dependencies
uses: actions/cache@v1
uses: actions/cache@v2
with:
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ hashFiles('package/requirements-windows.txt') }}
@@ -50,14 +50,15 @@ jobs:
pyinstaller -y ./package/Tautulli-windows.spec
- name: Create Installer
uses: joncloud/makensis-action@v1
uses: joncloud/makensis-action@v1.2
with:
script-file: ./package/Tautulli.nsi
arguments: /DVERSION=${{ steps.get_version.outputs.VERSION_NSIS }} /DINSTALLER_NAME=..\Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe
includeMorePlugins: package/nsis-plugins
include-more-plugins: true
include-custom-plugins-path: package/nsis-plugins
- name: Upload Installer
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v2
with:
name: Tautulli-windows-installer
path: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe
@@ -68,14 +69,14 @@ jobs:
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
job: Build Windows Installer
title: Build Windows Installer
nofail: true
build-macos:
runs-on: macos-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2.1.0
uses: actions/checkout@v2
- name: Set Release Version
id: get_version
@@ -93,13 +94,13 @@ jobs:
echo $GITHUB_SHA > version.txt
- name: Set Up Python
uses: actions/setup-python@v1.2.0
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Cache Dependencies
id: cache_dependencies
uses: actions/cache@v1
uses: actions/cache@v2
with:
path: ~/Library/Caches/pip
key: ${{ runner.os }}-pip-${{ hashFiles('package/requirements-macos.txt') }}
@@ -119,7 +120,7 @@ jobs:
sudo pkgbuild --install-location /Applications --version ${{ steps.get_version.outputs.VERSION }} --component ./dist/Tautulli.app --scripts ./package/macos-scripts Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg
- name: Upload Installer
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v2
with:
name: Tautulli-macos-installer
path: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg
@@ -130,7 +131,7 @@ jobs:
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
job: Build MacOS Installer
title: Build MacOS Installer
nofail: true
release:
@@ -142,7 +143,7 @@ jobs:
uses: technote-space/workflow-conclusion-action@v1
- name: Checkout Code
uses: actions/checkout@v2.1.0
uses: actions/checkout@v2
- name: Set Release Version
id: get_version
@@ -151,13 +152,13 @@ jobs:
- name: Download Windows Installer
if: env.WORKFLOW_CONCLUSION == 'success'
uses: actions/download-artifact@v1
uses: actions/download-artifact@v2
with:
name: Tautulli-windows-installer
- name: Download MacOS Installer
if: env.WORKFLOW_CONCLUSION == 'success'
uses: actions/download-artifact@v1
uses: actions/download-artifact@v2
with:
name: Tautulli-macos-installer
@@ -187,7 +188,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: Tautulli-windows-installer/Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe
asset_path: ./Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe
asset_name: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe
asset_content_type: application/vnd.microsoft.portable-executable
@@ -198,6 +199,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: Tautulli-macos-installer/Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg
asset_path: ./Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg
asset_name: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg
asset_content_type: application/vnd.apple.installer+xml

View File

@@ -1,16 +1,30 @@
# Changelog
# v2.5.0-beta (2020-05-31)
## v2.5.2 (2020-07-01)
* Announcements:
* Tautulli now supports Python 3!
* Python 2 is still supported for the time being, but it is recommended to upgrade to Python 3.
* UI:
* Notifications:
* Fix: Error uploading images to Cloudinary on Python 2.
* Fix: Testing browser notifications alert not disappearing.
* Change: Default recently added notification delay set to 300 seconds.
* UI:
* Fix: MacOS menu bar icon causing Tautulli to fail to start.
* Fix: Unable to login to Tautulli on Python 2.
* New: Windows and MacOS setting to enable Tautulli to start automatically when you login.
* New: Added system tray icon for MacOS.
* New: Added menu bar icon for MacOS.
* New: Ability to import a Tautulli database in the settings.
* New: Added Tautulli news area on the settings page.
* New: Added platform icon for LG devices.
* Remove: Ability to login to Tautulli using a Plex username and password has been removed. Login using a Plex.tv account is only supported via OAuth.
* Mobile App:
* Fix: Improved API security and validation when registering the Android app.
* Docker:
* Fix: Docker container not respecting the PUID and PGID environment variables.
* Other:
* Fix: Error creating self-signed certificates on Python 3.
* Fix: Tautulli login session cookie not set on the HTTP root path.
* New: Windows and MacOS app installers to install Tautulli without needing Python installed.

View File

@@ -16,7 +16,7 @@ RUN \
COPY . /app
CMD [ "python", "Tautulli.py", "--datadir", "/config" ]
ENTRYPOINT [ "./start.sh" ]
VOLUME /config
EXPOSE 8181

View File

@@ -257,15 +257,13 @@ def main():
plexpy.HTTP_ROOT)
if common.PLATFORM == 'Darwin' and plexpy.CONFIG.SYS_TRAY_ICON:
try:
import AppKit
except ImportError:
if not macos.HAS_PYOBJC:
logger.warn("The pyobjc module is missing. Install this "
"module to enable the system tray icon.")
"module to enable the MacOS menu bar icon.")
plexpy.CONFIG.SYS_TRAY_ICON = False
if plexpy.CONFIG.SYS_TRAY_ICON:
# MacOS system tray icon must be run on the main thread and is blocking
# MacOS menu bar icon must be run on the main thread and is blocking
# Start the rest of Tautulli on a new thread
threading.Thread(target=wait).start()
plexpy.MAC_SYS_TRAY_ICON = macos.MacOSSystemTray()

View File

@@ -230,20 +230,12 @@ ${next.modalIncludes()}
</div>
</div>
<ul id="donation_type" class="nav nav-pills" role="tablist" style="display: flex; justify-content: center; margin: 10px 0;">
<li class="active"><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
<li><a href="#github-donation" role="tab" data-toggle="tab">GitHub</a></li>
<li class="active"><a href="#github-donation" role="tab" data-toggle="tab">GitHub</a></li>
<li><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
<li><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="patreon-donation" style="text-align: center">
<p>
Click the button below to continue to Patreon.
</p>
<a href="${anon_url('https://www.patreon.com/join/tautulli')}" target="_blank">
<img src="images/become_a_patron_button.png" alt="Become a Patron" height="40">
</a>
</div>
<div role="tabpanel" class="tab-pane" id="github-donation" style="text-align: center">
<div role="tabpanel" class="tab-pane active" id="github-donation" style="text-align: center">
<p>
Click the button below to continue to GitHub.
</p>
@@ -251,6 +243,14 @@ ${next.modalIncludes()}
<i class="fa fa-heart fa-sm" style="color: #ea4aaa;"></i>&nbsp; Sponsor
</a>
</div>
<div role="tabpanel" class="tab-pane" id="patreon-donation" style="text-align: center">
<p>
Click the button below to continue to Patreon.
</p>
<a href="${anon_url('https://www.patreon.com/join/tautulli')}" target="_blank">
<img src="images/become_a_patron_button.png" alt="Become a Patron" height="40">
</a>
</div>
<div role="tabpanel" class="tab-pane" id="paypal-donation" style="text-align: center">
<p>
Click the button below to continue to PayPal.
@@ -296,9 +296,7 @@ ${next.modalIncludes()}
<script src="${http_root}js/ipaddr.min.js"></script>
<script src="${http_root}js/script.js${cache_param}"></script>
<script src="${http_root}js/jquery.tripleclick.min.js"></script>
% if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS:
<script src="${http_root}js/ajaxNotifications.js"></script>
% endif
<script>
% if _session['user_group'] == 'admin':
$('body').on('click', '#updateDismiss', function() {
@@ -423,6 +421,10 @@ ${next.modalIncludes()}
$(document).on('hidden.bs.modal', '.modal', function () {
$('.modal:visible').length && $(document.body).addClass('modal-open');
});
% if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS:
check_notifications();
% endif
});
% if _session['user_group'] != 'admin':

View File

@@ -2161,7 +2161,7 @@ div.advanced-setting {
li.advanced-setting {
border-left: 1px solid #cc7b19;
}
.docker-setting {
.setting-message {
color: #cc7b19;
margin-left: 10px;
}
@@ -2312,6 +2312,7 @@ li.advanced-setting {
width: 140px;
margin-left: 10px;
margin-bottom: 10px;
white-space: nowrap;
}
.user-player-instance-playcount h3 {
font-size: 30px;
@@ -3873,6 +3874,10 @@ a:hover .overlay-refresh-image:hover {
background-color: #31afe1;
background-image: url(../images/platforms/kodi.svg);
}
.platform-lg {
background-color: #a50034;
background-image: url(../images/platforms/lg.svg);
}
.platform-linux {
background-color: #1793d0;
background-image: url(../images/platforms/linux.svg);
@@ -3974,6 +3979,9 @@ a:hover .overlay-refresh-image:hover {
.platform-kodi-rgba {
background-color: rgba(49, 175, 225, 0.40);
}
.platform-lg-rgba {
background-color: rgba(165, 0, 52, 0.40);
}
.platform-linux-rgba {
background-color: rgba(23, 147, 208, 0.40);
}

View File

@@ -0,0 +1,7 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
<title>lg</title>
<path fill="#fff" d="M30.203 31.797c0 8.176-6.654 14.832-14.835 14.82-7.927-0.011-14.818-6.28-14.812-14.838 0.005-8.282 6.541-14.82 14.841-14.803 8.618 0.017 14.807 6.969 14.806 14.822zM26.577 32.388c-0.087 4.433-3.485 9.518-9.37 10.487-6.122 1.008-11.584-2.989-12.814-8.656-0.632-2.912-0.221-5.696 1.362-8.228 2.347-3.754 5.815-5.502 10.222-5.453 0-0.387 0-0.761 0-1.134-4.114-0.281-9.226 1.824-11.763 6.923-2.454 4.932-1.296 10.953 2.811 14.672 4.153 3.762 10.224 4.309 14.953 1.326 2.328-1.468 3.999-3.496 4.997-6.067 0.628-1.617 0.882-3.296 0.813-5.032-2.967 0-5.909 0-8.864 0 0 0.39 0 0.768 0 1.162 2.558-0 5.097-0 7.652-0zM15.991 37.112c0-0.129 0-0.221 0-0.313 0-3.731 0-7.463 0-11.194 0-0.060-0.004-0.119 0-0.179 0.009-0.118-0.038-0.166-0.16-0.163-0.278 0.006-0.556 0.012-0.833-0.002-0.178-0.008-0.237 0.042-0.237 0.23 0.005 4.194 0.005 8.389-0 12.583-0 0.198 0.065 0.239 0.249 0.237 1.224-0.007 2.448-0.004 3.672-0.004 0.072 0 0.143 0 0.244 0 0-0.343-0.008-0.665 0.003-0.987 0.006-0.166-0.050-0.214-0.214-0.212-0.82 0.007-1.641 0.003-2.461 0.003-0.078 0-0.155 0-0.263 0zM12.434 27.068c0.003-0.987-0.799-1.798-1.785-1.805s-1.799 0.796-1.805 1.782c-0.006 0.985 0.799 1.8 1.783 1.805 0.985 0.004 1.804-0.803 1.807-1.783z"></path>
<path fill="#fff" d="M63.467 30.606c0 2.864 0 5.707 0 8.571-1.242 0-2.479 0-3.742 0 0-0.468 0-0.933 0-1.433-0.203 0.226-0.366 0.432-0.553 0.612-0.683 0.656-1.518 1-2.441 1.136-1.187 0.174-2.348 0.075-3.462-0.4-1.234-0.526-2.145-1.407-2.8-2.565-0.599-1.058-0.906-2.207-1.035-3.409-0.148-1.367-0.103-2.723 0.28-4.051 0.797-2.764 2.635-4.391 5.453-4.899 1.534-0.277 3.058-0.208 4.54 0.311 1.243 0.436 2.298 1.139 3.011 2.276 0.431 0.688 0.584 1.467 0.687 2.258 0.013 0.097 0.028 0.195 0.046 0.318-0.064 0.003-0.126 0.010-0.188 0.010-1.389 0.001-2.779-0.002-4.169 0.003-0.151 0.001-0.215-0.034-0.245-0.197-0.229-1.234-1.281-1.773-2.308-1.679-1.182 0.108-1.823 0.859-2.22 1.888-0.211 0.547-0.315 1.12-0.352 1.703-0.066 1.061-0.039 2.117 0.31 3.138 0.211 0.618 0.523 1.173 1.050 1.579 1.371 1.055 3.326 0.436 3.877-1.228 0.090-0.274 0.157-0.557 0.246-0.875-0.112 0-0.182 0-0.251 0-0.794 0-1.588-0.005-2.382 0.004-0.168 0.002-0.213-0.053-0.212-0.214 0.006-0.887 0.005-1.774 0.001-2.66-0.001-0.139 0.019-0.215 0.192-0.215 2.17 0.006 4.341 0.004 6.511 0.004 0.045 0 0.090 0.008 0.157 0.013z"></path>
<path fill="#fff" d="M48.501 35.522c0 1.233 0 2.44 0 3.661-3.613 0-7.216 0-10.834 0 0-4.923 0-9.841 0-14.77 1.44 0 2.872 0 4.331 0 0 0.092 0 0.175 0 0.259 0 3.526 0 7.053 0 10.579 0 0.271 0 0.271 0.267 0.271 1.985 0 3.97 0 5.954 0 0.086 0 0.171 0 0.281 0z"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -36,7 +36,3 @@ function check_notifications() {
check_notifications();
}, 5000);
}
$(document).ready(function () {
check_notifications();
});

View File

@@ -24,7 +24,7 @@
<!-- ICONS -->
<!-- Android -->
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5" crossorigin="use-credentials>
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5" crossorigin="use-credentials">
<meta name="theme-color" content="#282a2d">
<!-- Apple -->
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">

View File

@@ -13,7 +13,11 @@ DOCUMENTATION :: END
% for device in sorted(devices_list, key=lambda k: k['device_name']):
<li class="mobile-device pointer" data-id="${device['id']}" data-name="${device['device_name']}">
<span>
<span class="toggle-left"><i class="fa fa-lg fa-fw fa-mobile"></i></span>
% if device['official']:
<span class="toggle-left"><i class="fa fa-lg fa-fw fa-mobile"></i></span>
% else:
<span class="toggle-left officail-tooltip" data-toggle="tooltip" data-placement="top" title="Unofficial or Unknown App"><i class="fa fa-lg fa-fw fa-exclamation-triangle"></i></span>
% endif
${device['friendly_name'] or device['device_name']} &nbsp;<span class="friendly_name">(${device['id']})</span>
<span class="toggle-right"><i class="fa fa-lg fa-fw fa-cog"></i></span>
<span class="toggle-right friendly_name" id="device-last_seen-${device['id']}">
@@ -138,4 +142,6 @@ DOCUMENTATION :: END
}
verifiedDevice = true;
})
$('.officail-tooltip').tooltip();
</script>

View File

@@ -853,10 +853,7 @@
PNotify.prototype.options.hide = true;
PNotify.prototype.options.delay = $('#browser_auto_hide_delay').val() * 1000;
}
var notification = new PNotify({
title: $('#test_subject').val(),
text: $('#test_body').val()
});
displayPNotify($('#test_subject').val(), $('#test_body').val());
showMsg('<i class="fa fa-check"></i> Notification sent.', false, true, 5000);
}
}

View File

@@ -8,7 +8,7 @@
from plexpy.helpers import anon_url, checked
docker_setting = 'disabled' if plexpy.DOCKER else ''
docker_msg = '<span class="docker-setting small">(Controlled by Docker Container)</span>' if plexpy.DOCKER else ''
docker_msg = '<span class="setting-message small">(Controlled by Docker Container)</span>' if plexpy.DOCKER else ''
available_notification_agents = sorted(notifiers.available_notification_agents(), key=lambda k: k['label'].lower())
available_newsletter_agents = sorted(newsletters.available_newsletter_agents(), key=lambda k: k['label'].lower())
@@ -458,11 +458,20 @@
<p class="help-block">Note: Web interface changes require a restart.</p>
% if common.PLATFORM in ('Windows', 'Darwin'):
<%
tray = {'Windows': 'System Tray', 'Darwin': 'Menu Bar'}
tray_disabled = tray_disabled_msg = ''
if common.PLATFORM == 'Darwin':
from plexpy.macos import HAS_PYOBJC
if not HAS_PYOBJC:
tray_disabled = 'disabled'
tray_disabled_msg = '<span class="setting-message small">(Missing pyobjc module)</span>'
%>
<div class="checkbox">
<label>
<input type="checkbox" class="http-settings" name="sys_tray_icon" id="sys_tray_icon" value="1" ${config['sys_tray_icon']}> Enable System Tray Icon
<input type="checkbox" class="http-settings" name="sys_tray_icon" id="sys_tray_icon" value="1" ${config['sys_tray_icon']} ${tray_disabled}> Enable ${tray[common.PLATFORM]} Icon ${tray_disabled_msg | n}
</label>
<p class="help-block">Show Tautulli shortcut in the system tray.</p>
<p class="help-block">Show Tautulli shortcut in the ${tray[common.PLATFORM].lower()}.</p>
</div>
<div class="checkbox">
<label>
@@ -1047,7 +1056,7 @@
</div>
<div id="notify_recently_added_delay_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">Set the delay (in seconds) to wait for consecutive recently added items to group together and to allow metadata to be processed before sending the notification. Minimum 60 seconds.</p>
<p class="help-block">Set the delay (in seconds) to wait for consecutive recently added items to group together and to allow metadata to be processed before sending the recently added notification. Minimum 60 seconds, default 300.</p>
</div>
<div class="form-group advanced-setting">
<label>Flush Recently Added</label>
@@ -1426,6 +1435,7 @@
<label>Registered Devices</label>
<p class="help-block">Register a new device using a QR code, or configure an existing device by clicking the settings icon on the right.</p>
<p id="app_api_msg" style="color: #eb8600;">Warning: The API must be enabled under <a data-tab-destination="web_interface" data-target="api_enabled">Web Interface</a> to use the app.</p>
<br />
<div class="row">
<div id="plexpy-mobile-devices-table" class="col-md-12">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading registered devices...</div>
@@ -1861,7 +1871,10 @@ Rating: {rating}/10 --> Rating: /10
<label>Instructions</label>
<p class="help-block">
Scan the QR code below with the Tautulli Android app to automatically register it with the server (make sure the Tautulli Address below is correct)
or manually enter the connection info and device token into the app settings.
or manually enter the connection info and device token into the app settings. This window will automatically close once device registration is successful.
</p>
<p class="help-block">
Note: OneSignal.com must not be blocked (e.g. in Pi-hole) for device registration.
</p>
<label>QR Code</label>
<pre id="api_qr_code" style="text-align: center"></pre>
@@ -2541,9 +2554,7 @@ $(document).ready(function() {
$("#token_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
}
function OAuthSuccessCallback(authToken) {
var x_plex_headers = getPlexHeaders();
$("#pms_token").val(authToken);
$("#pms_uuid").val(x_plex_headers['X-Plex-Client-Identifier']);
$("#token_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
getServerOptions(authToken);
}

View File

@@ -27,7 +27,7 @@ DOCUMENTATION :: END
<div id="user-player-image-${a['result_id']}">
<div class="user-player-instance-box svg-icon platform-${a['platform_name']}"></div>
</div>
<div class="user-player-instance-name">
<div class="user-player-instance-name" title="${a['player_name']}">
${a['player_name']}
</div>
<div class="user-player-instance-playcount">

View File

@@ -100,7 +100,7 @@ def createSelfSignedCertificate(issuerName, issuerKey, serial, notBefore, notAft
cert.set_pubkey(issuerKey)
if altNames:
cert.add_extensions([crypto.X509Extension("subjectAltName", False, altNames)])
cert.add_extensions([crypto.X509Extension(b"subjectAltName", False, altNames)])
cert.sign(issuerKey, digest)
return cert

View File

@@ -3,7 +3,7 @@
dialogText=`osascript -e 'set dialogText to button returned of (display dialog "Installation complete. Start Tautulli?" buttons {"Start", "Close"})'`;
if [[ $dialogText == 'Start' ]]
then
open /Applications/Tautulli.app
open /Applications/Tautulli.app
else
exit 0;
fi

View File

@@ -745,7 +745,7 @@ def dbcheck():
c_db.execute(
'CREATE TABLE IF NOT EXISTS mobile_devices (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'device_id TEXT NOT NULL UNIQUE, device_token TEXT, device_name TEXT, friendly_name TEXT, '
'last_seen INTEGER)'
'last_seen INTEGER, official INTEGER DEFAULT 0)'
)
# tvmaze_lookup table :: This table keeps record of the TVmaze lookups
@@ -2001,6 +2001,19 @@ def dbcheck():
'ALTER TABLE mobile_devices ADD COLUMN last_seen INTEGER'
)
# Upgrade mobile_devices table from earlier versions
try:
c_db.execute('SELECT official FROM mobile_devices')
except sqlite3.OperationalError:
logger.debug("Altering database. Updating database table mobile_devices.")
c_db.execute(
'ALTER TABLE mobile_devices ADD COLUMN official INTEGER DEFAULT 0'
)
# Update official mobile device flag
for device_id, in c_db.execute('SELECT device_id FROM mobile_devices').fetchall():
c_db.execute('UPDATE mobile_devices SET official = ? WHERE device_id = ?',
[mobile_app.validate_device_id(device_id), device_id])
# Upgrade notifiers table from earlier versions
try:
c_db.execute('SELECT custom_conditions FROM notifiers')

View File

@@ -136,7 +136,11 @@ class API2(object):
self._api_app = True
if plexpy.CONFIG.API_ENABLED and not self._api_msg or self._api_cmd in ('get_apikey', 'docs', 'docs_md'):
if self._api_apikey == plexpy.CONFIG.API_KEY or (self._api_app and self._api_apikey == mobile_app.get_temp_device_token()):
if self._api_apikey == plexpy.CONFIG.API_KEY:
self._api_authenticated = True
elif self._api_app and self._api_apikey == mobile_app.get_temp_device_token() and \
self._api_cmd == 'register_device':
self._api_authenticated = True
elif self._api_app and mobile_app.get_mobile_device_by_token(self._api_apikey):
@@ -403,12 +407,12 @@ class API2(object):
```
"""
if not device_id:
self._api_msg = 'Device registartion failed: no device id provided.'
self._api_msg = 'Device registration failed: no device id provided.'
self._api_result_type = 'error'
return
elif not device_name:
self._api_msg = 'Device registartion failed: no device name provided.'
self._api_msg = 'Device registration failed: no device name provided.'
self._api_result_type = 'error'
return
@@ -422,7 +426,7 @@ class API2(object):
self._api_result_type = 'success'
mobile_app.set_temp_device_token(None)
else:
self._api_msg = 'Device registartion failed: database error.'
self._api_msg = 'Device registration failed: database error.'
self._api_result_type = 'error'
return

View File

@@ -104,6 +104,7 @@ PLATFORM_NAMES = {
'nexus': 'android',
'macos': 'macos',
'microsoft edge': 'msedge',
'netcast': 'lg',
'opera': 'opera',
'osx': 'macos',
'playstation': 'playstation',
@@ -119,6 +120,7 @@ PLATFORM_NAMES = {
'tizen': 'samsung',
'tvos': 'atv',
'vizio': 'opera',
'webos': 'lg',
'wiiu': 'wiiu',
'windows': 'windows',
'windows phone': 'wp',

View File

@@ -348,7 +348,7 @@ _CONFIG_DEFINITIONS = {
'NOTIFY_GROUP_RECENTLY_ADDED': (int, 'Monitoring', 1),
'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 60),
'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 300),
'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED_UPGRADE': (int, 'Monitoring', 0),
'NOTIFY_CONCURRENT_BY_IP': (int, 'Monitoring', 0),
@@ -943,4 +943,10 @@ class Config(object):
if plexpy.DOCKER:
self.PLEXPY_AUTO_UPDATE = 0
self.CONFIG_VERSION == 15
self.CONFIG_VERSION = 15
if self.CONFIG_VERSION == 15:
if self.HTTP_ROOT and self.HTTP_ROOT != '/':
self.JWT_UPDATE_SECRET = True
self.CONFIG_VERSION = 16

View File

@@ -52,10 +52,12 @@ import xmltodict
import plexpy
if plexpy.PYTHON2:
import common
import logger
import request
from api2 import API2
else:
from plexpy import common
from plexpy import logger
from plexpy import request
from plexpy.api2 import API2
@@ -445,22 +447,25 @@ def create_https_certificates(ssl_cert, ssl_key):
return False
from certgen import createKeyPair, createSelfSignedCertificate, TYPE_RSA
issuer = common.PRODUCT
serial = timestamp()
not_before = 0
not_after = 60 * 60 * 24 * 365 * 10 # ten years
domains = ['DNS:' + d.strip() for d in plexpy.CONFIG.HTTPS_DOMAIN.split(',') if d]
ips = ['IP:' + d.strip() for d in plexpy.CONFIG.HTTPS_IP.split(',') if d]
altNames = ','.join(domains + ips)
alt_names = ','.join(domains + ips).encode('utf-8')
# Create the self-signed Tautulli certificate
logger.debug("Generating self-signed SSL certificate.")
pkey = createKeyPair(TYPE_RSA, 2048)
cert = createSelfSignedCertificate("Tautulli", pkey, serial, 0, 60 * 60 * 24 * 365 * 10, altNames) # ten years
cert = createSelfSignedCertificate(issuer, pkey, serial, not_before, not_after, alt_names)
# Save the key and certificate to disk
try:
with open(ssl_cert, "w") as fp:
fp.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
fp.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8'))
with open(ssl_key, "w") as fp:
fp.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
fp.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey).decode('utf-8'))
except IOError as e:
logger.error("Error creating SSL key and certificate: %s", e)
return False
@@ -732,7 +737,7 @@ def upload_to_cloudinary(img_data, img_title='', rating_key='', fallback=''):
)
try:
response = upload(img_data,
response = upload((img_title, img_data),
public_id='{}_{}'.format(fallback, rating_key),
tags=['tautulli', fallback, str(rating_key)],
context={'title': img_title, 'rating_key': str(rating_key), 'fallback': fallback})

View File

@@ -19,7 +19,16 @@ import os
import subprocess
import sys
import plistlib
import rumps
try:
import AppKit
import Foundation
HAS_PYOBJC = True
except ImportError:
HAS_PYOBJC = False
if HAS_PYOBJC:
import rumps
import plexpy
if plexpy.PYTHON2:
@@ -59,11 +68,11 @@ class MacOSSystemTray(object):
self.tray_icon = rumps.App(common.PRODUCT, icon=self.icon, menu=self.menu, quit_button=None)
def start(self):
logger.info("Launching MacOS system tray icon.")
logger.info("Launching MacOS menu bar icon.")
try:
self.tray_icon.run()
except Exception as e:
logger.error("Unable to launch system tray icon: %s." % e)
logger.error("Unable to launch menu bar icon: %s." % e)
def shutdown(self):
rumps.quit_application()

View File

@@ -18,6 +18,7 @@
from __future__ import unicode_literals
from future.builtins import str
import requests
import threading
import plexpy
@@ -34,6 +35,8 @@ else:
TEMP_DEVICE_TOKEN = None
INVALIDATE_TIMER = None
_ONESIGNAL_APP_ID = '3b4b666a-d557-4b92-acdf-e2c8c4b95357'
def set_temp_device_token(token=None):
global TEMP_DEVICE_TOKEN
@@ -84,7 +87,8 @@ def add_mobile_device(device_id=None, device_name=None, device_token=None, frien
keys = {'device_id': device_id}
values = {'device_name': device_name,
'device_token': device_token}
'device_token': device_token,
'official': validate_device_id(device_id=device_id)}
if friendly_name:
values['friendly_name'] = friendly_name
@@ -161,6 +165,14 @@ def set_last_seen(device_token=None):
return
def validate_device_id(device_id):
headers = {'Content-Type': 'application/json'}
payload = {'app_id': _ONESIGNAL_APP_ID}
r = requests.get('https://onesignal.com/api/v1/players/{}'.format(device_id), headers=headers, json=payload)
return r.status_code == 200
def blacklist_logger():
devices = get_mobile_devices()
for d in devices:

View File

@@ -882,8 +882,6 @@ class ANDROIDAPP(Notifier):
'priority': 3
}
_ONESIGNAL_APP_ID = '3b4b666a-d557-4b92-acdf-e2c8c4b95357'
def agent_notify(self, subject='', body='', action='', notification_id=None, **kwargs):
# Check mobile device is still registered
device = mobile_app.get_mobile_devices(device_id=self.config['device_id'])
@@ -930,7 +928,7 @@ class ANDROIDAPP(Notifier):
#logger.debug("Nonce (base64): {}".format(base64.b64encode(nonce)))
#logger.debug("Salt (base64): {}".format(base64.b64encode(salt)))
payload = {'app_id': self._ONESIGNAL_APP_ID,
payload = {'app_id': mobile_app._ONESIGNAL_APP_ID,
'include_player_ids': [self.config['device_id']],
'contents': {'en': 'Tautulli Notification'},
'data': {'encrypted': True,
@@ -943,7 +941,7 @@ class ANDROIDAPP(Notifier):
"Android app notifications will be sent unecrypted. "
"Install the library to encrypt the notifications.")
payload = {'app_id': self._ONESIGNAL_APP_ID,
payload = {'app_id': mobile_app._ONESIGNAL_APP_ID,
'include_player_ids': [self.config['device_id']],
'contents': {'en': 'Tautulli Notification'},
'data': {'encrypted': False,
@@ -960,7 +958,7 @@ class ANDROIDAPP(Notifier):
db = database.MonitorDatabase()
try:
query = 'SELECT * FROM mobile_devices'
query = 'SELECT * FROM mobile_devices WHERE official = 1'
result = db.select(query=query)
except Exception as e:
logger.warn("Tautulli Notifiers :: Unable to retrieve Android app devices list: %s." % e)
@@ -1123,17 +1121,22 @@ class BROWSER(Notifier):
return True
def _return_config_options(self):
config_option = [{'label': 'Allow Notifications',
config_option = [{'label': 'Note',
'description': 'You may need to refresh the page after saving for changes to take effect.',
'input_type': 'help'
},
{'label': 'Allow Notifications',
'value': 'Allow Notifications',
'name': 'browser_allow_browser',
'description': 'Click to allow browser notifications. You must click this button for each browser.',
'description': 'Click to allow browser notifications. '
'You must click this button for each browser.',
'input_type': 'button'
},
{'label': 'Auto Hide Delay',
'value': self.config['auto_hide_delay'],
'name': 'browser_auto_hide_delay',
'description': 'Set the number of seconds for the notification to remain visible. \
Set 0 to disable auto hiding. (Note: Some browsers have a maximum time limit.)',
'description': 'Set the number of seconds for the notification to remain visible. '
'Set 0 to disable auto hiding. (Note: Some browsers have a maximum time limit.)',
'input_type': 'number'
}
]

View File

@@ -17,5 +17,5 @@
from __future__ import unicode_literals
PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.5.0-beta"
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.5.2"

View File

@@ -41,6 +41,13 @@ else:
from plexpy.users import Users, refresh_users
from plexpy.plextv import PlexTV
# Monkey patch SameSite support into cookies.
# https://stackoverflow.com/a/50813092
try:
from http.cookies import Morsel
except ImportError:
from Cookie import Morsel
Morsel._reserved[str('samesite')] = str('SameSite')
JWT_ALGORITHM = 'HS256'
JWT_COOKIE_NAME = 'tautulli_token_'
@@ -141,7 +148,7 @@ def check_credentials(username=None, password=None, token=None, admin_login='0',
return True, user_details, 'admin'
if plexpy.CONFIG.HTTP_PLEX_ADMIN or (not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS):
plex_login = plex_user_login(username=username, password=password, token=token, headers=headers)
plex_login = plex_user_login(token=token, headers=headers)
if plex_login is not None:
return True, plex_login[0], plex_login[1]
@@ -296,9 +303,13 @@ class AuthController(object):
self.on_logout(payload['user'], payload['user_group'])
jwt_cookie = str(JWT_COOKIE_NAME + plexpy.CONFIG.PMS_UUID)
cherrypy.response.cookie[jwt_cookie] = 'expire'
cherrypy.response.cookie[jwt_cookie] = ''
cherrypy.response.cookie[jwt_cookie]['expires'] = 0
cherrypy.response.cookie[jwt_cookie]['path'] = '/'
cherrypy.response.cookie[jwt_cookie]['path'] = plexpy.HTTP_ROOT.rstrip('/') or '/'
if plexpy.HTTP_ROOT != '/':
# Also expire the JWT on the root path
cherrypy.response.headers['Set-Cookie'] = jwt_cookie + '=""; expires=Thu, 01 Jan 1970 12:00:00 GMT; path=/'
cherrypy.request.login = None
@@ -344,7 +355,9 @@ class AuthController(object):
jwt_cookie = str(JWT_COOKIE_NAME + plexpy.CONFIG.PMS_UUID)
cherrypy.response.cookie[jwt_cookie] = jwt_token
cherrypy.response.cookie[jwt_cookie]['expires'] = int(time_delta.total_seconds())
cherrypy.response.cookie[jwt_cookie]['path'] = '/'
cherrypy.response.cookie[jwt_cookie]['path'] = plexpy.HTTP_ROOT.rstrip('/') or '/'
cherrypy.response.cookie[jwt_cookie]['httponly'] = True
cherrypy.response.cookie[jwt_cookie]['samesite'] = 'lax'
cherrypy.request.login = payload
cherrypy.response.status = 200

View File

@@ -1,2 +1,37 @@
#!/usr/bin/env bash
python Tautulli.py &> /dev/null &
if [[ "$TAUTULLI_DOCKER" = "True" ]]; then
if [[ -v PUID && -v PGID ]]; then
getent group "$PGID" 2>&1 > /dev/null || groupadd -g "$PGID" tautulli
getent passwd "$PUID" 2>&1 > /dev/null || useradd -r -u "$PUID" -g "$PGID" tautulli
user=$(getent passwd "$PUID" | cut -d: -f1)
group=$(getent group "$PGID" | cut -d: -f1)
usermod -a -G root "$user"
chown -R "$user":"$group" /config
echo "Running Tautulli using user $user (uid=$PUID) and group $group (gid=$PGID)"
su "$user" -g "$group" -c "python /app/Tautulli.py --datadir /config"
else
python Tautulli.py --datadir /config
fi
else
if which python3 >/dev/null; then
python3 Tautulli.py &> /dev/null &
elif which python3.8 >/dev/null; then
python3.8 Tautulli.py &> /dev/null &
elif which python3.7 >/dev/null; then
python3.7 Tautulli.py &> /dev/null &
elif which python3.6 >/dev/null; then
python3.6 Tautulli.py &> /dev/null &
elif which python >/dev/null; then
python Tautulli.py &> /dev/null &
elif which python2 >/dev/null; then
python2 Tautulli.py &> /dev/null &
elif which python2.7 >/dev/null; then
python2.7 Tautulli.py &> /dev/null &
else
echo "Cannot start Tautulli: python not found."
fi
fi