Initial commit: Complete workspace configuration
- MOper/ configurations - home-assistant/ configurations - scripts/ automation scripts - unix/ system configurations - docker/ Docker services (app, devtools, database, infra, maintenance, portainer, supervision, test) Excludes: databases, logs, large files, Git submodules, secrets (via .gitignore)
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"projectName": "Organizr",
|
||||
"projectOwner": "causefx",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"commitType": "docs",
|
||||
"commitConvention": "angular",
|
||||
"contributorsPerLine": 7,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "tronyx",
|
||||
"name": "Chris Yocum",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/22502007?v=4",
|
||||
"profile": "https://tronflix.app",
|
||||
"contributions": [
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Roxedus",
|
||||
"name": "Roxedus",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7110194?v=4",
|
||||
"profile": "http://roxedus.dev",
|
||||
"contributions": [
|
||||
"test"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "HalianElf",
|
||||
"name": "HalianElf",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/28244771?v=4",
|
||||
"profile": "https://github.com/HalianElf",
|
||||
"contributions": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
19
docker/test/tvtracker/config/www/organizr/.gitattributes
vendored
Normal file
19
docker/test/tvtracker/config/www/organizr/.gitattributes
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.css linguist-language=PHP
|
||||
bower_components/* linguist-vendored
|
||||
9
docker/test/tvtracker/config/www/organizr/.github/FUNDING.yml
vendored
Normal file
9
docker/test/tvtracker/config/www/organizr/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: causefx
|
||||
patreon: organizr
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
custom: # Replace with a single custom sponsorship URL
|
||||
22
docker/test/tvtracker/config/www/organizr/.github/ISSUE_TEMPLATE.md
vendored
Normal file
22
docker/test/tvtracker/config/www/organizr/.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<!-- Please Fill out as much information as possible, Thanks! -->
|
||||
###### Organizr Version: V 1.x
|
||||
###### Branch: Master/Develop
|
||||
###### WebServer: Nginx/Apache
|
||||
###### Operating System: Windows/MacOS/Ubuntu
|
||||
<hr>
|
||||
|
||||
##### Problem Description:
|
||||
<!---TYPE HERE--->
|
||||
|
||||
<hr>
|
||||
|
||||
##### Reproduction Steps:
|
||||
<!---TYPE HERE--->
|
||||
|
||||
<hr>
|
||||
|
||||
#### Errors on screen? If so paste here:
|
||||
<!-- (Errors go below the first ``` . Don't remove the ' tags) -->
|
||||
```
|
||||
|
||||
```
|
||||
14
docker/test/tvtracker/config/www/organizr/.github/workflows/lock.yml
vendored
Normal file
14
docker/test/tvtracker/config/www/organizr/.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Lock closed stale issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
if: github.event.label.name == 'closed-no-issue-activity'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: OSDKDev/lock-issues@v1.1
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
21
docker/test/tvtracker/config/www/organizr/.github/workflows/stale.yml
vendored
Normal file
21
docker/test/tvtracker/config/www/organizr/.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Close stale issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 3 * * *"
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v3
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: "This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs."
|
||||
close-issue-message: "This issue has been closed due to lack of activity, if this issue still persists, please re-open it."
|
||||
close-issue-label: "closed-no-issue-activity"
|
||||
stale-issue-label: "no-issue-activity"
|
||||
days-before-stale: 15
|
||||
days-before-close: 90
|
||||
exempt-issue-labels: "bypass-activity"
|
||||
exempt-pr-labels: "bypass-activity"
|
||||
194
docker/test/tvtracker/config/www/organizr/.gitignore
vendored
Executable file
194
docker/test/tvtracker/config/www/organizr/.gitignore
vendored
Executable file
@@ -0,0 +1,194 @@
|
||||
# PhpStorm Files
|
||||
.idea/*
|
||||
node_modules/*
|
||||
package-lock.json
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# =========================
|
||||
# Operating System Files
|
||||
# =========================
|
||||
|
||||
# OSX
|
||||
# =========================
|
||||
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Files from my local computer
|
||||
php_errors.log
|
||||
|
||||
# Backup Files
|
||||
.pydio_id
|
||||
|
||||
# =========================
|
||||
# Organizr files
|
||||
# =========================
|
||||
organizr-*.log
|
||||
databaseLocation.ini.php
|
||||
homepageSettings.ini.php
|
||||
loginLog.json
|
||||
custom.css
|
||||
_config.yml
|
||||
org.log
|
||||
org*.log
|
||||
test.php
|
||||
users.db
|
||||
speedtest.db
|
||||
chatpack.db
|
||||
api.json
|
||||
Cron.txt
|
||||
Docker.txt
|
||||
Github.txt
|
||||
Demo.txt
|
||||
DemoTest.txt
|
||||
Dev.txt
|
||||
updateInProgress.txt
|
||||
config/cacert.pem
|
||||
config/custom.pem
|
||||
config/config.php
|
||||
config/config*.bak.php
|
||||
config/users/
|
||||
config/users
|
||||
config/users*.db
|
||||
config/users*.bak.db
|
||||
config/tmp/*
|
||||
data/*
|
||||
data/config/*
|
||||
docs/api.json
|
||||
images/cache/*
|
||||
backups/*
|
||||
backups/
|
||||
backups
|
||||
tracy/
|
||||
logs/
|
||||
debug.php
|
||||
OrganizrV2/*
|
||||
orgv2/*
|
||||
test/*
|
||||
organizrLoginLog.json
|
||||
organizrLog.json
|
||||
api/config/config*
|
||||
upgrade/*
|
||||
api/upgrade*
|
||||
api/functions/cert/cacert.pem
|
||||
api/functions/cert/custom.pem
|
||||
wallpaper.jpg
|
||||
strings.json
|
||||
css/themes/*.css
|
||||
!css/themes/Organizr.css
|
||||
!css/themes/Blue.css
|
||||
*sonflix*
|
||||
plugins/images/cache/*.jpg
|
||||
testdata*
|
||||
strings.txt
|
||||
strings.php
|
||||
plugins/theme_files/*
|
||||
plugins/plugin_files/*
|
||||
!plugins/theme_files/index.html
|
||||
!plugins/plugin_files/index.html
|
||||
plugins/images/userTabs/*
|
||||
!plugins/images/userTabs/index.html
|
||||
plugins/images/faviconCustom/*
|
||||
!plugins/images/faviconCustom/placeFavIconsHere.txt
|
||||
api/v2/routes/custom/*
|
||||
!api/v2/routes/custom/index.html
|
||||
# =========================
|
||||
# Plugin files
|
||||
# =========================
|
||||
api/plugins/*
|
||||
!api/plugins/invites.php
|
||||
!api/plugins/api/invites.php
|
||||
!api/plugins/config/invites.php
|
||||
!api/plugins/js/invites.js
|
||||
!api/plugins/chat.php
|
||||
!api/plugins/api/chat.php
|
||||
!api/plugins/config/chat.php
|
||||
!api/plugins/js/chat.js
|
||||
!api/plugins/healthChecks.php
|
||||
!api/plugins/api/healthChecks.php
|
||||
!api/plugins/config/healthChecks.php
|
||||
!api/plugins/js/healthChecks.js
|
||||
!api/plugins/js/healthChecks-settings.js
|
||||
!api/plugins/php-mailer.php
|
||||
!api/plugins/api/php-mailer.php
|
||||
!api/plugins/config/php-mailer.php
|
||||
!api/plugins/js/php-mailer.js
|
||||
!api/plugins/speedTest.php
|
||||
!api/plugins/api/speedTest.php
|
||||
!api/plugins/config/speedTest.php
|
||||
!api/plugins/js/speedTest.js
|
||||
!api/plugins/misc/emailTemplates/default.php
|
||||
!api/plugins/misc/emailTemplates/gray.php
|
||||
!api/plugins/misc/emailTemplates/light.php
|
||||
!api/plugins/misc/emailTemplates/dark.php
|
||||
!api/plugins/misc/emailTemplates/plehex.php
|
||||
!api/plugins/misc/speedTest/empty.php
|
||||
!api/plugins/misc/speedTest/garbage.php
|
||||
!api/plugins/misc/speedTest/getIP.php
|
||||
!api/plugins/misc/speedTest/telemetry.php
|
||||
!api/plugins/misc/speedTest/telemetry_settings.php
|
||||
!api/plugins/misc/speedTest/speedtest_worker.min.js
|
||||
!api/plugins/bookmark.php
|
||||
!api/plugins/api/bookmark.php
|
||||
!api/plugins/config/bookmark.php
|
||||
!api/plugins/js/bookmark-settings.js
|
||||
!api/plugins/css/bookmark.css
|
||||
!api/plugins/bookmark/
|
||||
!api/plugins/chat/
|
||||
!api/plugins/healthChecks/
|
||||
!api/plugins/invites/
|
||||
!api/plugins/php-mailer/
|
||||
!api/plugins/speedTest/
|
||||
!api/plugins/shuck-stop/
|
||||
api/plugins/shuck-stop/drives.json
|
||||
|
||||
# =========================
|
||||
# Custom files
|
||||
# =========================
|
||||
api/pages/custom/*.php
|
||||
/plugins/images/tabs/eatsleep.jpg
|
||||
/plugins/images/cache/tautulli-show.svg
|
||||
/plugins/images/cache/tautulli-android.svg
|
||||
/plugins/images/cache/tautulli-artist.svg
|
||||
/plugins/images/cache/tautulli-movie.svg
|
||||
/plugins/images/cache/tautulli-windows.svg
|
||||
/plugins/images/cache/tautulli-samsung.svg
|
||||
/plugins/images/cache/tautulli-chrome.svg
|
||||
.vscode
|
||||
128
docker/test/tvtracker/config/www/organizr/CODE_OF_CONDUCT.md
Normal file
128
docker/test/tvtracker/config/www/organizr/CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
32
docker/test/tvtracker/config/www/organizr/CONTRIBUTING.md
Normal file
32
docker/test/tvtracker/config/www/organizr/CONTRIBUTING.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# How to Contribute #
|
||||
|
||||
We're always looking for people to help make Organizr better.
|
||||
|
||||
## Documentation ##
|
||||
[Docs](https://docs.organizr.app)
|
||||
|
||||
## Development ##
|
||||
|
||||
### Tools required ###
|
||||
- Any IDE of your choice that supports PHP, HTML and Javascript.
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
|
||||
### Getting started ###
|
||||
|
||||
1. Fork Organizr
|
||||
2. Clone the repository into your development machine. [*info*](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github)
|
||||
3. Get to work
|
||||
|
||||
### Contributing Code ###
|
||||
- If you're adding a new, already requested feature, please make a new Github issue so work is not duplicated (If you want to add something not already on there, please talk to us first)
|
||||
- Rebase from Organizr's `develop` branch, don't merge
|
||||
- Make meaningful commits, or squash them
|
||||
- Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements
|
||||
- One feature/bug fix per pull request to keep things clean and easy to understand
|
||||
|
||||
### Pull Requesting ###
|
||||
- Only make pull requests to develop (currently v2-develop), never master, if you make a PR to master we'll comment on it and close it
|
||||
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
|
||||
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
|
||||
|
||||
If you have any questions about any of this, please let us know.
|
||||
674
docker/test/tvtracker/config/www/organizr/LICENSE
Normal file
674
docker/test/tvtracker/config/www/organizr/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
132
docker/test/tvtracker/config/www/organizr/README.md
Normal file
132
docker/test/tvtracker/config/www/organizr/README.md
Normal file
@@ -0,0 +1,132 @@
|
||||

|
||||
|
||||
[](http://isitmaintained.com/project/causefx/Organizr "Percentage of issues still open")
|
||||
[](http://isitmaintained.com/project/causefx/Organizr "Average time to resolve an issue")
|
||||
[](https://github.com/causefx/Organizr/stargazers)
|
||||
[](https://github.com/causefx/Organizr/network)
|
||||
[](https://hub.docker.com/r/organizr/organizr)
|
||||
[](https://paypal.me/causefx)
|
||||
[](https://beerpay.io/causefx/Organizr)
|
||||
[](https://beerpay.io/causefx/Organizr?focus=wish)
|
||||
|
||||

|
||||
|
||||
Do you have quite a bit of services running on your computer or server? Do you have a lot of bookmarks or have to memorize a bunch of ip's and ports? Well, Organizr is here to help with that. Organizr allows you to setup "Tabs" that will be loaded all in one webpage. You can then work on your server with ease. Want to give users access to some Tabs? No problem, just enable user support and have them make an account. Want guests to be able to visit too? Enable Guest support for those tabs.
|
||||
|
||||

|
||||
|
||||
- PHP 7.2+
|
||||
- [Official Site](https://organizr.app) - Will be refreshed soon!
|
||||
- [Official Discord](https://organizr.app/discord)
|
||||
|
||||
- [See Wiki](https://docs.organizr.app/) - Will be updated soon!
|
||||
- [Docker](https://hub.docker.com/r/organizr/organizr)
|
||||
|
||||

|
||||
|
||||
<img src="https://user-images.githubusercontent.com/16184466/53615855-35cc5a80-3b9d-11e9-882b-f09f3eb18173.png" width="23%"></img>
|
||||
<img src="https://user-images.githubusercontent.com/16184466/53615856-35cc5a80-3b9d-11e9-8428-1f2ae05da2c9.png" width="23%"></img>
|
||||
<img src="https://user-images.githubusercontent.com/16184466/53615857-35cc5a80-3b9d-11e9-82bf-91987c529e72.png" width="23%"></img>
|
||||
<img src="https://user-images.githubusercontent.com/16184466/53615858-35cc5a80-3b9d-11e9-8149-01a7fcd9160a.png" width="23%"></img>
|
||||
|
||||
[](https://www.youtube.com/watch?v=LZL4smFB6wU)
|
||||
|
||||

|
||||
|
||||
- 'Forgot Password' support [receive an email with your new password, prerequisites: mail server setup]
|
||||
- Additional language support
|
||||
- Custom tabs for your services
|
||||
- Customise the top bar by adding your own site logo or site name
|
||||
- Enable or disable iFrame for your tabs
|
||||
- Fail2ban support ([see wiki](https://docs.organizr.app/features/fail2ban-integration))
|
||||
- Fullscreen Support
|
||||
- Gravatar Support
|
||||
- Keyboard shortcut support (Check help tab in settings)
|
||||
- Login with Plex/Emby/LDAP or sFTP credentials
|
||||
- Mobile support
|
||||
- Multiple login support
|
||||
- Nginx Auth_Request support ([see wiki](https://docs.organizr.app/features/server-authentication))
|
||||
- Organizr login log viewer
|
||||
- Personalise any theme: Customise the look and feel of Organizr with access to the colour palette
|
||||
- Pin/Unpin sidebar
|
||||
- Protect new user account creation with registration password
|
||||
- Quick access tabs (access your tabs quickly e.g. www.example.com/#Sonarr)
|
||||
- Set default page on launch
|
||||
- Theme-able
|
||||
- Unlimited User Groups
|
||||
- Upload new icons with ease
|
||||
- User management support: Create, delete and promote users from the user management console
|
||||
- Many more...
|
||||
|
||||

|
||||
|
||||
[![Feature Requests]](https://vote.organizr.app/)
|
||||
|
||||

|
||||
|
||||
[](https://github.com/Organizr/docker-organizr)
|
||||
[](https://github.com/organizr/docker-organizr/actions?query=workflow%3A%22Build+Container%22)
|
||||
[](https://hub.docker.com/r/organizr/organizr/)
|
||||
|
||||
##### Usage
|
||||
|
||||
```bash
|
||||
docker create \
|
||||
--name=organizr \
|
||||
-v <path to data>:/config \
|
||||
-e PGID=<gid> -e PUID=<uid> \
|
||||
-p 80:80 \
|
||||
-e fpm="false" `#optional` \
|
||||
-e branch="v2-master" `#optional` \
|
||||
organizr/organizr
|
||||
```
|
||||
|
||||
##### Parameters
|
||||
|
||||
The parameters are split into two halves, separated by a colon, the left hand side representing the host and the right the container side. For example with a port -p external:internal - what this shows is the port mapping from internal to external of the container. So `-p 8080:80` would expose port 80 from inside the container to be accessible from the host's IP on port 8080 and `http://192.168.x.x:8080` would show you what's running INSIDE the container on port 80.
|
||||
|
||||
- `-p 80` - The port(s)
|
||||
- `-v /config` - Mapping the config files for Organizr
|
||||
- `-e PGID` Used for GroupID - see below for link
|
||||
- `-e PUID` Used for UserID - see below for link
|
||||
|
||||
The optional parameters and GID and UID are described in the [readme](https://github.com/Organizr/docker-organizr#parameters) for the container.
|
||||
|
||||
##### Info
|
||||
|
||||
- Shell access whilst the container is running: `docker exec -it organizr /bin/bash`
|
||||
- To monitor the logs of the container in realtime: `docker logs -f organizr`
|
||||
|
||||

|
||||
|
||||
### Seedboxes.cc
|
||||
|
||||
[](https://www.seedboxes.cc)
|
||||
|
||||
### BrowserStack for allowing us to use their platform for testing
|
||||
|
||||
[](https://www.browserstack.com)
|
||||
|
||||
### This project is supported by
|
||||
|
||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/SVG/DO_Logo_horizontal_blue.svg" width="200px"></img>
|
||||
|
||||
## Contributors
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- prettier-ignore-start -->
|
||||
<!-- markdownlint-disable -->
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://tronflix.app"><img src="https://avatars.githubusercontent.com/u/22502007?v=4?s=100" width="100px;" alt="Chris Yocum"/><br /><sub><b>Chris Yocum</b></sub></a><br /><a href="#test-tronyx" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://roxedus.dev"><img src="https://avatars.githubusercontent.com/u/7110194?v=4?s=100" width="100px;" alt="Roxedus"/><br /><sub><b>Roxedus</b></sub></a><br /><a href="#test-Roxedus" title="Tests">⚠️</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/HalianElf"><img src="https://avatars.githubusercontent.com/u/28244771?v=4?s=100" width="100px;" alt="HalianElf"/><br /><sub><b>HalianElf</b></sub></a><br /><a href="#test-HalianElf" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- markdownlint-restore -->
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END --
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
class Requests_Auth_Digest extends Requests_Auth_Basic
|
||||
{
|
||||
|
||||
/**
|
||||
* Set cURL parameters before the data is sent
|
||||
*
|
||||
* @param resource $handle cURL resource
|
||||
*/
|
||||
public function curl_before_send(&$handle)
|
||||
{
|
||||
curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString());
|
||||
curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); //CURLAUTH_ANY work with Wowza RESTful
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
<?php
|
||||
|
||||
class deluge
|
||||
{
|
||||
private $ch;
|
||||
private $url;
|
||||
private $request_id;
|
||||
public $options;
|
||||
|
||||
public function __construct($host, $password, $options)
|
||||
{
|
||||
$verify = $options['verify'] ?? false;
|
||||
$this->url = $host . (substr($host, -1) == "/" ? "" : "/") . "json";
|
||||
$this->request_id = 0;
|
||||
$this->ch = curl_init($this->url);
|
||||
switch (gettype($verify)) {
|
||||
case 'string':
|
||||
$cert = $options['custom_cert'];
|
||||
$verify = 2;
|
||||
break;
|
||||
case 'NULL':
|
||||
$cert = $options['organizr_cert'];
|
||||
$verify = 2;
|
||||
break;
|
||||
default:
|
||||
$cert = $options['organizr_cert'];
|
||||
$verify = false;
|
||||
break;
|
||||
}
|
||||
$curl_options = array(
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => array("Accept: application/json", "Content-Type: application/json"),
|
||||
CURLOPT_ENCODING => "",
|
||||
CURLOPT_COOKIEJAR => "",
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_CAINFO => $cert,
|
||||
CURLOPT_SSL_VERIFYHOST => $verify,
|
||||
CURLOPT_SSL_VERIFYPEER => $verify,
|
||||
);
|
||||
curl_setopt_array($this->ch, $curl_options);
|
||||
//Log in and get cookies
|
||||
$result = $this->makeRequest("auth.login", array($password));
|
||||
if (gettype($result) == 'boolean' && $result !== true) {
|
||||
throw new Exception("Login failed");
|
||||
} elseif (gettype($result) == 'string') {
|
||||
throw new Exception($result);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//webapi functions (https://github.com/idlesign/deluge-webapi)
|
||||
//
|
||||
/////////////////////////////
|
||||
//ids is an array of hashes, or null for all
|
||||
//params is an array of params:
|
||||
//active_time, all_time_download, comment, compact, distributed_copies, download_payload_rate, eta, file_priorities, file_progress, files, hash, is_auto_managed, is_finished, is_seed, max_connections, max_download_speed, max_upload_slots, max_upload_speed, message, move_completed, move_completed_path, move_on_completed, move_on_completed_path, name, next_announce, num_files, num_peers, num_pieces, num_seeds, paused, peers, piece_length, prioritize_first_last, private, progress, queue, ratio, remove_at_ratio, save_path, seed_rank, seeding_time, seeds_peers_ratio, state, stop_at_ratio, stop_ratio, time_added, total_done, total_payload_download, total_payload_upload, total_peers, total_seeds, total_size, total_uploaded, total_wanted, tracker, tracker_host, tracker_status, trackers, upload_payload_rate
|
||||
//or an empty array for all (or null, which returns comment, hash, name, save_path)
|
||||
public function getTorrents($ids, $params)
|
||||
{
|
||||
//if (isset($this->makeRequest("webapi.get_torrents", array($ids, $params))->torrents)) {
|
||||
return $this->makeRequest("webapi.get_torrents", array($ids, $params))->torrents;
|
||||
//} else {
|
||||
//throw new Exception("Login failed");
|
||||
//}
|
||||
}
|
||||
|
||||
//metainfo is base64 encoded torrent data or a magnet url
|
||||
//returns a torrent hash or null if the torrent wasn't aded
|
||||
//params are torrent addition parameters like download_location?
|
||||
public function addTorrent($metaInfo, $params)
|
||||
{
|
||||
return $this->makeRequest("webapi.add_torrent", array($metaInfo, $params));
|
||||
}
|
||||
|
||||
/*implemented in core
|
||||
public function removeTorrent($hash, $removeData) {
|
||||
return $this->makeRequest("webapi.remove_torrent", array($hash, $removeData));
|
||||
}*/
|
||||
public function getWebAPIVersion()
|
||||
{
|
||||
return $this->makeRequest("webapi.get_api_version", array());
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//core functions
|
||||
//
|
||||
//parsed from https://web.archive.org/web/20150423162855/http://deluge-torrent.org:80/docs/master/core/rpc.html
|
||||
//
|
||||
/////////////////////////////
|
||||
//Adds a torrent file to the session.
|
||||
//Parameters:
|
||||
//filename (string) – the filename of the torrent
|
||||
//filedump (string) – a base64 encoded string of the torrent file contents
|
||||
//options (dict) – the options to apply to the torrent on add
|
||||
public function addTorrentFile($filename, $filedump, $options)
|
||||
{
|
||||
return $this->makeRequest("core.add_torrent_file", array($filename, $filedump, $options));
|
||||
}
|
||||
|
||||
//Adds a torrent from a magnet link.
|
||||
//Parameters:
|
||||
//uri (string) – the magnet link
|
||||
//options (dict) – the options to apply to the torrent on add
|
||||
public function addTorrentMagnet($uri, $options)
|
||||
{
|
||||
return $this->makeRequest("core.add_torrent_magnet", array($uri, $options));
|
||||
}
|
||||
|
||||
//Adds a torrent from a url. Deluge will attempt to fetch the torrentfrom url prior to adding it to the session.
|
||||
//Parameters:
|
||||
//url (string) – the url pointing to the torrent file
|
||||
//options (dict) – the options to apply to the torrent on add
|
||||
//headers (dict) – any optional headers to send
|
||||
public function addTorrentUrl($url, $options, $headers)
|
||||
{
|
||||
return $this->makeRequest("core.add_torrent_url", array($url, $options, $headers));
|
||||
}
|
||||
|
||||
public function connectPeer($torrentId, $ip, $port)
|
||||
{
|
||||
return $this->makeRequest("core.connect_peer", array($torrentId, $ip, $port));
|
||||
}
|
||||
|
||||
public function createTorrent($path, $tracker, $pieceLength, $comment, $target, $webseeds, $private, $createdBy, $trackers, $addToSession)
|
||||
{
|
||||
return $this->makeRequest("core.create_torrent", array($path, $tracker, $pieceLength, $comment, $target, $webseeds, $private, $createdBy, $trackers, $addToSession));
|
||||
}
|
||||
|
||||
public function disablePlugin($plugin)
|
||||
{
|
||||
return $this->makeRequest("core.disable_plugin", array($plugin));
|
||||
}
|
||||
|
||||
public function enablePlugin($plugin)
|
||||
{
|
||||
return $this->makeRequest("core.enable_plugin", array($plugin));
|
||||
}
|
||||
|
||||
public function forceReannounce($torrentIds)
|
||||
{
|
||||
return $this->makeRequest("core.force_reannounce", array($torrentIds));
|
||||
}
|
||||
|
||||
//Forces a data recheck on torrent_ids
|
||||
public function forceRecheck($torrentIds)
|
||||
{
|
||||
return $this->makeRequest("core.force_recheck", array($torrentIds));
|
||||
}
|
||||
|
||||
//Returns a list of plugins available in the core
|
||||
public function getAvailablePlugins()
|
||||
{
|
||||
return $this->makeRequest("core.get_available_plugins", array());
|
||||
}
|
||||
|
||||
//Returns a dictionary of the session’s cache status.
|
||||
public function getCacheStatus()
|
||||
{
|
||||
return $this->makeRequest("core.get_cache_status", array());
|
||||
}
|
||||
|
||||
//Get all the preferences as a dictionary
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->makeRequest("core.get_config", array());
|
||||
}
|
||||
|
||||
//Get the config value for key
|
||||
public function getConfigValue($key)
|
||||
{
|
||||
return $this->makeRequest("core.get_config_value", array($key));
|
||||
}
|
||||
|
||||
//Get the config values for the entered keys
|
||||
public function getConfigValues($keys)
|
||||
{
|
||||
return $this->makeRequest("core.get_config_values", array($keys));
|
||||
}
|
||||
|
||||
//Returns a list of enabled plugins in the core
|
||||
public function getEnabledPlugins()
|
||||
{
|
||||
return $this->makeRequest("core.get_enabled_plugins", array());
|
||||
}
|
||||
|
||||
//returns {field: [(value,count)] }for use in sidebar(s)
|
||||
public function getFilterTree($showZeroHits, $hideCat)
|
||||
{
|
||||
return $this->makeRequest("core.get_filter_tree", array($showZeroHits, $hideCat));
|
||||
}
|
||||
|
||||
//Returns the number of free bytes at path
|
||||
public function getFreeSpace($path)
|
||||
{
|
||||
return $this->makeRequest("core.get_free_space", array($path));
|
||||
}
|
||||
|
||||
//Returns the libtorrent version.
|
||||
public function getLibtorrentVersion()
|
||||
{
|
||||
return $this->makeRequest("core.get_libtorrent_version", array());
|
||||
}
|
||||
|
||||
//Returns the active listen port
|
||||
public function getListenPort()
|
||||
{
|
||||
return $this->makeRequest("core.get_listen_port", array());
|
||||
}
|
||||
|
||||
//Returns the current number of connections
|
||||
public function getNumConnections()
|
||||
{
|
||||
return $this->makeRequest("core.get_num_connections", array());
|
||||
}
|
||||
|
||||
public function getPathSize($path)
|
||||
{
|
||||
return $this->makeRequest("core.get_path_size", array($path));
|
||||
}
|
||||
|
||||
//Returns a list of torrent_ids in the session.
|
||||
public function getSessionState()
|
||||
{
|
||||
return $this->makeRequest("core.get_session_state", array());
|
||||
}
|
||||
|
||||
//Gets the session status values for ‘keys’, these keys are takingfrom libtorrent’s session status.
|
||||
public function getSessionStatus($keys)
|
||||
{
|
||||
return $this->makeRequest("core.get_session_status", array($keys));
|
||||
}
|
||||
|
||||
public function getTorrentStatus($torrentId, $keys, $diff)
|
||||
{
|
||||
return $this->makeRequest("core.get_torrent_status", array($torrentId, $keys, $diff));
|
||||
}
|
||||
|
||||
//returns all torrents , optionally filtered by filter_dict.
|
||||
public function getTorrentsStatus($filterDict, $keys, $diff)
|
||||
{
|
||||
return $this->makeRequest("core.get_torrents_status", array($filterDict, $keys, $diff));
|
||||
}
|
||||
|
||||
public function glob($path)
|
||||
{
|
||||
return $this->makeRequest("core.glob", array($path));
|
||||
}
|
||||
|
||||
public function moveStorage($torrentIds, $dest)
|
||||
{
|
||||
return $this->makeRequest("core.move_storage", array($torrentIds, $dest));
|
||||
}
|
||||
|
||||
//Pause all torrents in the session
|
||||
public function pauseAllTorrents()
|
||||
{
|
||||
return $this->makeRequest("core.pause_all_torrents", array());
|
||||
}
|
||||
|
||||
public function pauseTorrent($torrentIds)
|
||||
{
|
||||
return $this->makeRequest("core.pause_torrent", array($torrentIds));
|
||||
}
|
||||
|
||||
public function queueBottom($torrentIds)
|
||||
{
|
||||
return $this->makeRequest("core.queue_bottom", array($torrentIds));
|
||||
}
|
||||
|
||||
public function queueDown($torrentIds)
|
||||
{
|
||||
return $this->makeRequest("core.queue_down", array($torrentIds));
|
||||
}
|
||||
|
||||
public function queueTop($torrentIds)
|
||||
{
|
||||
return $this->makeRequest("core.queue_top", array($torrentIds));
|
||||
}
|
||||
|
||||
public function queueUp($torrentIds)
|
||||
{
|
||||
return $this->makeRequest("core.queue_up", array($torrentIds));
|
||||
}
|
||||
|
||||
//Removes a torrent from the session.
|
||||
//Parameters:
|
||||
//torrentId (string) – the torrentId of the torrent to remove
|
||||
//removeData (boolean) – if True, remove the data associated with this torrent
|
||||
public function removeTorrent($torrentId, $removeData)
|
||||
{
|
||||
return $this->makeRequest("core.remove_torrent", array($torrentId, $removeData));
|
||||
}
|
||||
|
||||
//Rename files in torrent_id. Since this is an asynchronous operation bylibtorrent, watch for the TorrentFileRenamedEvent to know when thefiles have been renamed.
|
||||
//Parameters:
|
||||
//torrentId (string) – the torrentId to rename files
|
||||
//filenames (((index, filename), ...)) – a list of index, filename pairs
|
||||
public function renameFiles($torrentId, $filenames)
|
||||
{
|
||||
return $this->makeRequest("core.rename_files", array($torrentId, $filenames));
|
||||
}
|
||||
|
||||
//Renames the ‘folder’ to ‘new_folder’ in ‘torrent_id’. Watch for theTorrentFolderRenamedEvent which is emitted when the folder has beenrenamed successfully.
|
||||
//Parameters:
|
||||
//torrentId (string) – the torrent to rename folder in
|
||||
//folder (string) – the folder to rename
|
||||
//newFolder (string) – the new folder name
|
||||
public function renameFolder($torrentId, $folder, $newFolder)
|
||||
{
|
||||
return $this->makeRequest("core.rename_folder", array($torrentId, $folder, $newFolder));
|
||||
}
|
||||
|
||||
//Rescans the plugin folders for new plugins
|
||||
public function rescanPlugins()
|
||||
{
|
||||
return $this->makeRequest("core.rescan_plugins", array());
|
||||
}
|
||||
|
||||
//Resume all torrents in the session
|
||||
public function resumeAllTorrents()
|
||||
{
|
||||
return $this->makeRequest("core.resume_all_torrents", array());
|
||||
}
|
||||
|
||||
public function resumeTorrent($torrentIds)
|
||||
{
|
||||
return $this->makeRequest("core.resume_torrent", array($torrentIds));
|
||||
}
|
||||
|
||||
//Set the config with values from dictionary
|
||||
public function setConfig($config)
|
||||
{
|
||||
return $this->makeRequest("core.set_config", array($config));
|
||||
}
|
||||
|
||||
//Sets the auto managed flag for queueing purposes
|
||||
public function setTorrentAutoManaged($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_auto_managed", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets a torrents file priorities
|
||||
public function setTorrentFilePriorities($torrentId, $priorities)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_file_priorities", array($torrentId, $priorities));
|
||||
}
|
||||
|
||||
//Sets a torrents max number of connections
|
||||
public function setTorrentMaxConnections($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_max_connections", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets a torrents max download speed
|
||||
public function setTorrentMaxDownloadSpeed($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_max_download_speed", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets a torrents max number of upload slots
|
||||
public function setTorrentMaxUploadSlots($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_max_upload_slots", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets a torrents max upload speed
|
||||
public function setTorrentMaxUploadSpeed($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_max_upload_speed", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets the torrent to be moved when completed
|
||||
public function setTorrentMoveCompleted($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_move_completed", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets the path for the torrent to be moved when completed
|
||||
public function setTorrentMoveCompletedPath($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_move_completed_path", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets the torrent options for torrent_ids
|
||||
public function setTorrentOptions($torrentIds, $options)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_options", array($torrentIds, $options));
|
||||
}
|
||||
|
||||
//Sets a higher priority to the first and last pieces
|
||||
public function setTorrentPrioritizeFirstLast($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_prioritize_first_last", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets the torrent to be removed at ‘stop_ratio’
|
||||
public function setTorrentRemoveAtRatio($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_remove_at_ratio", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets the torrent to stop at ‘stop_ratio’
|
||||
public function setTorrentStopAtRatio($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_stop_at_ratio", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets the ratio when to stop a torrent if ‘stop_at_ratio’ is set
|
||||
public function setTorrentStopRatio($torrentId, $value)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_stop_ratio", array($torrentId, $value));
|
||||
}
|
||||
|
||||
//Sets a torrents tracker list. trackers will be [{“url”, “tier”}]
|
||||
public function setTorrentTrackers($torrentId, $trackers)
|
||||
{
|
||||
return $this->makeRequest("core.set_torrent_trackers", array($torrentId, $trackers));
|
||||
}
|
||||
|
||||
//Checks if the active port is open
|
||||
public function testListenPort()
|
||||
{
|
||||
return $this->makeRequest("core.test_listen_port", array());
|
||||
}
|
||||
|
||||
public function uploadPlugin($filename, $filedump)
|
||||
{
|
||||
return $this->makeRequest("core.upload_plugin", array($filename, $filedump));
|
||||
}
|
||||
|
||||
//Returns a list of the exported methods.
|
||||
public function getMethodList()
|
||||
{
|
||||
return $this->makeRequest("daemon.get_method_list", array());
|
||||
}
|
||||
|
||||
//Returns some info from the daemon.
|
||||
public function info()
|
||||
{
|
||||
return $this->makeRequest("daemon.info", array());
|
||||
}
|
||||
|
||||
public function shutdown(...$params)
|
||||
{
|
||||
return $this->makeRequest("daemon.shutdown", $params);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//web ui functions
|
||||
//
|
||||
//parsed from https://web.archive.org/web/20150423143401/http://deluge-torrent.org:80/docs/master/modules/ui/web/json_api.html#module-deluge.ui.web.json_api
|
||||
//
|
||||
/////////////////////////////
|
||||
//Parameters:
|
||||
//host (string) – the hostname
|
||||
//port (int) – the port
|
||||
//username (string) – the username to login as
|
||||
//password (string) – the password to login with
|
||||
public function addHost($host, $port, $username, $password)
|
||||
{
|
||||
return $this->makeRequest("web.add_host", array($host, $port, $username, $password));
|
||||
}
|
||||
|
||||
//Usage
|
||||
public function addTorrents($torrents)
|
||||
{
|
||||
return $this->makeRequest("web.add_torrents", array($torrents));
|
||||
}
|
||||
|
||||
public function connect($hostId)
|
||||
{
|
||||
return $this->makeRequest("web.connect", array($hostId));
|
||||
}
|
||||
|
||||
public function connected()
|
||||
{
|
||||
return $this->makeRequest("web.connected", array());
|
||||
}
|
||||
|
||||
public function deregisterEventListener($event)
|
||||
{
|
||||
return $this->makeRequest("web.deregister_event_listener", array($event));
|
||||
}
|
||||
|
||||
public function disconnect()
|
||||
{
|
||||
return $this->makeRequest("web.disconnect", array());
|
||||
}
|
||||
|
||||
public function downloadTorrentFromUrl($url, $cookie)
|
||||
{
|
||||
return $this->makeRequest("web.download_torrent_from_url", array($url, $cookie));
|
||||
}
|
||||
|
||||
/* in core
|
||||
public function getConfig() {
|
||||
return $this->makeRequest("web.get_config", array());
|
||||
}*/
|
||||
public function getEvents()
|
||||
{
|
||||
return $this->makeRequest("web.get_events", array());
|
||||
}
|
||||
|
||||
public function getHost($hostId)
|
||||
{
|
||||
return $this->makeRequest("web.get_host", array($hostId));
|
||||
}
|
||||
|
||||
public function getHostStatus($hostId)
|
||||
{
|
||||
return $this->makeRequest("web.get_host_status", array($hostId));
|
||||
}
|
||||
|
||||
public function getHosts()
|
||||
{
|
||||
return $this->makeRequest("web.get_hosts", array());
|
||||
}
|
||||
|
||||
public function getTorrentFiles($torrentId)
|
||||
{
|
||||
return $this->makeRequest("web.get_torrent_files", array($torrentId));
|
||||
}
|
||||
|
||||
public function getTorrentInfo($filename)
|
||||
{
|
||||
return $this->makeRequest("web.get_torrent_info", array($filename));
|
||||
}
|
||||
|
||||
public function registerEventListener($event)
|
||||
{
|
||||
return $this->makeRequest("web.register_event_listener", array($event));
|
||||
}
|
||||
|
||||
public function removeHost($connectionId)
|
||||
{
|
||||
return $this->makeRequest("web.remove_host", array($connectionId));
|
||||
}
|
||||
|
||||
/*in core
|
||||
public function setConfig($config) {
|
||||
return $this->makeRequest("web.set_config", array($config));
|
||||
}*/
|
||||
public function startDaemon($port)
|
||||
{
|
||||
return $this->makeRequest("web.start_daemon", array($port));
|
||||
}
|
||||
|
||||
public function stopDaemon($hostId)
|
||||
{
|
||||
return $this->makeRequest("web.stop_daemon", array($hostId));
|
||||
}
|
||||
|
||||
//Parameters:
|
||||
//keys (list) – the information about the torrents to gather
|
||||
//filterDict (dictionary) – the filters to apply when selecting torrents.
|
||||
public function updateUi($keys, $filterDict)
|
||||
{
|
||||
return $this->makeRequest("web.update_ui", array($keys, $filterDict));
|
||||
}
|
||||
|
||||
private function makeRequest($method, $params)
|
||||
{
|
||||
$post_data = array("id" => $this->request_id, "method" => $method, "params" => $params);
|
||||
curl_setopt($this->ch, CURLOPT_POSTFIELDS, json_encode($post_data));
|
||||
$result = curl_exec($this->ch);
|
||||
if ($result === false) {
|
||||
throw new Exception("Could not log in due to curl error (no. " . curl_errno($this->ch) . "): " . curl_error($this->ch));
|
||||
}
|
||||
$http_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
|
||||
if ($http_code != 200) {
|
||||
throw new Exception("Request for method $method returned http code $http_code");
|
||||
}
|
||||
$result = json_decode($result);
|
||||
if (!is_null($result->error)) {
|
||||
throw new Exception("Method request returned an error (no. " . $result->error->code . "): " . $result->error->message);
|
||||
}
|
||||
if ($result->id != $this->request_id) {
|
||||
throw new Exception("Response id did not match request id");
|
||||
}
|
||||
$this->request_id++;
|
||||
return $result->result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use Monolog\Handler\SlackWebhookHandler;
|
||||
use Nekonomokochan\PhpJsonLogger\Logger;
|
||||
use Nekonomokochan\PhpJsonLogger\LoggerBuilder;
|
||||
|
||||
class OrganizrLogger extends LoggerBuilder
|
||||
{
|
||||
public $isReady;
|
||||
/**
|
||||
* @var SlackWEbhookHandler
|
||||
*/
|
||||
private $slackWebhookHandler;
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function getReadyStatus(): bool
|
||||
{
|
||||
return $this->isReady;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param boolean $readyStatus
|
||||
*/
|
||||
public function setReadyStatus(bool $readyStatus)
|
||||
{
|
||||
$this->isReady = $readyStatus;
|
||||
}
|
||||
|
||||
public function build(): Logger
|
||||
{
|
||||
if (!$this->isReady) {
|
||||
$this->setChannel('Organizr');
|
||||
$this->setLogLevel(self::DEBUG);
|
||||
$this->setMaxFiles(1);
|
||||
}
|
||||
return new Logger($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SlackWebhookHandler
|
||||
*/
|
||||
public function getSlackWebhookHandler(): ?SlackWebhookHandler
|
||||
{
|
||||
return $this->slackWebhookHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SlackWebhookHandler $slackWebhookHandler
|
||||
*/
|
||||
public function setSlackWebhookHandler(SlackWebhookHandler $slackWebhookHandler)
|
||||
{
|
||||
$this->slackWebhookHandler = $slackWebhookHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?php
|
||||
/*
|
||||
* deprecated
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
class Ping
|
||||
{
|
||||
|
||||
private $host;
|
||||
private $ttl;
|
||||
private $timeout;
|
||||
private $port = 80;
|
||||
private $data = 'Ping';
|
||||
private $commandOutput;
|
||||
|
||||
/**
|
||||
* Called when the Ping object is created.
|
||||
*
|
||||
* @param string $host
|
||||
* The host to be pinged.
|
||||
* @param int $ttl
|
||||
* Time-to-live (TTL) (You may get a 'Time to live exceeded' error if this
|
||||
* value is set too low. The TTL value indicates the scope or range in which
|
||||
* a packet may be forwarded. By convention:
|
||||
* - 0 = same host
|
||||
* - 1 = same subnet
|
||||
* - 32 = same site
|
||||
* - 64 = same region
|
||||
* - 128 = same continent
|
||||
* - 255 = unrestricted
|
||||
* @param int $timeout
|
||||
* Timeout (in seconds) used for ping and fsockopen().
|
||||
* @throws \Exception if the host is not set.
|
||||
*/
|
||||
public function __construct($host, $ttl = 255, $timeout = 10)
|
||||
{
|
||||
if (!isset($host)) {
|
||||
throw new \Exception("Error: Host name not supplied.");
|
||||
}
|
||||
$this->host = $host;
|
||||
$this->ttl = $ttl;
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ttl (in hops).
|
||||
*
|
||||
* @param int $ttl
|
||||
* TTL in hops.
|
||||
*/
|
||||
public function setTtl($ttl)
|
||||
{
|
||||
$this->ttl = $ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ttl.
|
||||
*
|
||||
* @return int
|
||||
* The current ttl for Ping.
|
||||
*/
|
||||
public function getTtl()
|
||||
{
|
||||
return $this->ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timeout.
|
||||
*
|
||||
* @param int $timeout
|
||||
* Time to wait in seconds.
|
||||
*/
|
||||
public function setTimeout($timeout)
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timeout.
|
||||
*
|
||||
* @return int
|
||||
* Current timeout for Ping.
|
||||
*/
|
||||
public function getTimeout()
|
||||
{
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the host.
|
||||
*
|
||||
* @param string $host
|
||||
* Host name or IP address.
|
||||
*/
|
||||
public function setHost($host)
|
||||
{
|
||||
$this->host = $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the host.
|
||||
*
|
||||
* @return string
|
||||
* The current hostname for Ping.
|
||||
*/
|
||||
public function getHost()
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the port (only used for fsockopen method).
|
||||
*
|
||||
* Since regular pings use ICMP and don't need to worry about the concept of
|
||||
* 'ports', this is only used for the fsockopen method, which pings servers by
|
||||
* checking port 80 (by default).
|
||||
*
|
||||
* @param int $port
|
||||
* Port to use for fsockopen ping (defaults to 80 if not set).
|
||||
*/
|
||||
public function setPort($port)
|
||||
{
|
||||
$this->port = $port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the port (only used for fsockopen method).
|
||||
*
|
||||
* @return int
|
||||
* The port used by fsockopen pings.
|
||||
*/
|
||||
public function getPort()
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the command output when method=exec.
|
||||
* @return string
|
||||
*/
|
||||
public function getCommandOutput()
|
||||
{
|
||||
return $this->commandOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches an IP on command output and returns.
|
||||
* @return string
|
||||
*/
|
||||
public function getIpAddress()
|
||||
{
|
||||
$out = array();
|
||||
if (preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $this->commandOutput, $out)) {
|
||||
return $out[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping a host.
|
||||
*
|
||||
* @param string $method
|
||||
* Method to use when pinging:
|
||||
* - exec (default): Pings through the system ping command. Fast and
|
||||
* robust, but a security risk if you pass through user-submitted data.
|
||||
* - fsockopen: Pings a server on port 80.
|
||||
* - socket: Creates a RAW network socket. Only usable in some
|
||||
* environments, as creating a SOCK_RAW socket requires root privileges.
|
||||
*
|
||||
* @throws InvalidArgumentException if $method is not supported.
|
||||
*
|
||||
* @return mixed
|
||||
* Latency as integer, in ms, if host is reachable or FALSE if host is down.
|
||||
*/
|
||||
public function ping($method = 'exec')
|
||||
{
|
||||
$latency = false;
|
||||
switch ($method) {
|
||||
case 'exec':
|
||||
$latency = $this->pingExec();
|
||||
break;
|
||||
case 'fsockopen':
|
||||
$latency = $this->pingFsockopen();
|
||||
break;
|
||||
case 'socket':
|
||||
$latency = $this->pingSocket();
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException('Unsupported ping method.');
|
||||
}
|
||||
// Return the latency.
|
||||
return $latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* The exec method uses the possibly insecure exec() function, which passes
|
||||
* the input to the system. This is potentially VERY dangerous if you pass in
|
||||
* any user-submitted data. Be SURE you sanitize your inputs!
|
||||
*
|
||||
* @return int
|
||||
* Latency, in ms.
|
||||
*/
|
||||
private function pingExec()
|
||||
{
|
||||
$latency = false;
|
||||
$ttl = escapeshellcmd($this->ttl);
|
||||
$timeout = escapeshellcmd($this->timeout);
|
||||
$host = escapeshellcmd($this->host);
|
||||
// Exec string for Windows-based systems.
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||
// -n = number of pings; -i = ttl; -w = timeout (in milliseconds).
|
||||
$exec_string = 'ping -n 1 -i ' . $ttl . ' -w ' . ($timeout * 1000) . ' ' . $host;
|
||||
} // Exec string for Darwin based systems (OS X).
|
||||
else if (strtoupper(PHP_OS) === 'DARWIN') {
|
||||
// -n = numeric output; -c = number of pings; -m = ttl; -t = timeout.
|
||||
$exec_string = 'ping -n -c 1 -m ' . $ttl . ' -t ' . $timeout . ' ' . $host;
|
||||
} // Exec string for other UNIX-based systems (Linux).
|
||||
else {
|
||||
// -n = numeric output; -c = number of pings; -t = ttl; -W = timeout
|
||||
$exec_string = 'ping -n -c 1 -t ' . $ttl . ' -W ' . $timeout . ' ' . $host . ' 2>&1';
|
||||
}
|
||||
exec($exec_string, $output, $return);
|
||||
// Strip empty lines and reorder the indexes from 0 (to make results more
|
||||
// uniform across OS versions).
|
||||
$this->commandOutput = implode($output, '');
|
||||
$output = array_values(array_filter($output));
|
||||
// If the result line in the output is not empty, parse it.
|
||||
if (!empty($output[1])) {
|
||||
// Search for a 'time' value in the result line.
|
||||
$response = preg_match("/time(?:=|<)(?<time>[\.0-9]+)(?:|\s)ms/", $output[1], $matches);
|
||||
// If there's a result and it's greater than 0, return the latency.
|
||||
if ($response > 0 && isset($matches['time'])) {
|
||||
$latency = round($matches['time'], 2);
|
||||
}
|
||||
}
|
||||
return $latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* The fsockopen method simply tries to reach the host on a port. This method
|
||||
* is often the fastest, but not necessarily the most reliable. Even if a host
|
||||
* doesn't respond, fsockopen may still make a connection.
|
||||
*
|
||||
* @return int
|
||||
* Latency, in ms.
|
||||
*/
|
||||
private function pingFsockopen()
|
||||
{
|
||||
$start = microtime(true);
|
||||
// fsockopen prints a bunch of errors if a host is unreachable. Hide those
|
||||
// irrelevant errors and deal with the results instead.
|
||||
$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout);
|
||||
if (!$fp) {
|
||||
$latency = false;
|
||||
} else {
|
||||
$latency = microtime(true) - $start;
|
||||
$latency = round($latency * 1000, 2);
|
||||
}
|
||||
return $latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* The socket method uses raw network packet data to try sending an ICMP ping
|
||||
* packet to a server, then measures the response time. Using this method
|
||||
* requires the script to be run with root privileges, though, so this method
|
||||
* only works reliably on Windows systems and on Linux servers where the
|
||||
* script is not being run as a web user.
|
||||
*
|
||||
* @return int
|
||||
* Latency, in ms.
|
||||
*/
|
||||
private function pingSocket()
|
||||
{
|
||||
// Create a package.
|
||||
$type = "\x08";
|
||||
$code = "\x00";
|
||||
$checksum = "\x00\x00";
|
||||
$identifier = "\x00\x00";
|
||||
$seq_number = "\x00\x00";
|
||||
$package = $type . $code . $checksum . $identifier . $seq_number . $this->data;
|
||||
// Calculate the checksum.
|
||||
$checksum = $this->calculateChecksum($package);
|
||||
// Finalize the package.
|
||||
$package = $type . $code . $checksum . $identifier . $seq_number . $this->data;
|
||||
// Create a socket, connect to server, then read socket and calculate.
|
||||
if ($socket = socket_create(AF_INET, SOCK_RAW, 1)) {
|
||||
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array(
|
||||
'sec' => 10,
|
||||
'usec' => 0,
|
||||
));
|
||||
// Prevent errors from being printed when host is unreachable.
|
||||
@socket_connect($socket, $this->host, null);
|
||||
$start = microtime(true);
|
||||
// Send the package.
|
||||
@socket_send($socket, $package, strlen($package), 0);
|
||||
if (socket_read($socket, 255) !== false) {
|
||||
$latency = microtime(true) - $start;
|
||||
$latency = round($latency * 1000, 2);
|
||||
} else {
|
||||
$latency = false;
|
||||
}
|
||||
} else {
|
||||
$latency = false;
|
||||
}
|
||||
// Close the socket.
|
||||
socket_close($socket);
|
||||
return $latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a checksum.
|
||||
*
|
||||
* @param string $data
|
||||
* Data for which checksum will be calculated.
|
||||
*
|
||||
* @return string
|
||||
* Binary string checksum of $data.
|
||||
*/
|
||||
private function calculateChecksum($data)
|
||||
{
|
||||
if (strlen($data) % 2) {
|
||||
$data .= "\x00";
|
||||
}
|
||||
$bit = unpack('n*', $data);
|
||||
$sum = array_sum($bit);
|
||||
while ($sum >> 16) {
|
||||
$sum = ($sum >> 16) + ($sum & 0xffff);
|
||||
}
|
||||
return pack('n*', ~$sum);
|
||||
}
|
||||
}
|
||||
27
docker/test/tvtracker/config/www/organizr/api/composer.json
Normal file
27
docker/test/tvtracker/config/www/organizr/api/composer.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"require": {
|
||||
"dibi/dibi": "^4.2",
|
||||
"lcobucci/jwt": "^4.1",
|
||||
"composer/semver": "^1.4",
|
||||
"phpmailer/phpmailer": "^6.2",
|
||||
"rmccue/requests": "^1.7",
|
||||
"kryptonit3/sonarr": "1.0.6.1",
|
||||
"kryptonit3/couchpotato": "^1.0",
|
||||
"kryptonit3/sickrage": "^1.0",
|
||||
"pusher/pusher-php-server": "^4.0",
|
||||
"pragmarx/google2fa": "^3.0",
|
||||
"psr/log": "^1.1",
|
||||
"adldap2/adldap2": "^10.0",
|
||||
"slim/slim": "^4.0",
|
||||
"slim/psr7": "^1.1",
|
||||
"zircote/swagger-php": "^3.0",
|
||||
"bogstag/oauth2-trakt": "^1.0",
|
||||
"paquettg/php-html-parser": "^3.1",
|
||||
"nekonomokochan/php-json-logger": "^1.3",
|
||||
"bcremer/line-reader": "^1.1",
|
||||
"peppeocchi/php-cron-scheduler": "^4.0",
|
||||
"simshaun/recurr": "^5.0",
|
||||
"stripe/stripe-php": "^7.116",
|
||||
"ramsey/uuid": "^4.2"
|
||||
}
|
||||
}
|
||||
4155
docker/test/tvtracker/config/www/organizr/api/composer.lock
generated
Normal file
4155
docker/test/tvtracker/config/www/organizr/api/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
774
docker/test/tvtracker/config/www/organizr/api/config/default.php
Normal file
774
docker/test/tvtracker/config/www/organizr/api/config/default.php
Normal file
@@ -0,0 +1,774 @@
|
||||
<?php
|
||||
return [
|
||||
'driver' => 'sqlite3',
|
||||
'dbHost' => '',
|
||||
'dbUsername' => '',
|
||||
'dbPassword' => '',
|
||||
'branch' => 'v2-master',
|
||||
'authType' => 'internal',
|
||||
'authBackend' => '',
|
||||
'authBackendHost' => '',
|
||||
'authBackendHostPrefix' => '',
|
||||
'authBackendHostSuffix' => '',
|
||||
'ldapBindUsername' => '',
|
||||
'ldapBindPassword' => '',
|
||||
'ldapSSL' => false,
|
||||
'ldapTLS' => false,
|
||||
'authBaseDN' => '',
|
||||
'authBackendDomain' => '',
|
||||
'ldapType' => '1',
|
||||
'logo' => 'plugins/images/organizr/organizr-logo-h.png',
|
||||
'loginLogo' => 'plugins/images/organizr/organizr-logo-h.png',
|
||||
'loginWallpaper' => '',
|
||||
'title' => 'Organizr V2',
|
||||
'useLogo' => false,
|
||||
'useLogoLogin' => false,
|
||||
'headerColor' => '',
|
||||
'headerTextColor' => '',
|
||||
'sidebarColor' => '',
|
||||
'sidebarTextColor' => '',
|
||||
'accentColor' => '',
|
||||
'accentTextColor' => '',
|
||||
'buttonColor' => '',
|
||||
'buttonTextColor' => '',
|
||||
'buttonHoverColor' => '',
|
||||
'buttonTextHoverColor' => '',
|
||||
'alternateHomepageHeaders' => false,
|
||||
'lockScreen' => false,
|
||||
'theme' => 'Organizr',
|
||||
'style' => 'dark',
|
||||
'plexURL' => '',
|
||||
'plexTabURL' => '',
|
||||
'plexToken' => '',
|
||||
'plexTabName' => '',
|
||||
'plexAdmin' => '',
|
||||
'plexDisableCertCheck' => false,
|
||||
'plexUseCustomCertificate' => false,
|
||||
'embyTabURL' => '',
|
||||
'embyTabName' => '',
|
||||
'embyURL' => '',
|
||||
'embyToken' => '',
|
||||
'embyDisableCertCheck' => false,
|
||||
'embyUseCustomCertificate' => false,
|
||||
'jellyfinTabName' => '',
|
||||
'jellyfinURL' => '',
|
||||
'jellyfinToken' => '',
|
||||
'jellyfinSSOURL' => '',
|
||||
'jellyfinUseCustomCertificate' => false,
|
||||
'jellyfinDisableCertCheck' => false,
|
||||
'plexID' => '',
|
||||
'tautulliURL' => '',
|
||||
'ombiURL' => '',
|
||||
'ombiToken' => '',
|
||||
'ombiAlias' => false,
|
||||
'ombiFallbackUser' => '',
|
||||
'ombiFallbackPassword' => '',
|
||||
'ombiDisableCertCheck' => false,
|
||||
'ombiUseCustomCertificate' => false,
|
||||
'overseerrURL' => '',
|
||||
'overseerrToken' => '',
|
||||
'overseerrFallbackUser' => '',
|
||||
'overseerrFallbackPassword' => '',
|
||||
'overseerrDisableCertCheck' => false,
|
||||
'overseerrUseCustomCertificate' => false,
|
||||
'petioURL' => '',
|
||||
'petioToken' => '',
|
||||
'petioFallbackUser' => '',
|
||||
'petioFallbackPassword' => '',
|
||||
'ssoPlex' => false,
|
||||
'ssoOmbi' => false,
|
||||
'ssoTautulli' => false,
|
||||
'ssoTautulliAuth' => '4',
|
||||
'ssoJellyfin' => false,
|
||||
'ssoOverseerr' => false,
|
||||
'ssoPetio' => false,
|
||||
'ssoKomga' => false,
|
||||
'ssoKomgaAuth' => '4',
|
||||
'komgaURL' => '',
|
||||
'komgaFallbackUser' => '',
|
||||
'komgaFallbackPassword' => '',
|
||||
'komgaSSOMasterPassword' => '',
|
||||
'sonarrURL' => '',
|
||||
'sonarrUnmonitored' => false,
|
||||
'sonarrIcon' => true,
|
||||
'sonarrCalendarLink' => '',
|
||||
'sonarrFrameTarget' => '',
|
||||
'sonarrToken' => '',
|
||||
'sonarrSocksEnabled' => false,
|
||||
'sonarrSocksAuth' => '999',
|
||||
'sonarrDisableCertCheck' => false,
|
||||
'sonarrUseCustomCertificate' => false,
|
||||
'lidarrURL' => '',
|
||||
'lidarrToken' => '',
|
||||
'lidarrSocksEnabled' => false,
|
||||
'lidarrSocksAuth' => '999',
|
||||
'lidarrDisableCertCheck' => false,
|
||||
'lidarrUseCustomCertificate' => false,
|
||||
'lidarrIcon' => true,
|
||||
'lidarrCalendarLink' => '',
|
||||
'lidarrFrameTarget' => '',
|
||||
'radarrURL' => '',
|
||||
'radarrUnmonitored' => false,
|
||||
'radarrIcon' => true,
|
||||
'radarrCalendarLink' => '',
|
||||
'radarrFrameTarget' => '',
|
||||
'radarrPhysicalRelease' => true,
|
||||
'radarrDigitalRelease' => false,
|
||||
'radarrCinemaRelease' => false,
|
||||
'radarrToken' => '',
|
||||
'radarrSocksEnabled' => false,
|
||||
'radarrSocksAuth' => '999',
|
||||
'radarrDisableCertCheck' => false,
|
||||
'radarrUseCustomCertificate' => false,
|
||||
'couchpotatoURL' => '',
|
||||
'couchpotatoToken' => '',
|
||||
'couchpotatoDisableCertCheck' => false,
|
||||
'couchpotatoUseCustomCertificate' => false,
|
||||
'sickrageURL' => '',
|
||||
'sickrageToken' => '',
|
||||
'sickrageDisableCertCheck' => false,
|
||||
'sickrageUseCustomCertificate' => false,
|
||||
'jdownloaderURL' => '',
|
||||
'jdownloaderUsername' => '',
|
||||
'jdownloaderPassword' => '',
|
||||
'jdownloaderCombine' => false,
|
||||
'jdownloaderRefresh' => '60000',
|
||||
'jdownloaderUseCustomCertificate' => false,
|
||||
'jdownloaderDisableCertCheck' => false,
|
||||
'sabnzbdURL' => '',
|
||||
'sabnzbdToken' => '',
|
||||
'sabnzbdSocksEnabled' => false,
|
||||
'sabnzbdSocksAuth' => '999',
|
||||
'sabnzbdCombine' => false,
|
||||
'sabnzbdRefresh' => '60000',
|
||||
'sabnzbdDisableCertCheck' => false,
|
||||
'sabnzbdUseCustomCertificate' => false,
|
||||
'nzbgetURL' => '',
|
||||
'nzbgetUsername' => '',
|
||||
'nzbgetPassword' => '',
|
||||
'nzbgetSocksEnabled' => false,
|
||||
'nzbgetSocksAuth' => '999',
|
||||
'nzbgetCombine' => false,
|
||||
'nzbgetRefresh' => '60000',
|
||||
'nzbgetDisableCertCheck' => false,
|
||||
'nzbgetUseCustomCertificate' => true,
|
||||
'transmissionURL' => '',
|
||||
'transmissionUsername' => '',
|
||||
'transmissionPassword' => '',
|
||||
'transmissionHideSeeding' => false,
|
||||
'transmissionHideCompleted' => false,
|
||||
'transmissionCombine' => false,
|
||||
'transmissionRefresh' => '60000',
|
||||
'transmissionUseCustomCertificate' => false,
|
||||
'transmissionDisableCertCheck' => true,
|
||||
'delugeURL' => '',
|
||||
'delugePassword' => '',
|
||||
'delugeHideSeeding' => false,
|
||||
'delugeHideCompleted' => false,
|
||||
'delugeHideStatus' => true,
|
||||
'delugeCombine' => false,
|
||||
'delugeRefresh' => '60000',
|
||||
'delugeDisableCertCheck' => false,
|
||||
'delugeUseCustomCertificate' => false,
|
||||
'qBittorrentURL' => '',
|
||||
'qBittorrentUsername' => '',
|
||||
'qBittorrentPassword' => '',
|
||||
'qBittorrentHideSeeding' => false,
|
||||
'qBittorrentHideCompleted' => false,
|
||||
'qBittorrentSortOrder' => 'eta',
|
||||
'qBittorrentReverseSorting' => false,
|
||||
'qBittorrentCombine' => false,
|
||||
'qBittorrentApiVersion' => '1',
|
||||
'qBittorrentDisableCertCheck' => false,
|
||||
'qBittorrentUseCustomCertificate' => false,
|
||||
'qBittorrentRefresh' => '60000',
|
||||
'qBittorrentSocksEnabled' => false,
|
||||
'qBittorrentSocksAuth' => '999',
|
||||
'rTorrentURL' => '',
|
||||
'uTorrentCombine' => false,
|
||||
'uTorrentCookie' => '',
|
||||
'uTorrentDisableCertCheck' => false,
|
||||
'uTorrentHideCompleted' => false,
|
||||
'uTorrentHideSeeding' => false,
|
||||
'uTorrentPassword' => '',
|
||||
'uTorrentRefresh' => '60000',
|
||||
'uTorrentToken' => '',
|
||||
'uTorrentURL' => '',
|
||||
'uTorrentUseCustomCertificate' => false,
|
||||
'uTorrentUsername' => '',
|
||||
'rTorrentURLOverride' => '',
|
||||
'rTorrentUsername' => '',
|
||||
'rTorrentPassword' => '',
|
||||
'rTorrentHideSeeding' => false,
|
||||
'rTorrentHideCompleted' => false,
|
||||
'rTorrentSortOrder' => 'datea',
|
||||
'rTorrentReverseSorting' => false,
|
||||
'rTorrentCombine' => false,
|
||||
'rTorrentIgnoreLabel' => '',
|
||||
'rTorrentDisableCertCheck' => false,
|
||||
'rTorrentUseCustomCertificate' => false,
|
||||
'rTorrentLimit' => '200',
|
||||
'rTorrentRefresh' => '60000',
|
||||
'homepageJackettEnabled' => false,
|
||||
'homepageJackettAuth' => '1',
|
||||
'jackettURL' => '',
|
||||
'jackettToken' => '',
|
||||
'jackettUseCustomCertificate' => false,
|
||||
'jackettDisableCertCheck' => false,
|
||||
'homepageJackettBackholeDownload' => false,
|
||||
'homepageProwlarrBackholeDownload' => false,
|
||||
'homepageProwlarrEnabled' => false,
|
||||
'homepageProwlarrAuth' => '1',
|
||||
'ProwlarrURL' => '',
|
||||
'ProwlarrToken' => '',
|
||||
'ProwlarrUseCustomCertificate' => false,
|
||||
'ProwlarrDisableCertCheck' => false,
|
||||
'homepageProwlarrBackholeDownload' => false,
|
||||
'homepageCalendarEnabled' => false,
|
||||
'homepageCalendarAuth' => '4',
|
||||
'calendariCal' => '',
|
||||
'homepageCustomHTML01Enabled' => false,
|
||||
'homepageCustomHTML01Auth' => '1',
|
||||
'customHTML01' => '',
|
||||
'homepageCustomHTML02Enabled' => false,
|
||||
'homepageCustomHTML02Auth' => '1',
|
||||
'customHTML02' => '',
|
||||
'homepageCustomHTML03Enabled' => false,
|
||||
'homepageCustomHTML03Auth' => '1',
|
||||
'customHTML03' => '',
|
||||
'homepageCustomHTML04Enabled' => false,
|
||||
'homepageCustomHTML04Auth' => '1',
|
||||
'customHTML04' => '',
|
||||
'homepageCustomHTML05Enabled' => false,
|
||||
'homepageCustomHTML05Auth' => '1',
|
||||
'customHTML05' => '',
|
||||
'homepageCustomHTML06Enabled' => false,
|
||||
'homepageCustomHTML06Auth' => '1',
|
||||
'customHTML06' => '',
|
||||
'homepageCustomHTML07Enabled' => false,
|
||||
'homepageCustomHTML07Auth' => '1',
|
||||
'customHTML07' => '',
|
||||
'homepageCustomHTML08Enabled' => false,
|
||||
'homepageCustomHTML08Auth' => '1',
|
||||
'customHTML08' => '',
|
||||
'homepageDelugeEnabled' => false,
|
||||
'homepageDelugeAuth' => '1',
|
||||
'homepageJdownloaderEnabled' => false,
|
||||
'homepageJdownloaderAuth' => '1',
|
||||
'homepageSabnzbdEnabled' => false,
|
||||
'homepageSabnzbdAuth' => '1',
|
||||
'homepageSonarrEnabled' => false,
|
||||
'homepageSonarrAuth' => '1',
|
||||
'homepageSonarrQueueEnabled' => false,
|
||||
'homepageSonarrQueueAuth' => '1',
|
||||
'homepageSonarrQueueCombine' => false,
|
||||
'homepageSonarrQueueRefresh' => '60000',
|
||||
'homepageLidarrEnabled' => false,
|
||||
'homepageLidarrAuth' => '1',
|
||||
'homepageCouchpotatoEnabled' => false,
|
||||
'homepageCouchpotatoAuth' => '1',
|
||||
'homepageSickrageEnabled' => false,
|
||||
'homepageSickrageAuth' => '1',
|
||||
'homepageRadarrEnabled' => false,
|
||||
'homepageRadarrAuth' => '1',
|
||||
'homepageRadarrQueueEnabled' => false,
|
||||
'homepageRadarrQueueAuth' => '1',
|
||||
'homepageRadarrQueueCombine' => false,
|
||||
'homepageRadarrQueueRefresh' => '60000',
|
||||
'homepageTransmissionEnabled' => false,
|
||||
'homepageTransmissionAuth' => '1',
|
||||
'homepageqBittorrentEnabled' => false,
|
||||
'homepageqBittorrentAuth' => '1',
|
||||
'homepagerTorrentEnabled' => false,
|
||||
'homepagerTorrentAuth' => '1',
|
||||
'homepageuTorrentAuth' => '1',
|
||||
'homepageuTorrentEnabled' => false,
|
||||
'homepageNzbgetEnabled' => false,
|
||||
'homepageNzbgetAuth' => '1',
|
||||
'homepagePlexEnabled' => false,
|
||||
'homepagePlexAuth' => '1',
|
||||
'homepageEmbyEnabled' => false,
|
||||
'homepageEmbyAuth' => '1',
|
||||
'homepageJellyfinEnabled' => false,
|
||||
'homepageJellyfinAuth' => '1',
|
||||
'homepageOmbiEnabled' => false,
|
||||
'homepageOmbiAuth' => '1',
|
||||
'homepageOmbiRequestAuth' => '1',
|
||||
'homepageOverseerrEnabled' => false,
|
||||
'homepageOverseerrAuth' => '1',
|
||||
'homepageOverseerrRequestAuth' => '1',
|
||||
'homepageJellyfinInstead' => false,
|
||||
'ombiLimitUser' => false,
|
||||
'ombiRefresh' => '600000',
|
||||
'ombiTvDefault' => 'all',
|
||||
'overseerrLimitUser' => false,
|
||||
'overseerrRefresh' => '600000',
|
||||
'overseerrTvDefault' => 'all',
|
||||
'overseerrPrefer4K' => false,
|
||||
'homepageWeatherAndAirEnabled' => false,
|
||||
'homepageWeatherAndAirAuth' => '1',
|
||||
'homepageWeatherAndAirRefresh' => '3600000',
|
||||
'homepageWeatherAndAirLatitude' => '',
|
||||
'homepageWeatherAndAirLongitude' => '',
|
||||
'homepageWeatherAndAirUnits' => 'imperial',
|
||||
'homepageWeatherAndAirPollenEnabled' => false,
|
||||
'homepageWeatherAndAirAirQualityEnabled' => false,
|
||||
'homepageWeatherAndAirWeatherEnabled' => false,
|
||||
'homepageWeatherAndAirWeatherHeader' => '',
|
||||
'homepageWeatherAndAirWeatherHeaderToggle' => false,
|
||||
'homepageHealthChecksEnabled' => false,
|
||||
'healthChecksToken' => '',
|
||||
'healthChecksTags' => '',
|
||||
'homepageHealthChecksAuth' => '1',
|
||||
'homepageHealthChecksShowDesc' => false,
|
||||
'homepageHealthChecksShowTags' => false,
|
||||
'healthChecksDisableCertCheck' => false,
|
||||
'healthChecksUseCustomCertificate' => false,
|
||||
'homepageDonateEnabled' => false,
|
||||
'homepageDonateAuth' => '999',
|
||||
'homepageDonatePublicToken' => '',
|
||||
'homepageDonateSecretToken' => '',
|
||||
'homepageDonateProductID' => '',
|
||||
'homepageDonateCustomizeHeading' => 'Donate Please',
|
||||
'homepageDonateCustomizeDescription' => 'Hi! Help me out with a donation...',
|
||||
'homepageDonateMinimum' => '100',
|
||||
'homepageDonateShowUserHistory' => false,
|
||||
'homepageOrdercustomhtml01' => '1',
|
||||
'homepageOrdercustomhtml02' => '2',
|
||||
'homepageOrdertransmission' => '3',
|
||||
'homepageOrderqBittorrent' => '4',
|
||||
'homepageOrderdeluge' => '5',
|
||||
'homepageOrdernzbget' => '6',
|
||||
'homepageOrdersabnzbd' => '7',
|
||||
'homepageOrderplexnowplaying' => '8',
|
||||
'homepageOrderplexrecent' => '9',
|
||||
'homepageOrderplexplaylist' => '10',
|
||||
'homepageOrderembynowplaying' => '11',
|
||||
'homepageOrderembyrecent' => '12',
|
||||
'homepageOrderombi' => '13',
|
||||
'homepageOrdercalendar' => '14',
|
||||
'homepageOrderrTorrent' => '15',
|
||||
'homepageOrderdownloader' => '16',
|
||||
'homepageOrderhealthchecks' => '17',
|
||||
'homepageOrderjdownloader' => '18',
|
||||
'homepageOrderunifi' => '19',
|
||||
'homepageOrderPihole' => '20',
|
||||
'homepageOrdertautulli' => '21',
|
||||
'homepageOrderMonitorr' => '22',
|
||||
'homepageOrderWeatherAndAir' => '23',
|
||||
'homepageOrderSpeedtest' => '24',
|
||||
'homepageOrderNetdata' => '25',
|
||||
'homepageOrderSonarrQueue' => '26',
|
||||
'homepageOrderRadarrQueue' => '27',
|
||||
'homepageOrderOctoprint' => '28',
|
||||
'homepageOrderjellyfinnowplaying' => '29',
|
||||
'homepageOrderjellyfinrecent' => '30',
|
||||
'homepageOrderJackett' => '31',
|
||||
'homepageOrdercustomhtml03' => '32',
|
||||
'homepageOrdercustomhtml04' => '33',
|
||||
'homepageOrdercustomhtml05' => '34',
|
||||
'homepageOrdercustomhtml06' => '35',
|
||||
'homepageOrdercustomhtml07' => '36',
|
||||
'homepageOrdercustomhtml08' => '37',
|
||||
'homepageOrderuTorrent' => '38',
|
||||
'homepageOrderoverseerr' => '39',
|
||||
'homepageOrderBookmarks' => '40',
|
||||
'homepageOrderDonate' => '41',
|
||||
'homepageOrderAdguard' => '42',
|
||||
'homepageOrderProwlarr' => '43',
|
||||
'homepageOrderUptimeKuma' => '44',
|
||||
'homepageOrderPromPage' => '45',
|
||||
'homepageOrderEmbyLiveTVTracker' => '46',
|
||||
'homepageOrderUserWatchStats' => '47',
|
||||
'homepageOrderJellyStat' => '48',
|
||||
'homepageEmbyLiveTVTrackerEnabled' => false,
|
||||
'homepageEmbyLiveTVTrackerAuth' => '1',
|
||||
'homepageEmbyLiveTVTrackerRefresh' => '5',
|
||||
'homepageEmbyLiveTVTrackerDaysShown' => '7',
|
||||
'homepageEmbyLiveTVTrackerCompactView' => false,
|
||||
'homepageEmbyLiveTVTrackerShowDuration' => true,
|
||||
'homepageEmbyLiveTVTrackerShowSeriesInfo' => true,
|
||||
'homepageEmbyLiveTVTrackerShowUserInfo' => false,
|
||||
'homepageEmbyLiveTVTrackerMaxItems' => '10',
|
||||
'homepageEmbyLiveTVTrackerShowCompleted' => true,
|
||||
'homepageEmbyLiveTVTrackerMaxCompletedItems' => '5',
|
||||
'homepageUserWatchStatsEnabled' => false,
|
||||
'homepageUserWatchStatsAuth' => '1',
|
||||
'homepageUserWatchStatsRefresh' => '30',
|
||||
'homepageUserWatchStatsService' => 'plex',
|
||||
'homepageUserWatchStatsURL' => '',
|
||||
'homepageUserWatchStatsToken' => '',
|
||||
'homepageUserWatchStatsDisableCertCheck' => false,
|
||||
'homepageUserWatchStatsUseCustomCertificate' => false,
|
||||
'homepageUserWatchStatsDays' => '30',
|
||||
'homepageUserWatchStatsCompactView' => false,
|
||||
'homepageUserWatchStatsShowTopUsers' => true,
|
||||
'homepageUserWatchStatsShowMostWatched' => true,
|
||||
'homepageUserWatchStatsShowRecentActivity' => true,
|
||||
'homepageUserWatchStatsMaxItems' => '10',
|
||||
'homepageUserWatchStatsHeader' => 'User Watch Statistics',
|
||||
'homepageUserWatchStatsHeaderToggle' => true,
|
||||
'homepageJellyStatEnabled' => false,
|
||||
'homepageJellyStatAuth' => '1',
|
||||
'homepageJellyStatDisplayMode' => 'native',
|
||||
'jellyStatURL' => '',
|
||||
'jellyStatInternalURL' => '',
|
||||
'jellyStatApikey' => '',
|
||||
'jellyStatDisableCertCheck' => false,
|
||||
'jellyStatUseCustomCertificate' => false,
|
||||
'homepageJellyStatRefresh' => '5',
|
||||
'homepageJellyStatDays' => '30',
|
||||
'homepageJellyStatShowLibraries' => true,
|
||||
'homepageJellyStatShowUsers' => true,
|
||||
'homepageJellyStatShowMostWatched' => true,
|
||||
'homepageJellyStatShowRecentActivity' => true,
|
||||
'homepageJellyStatMaxItems' => '10',
|
||||
'homepageJellyStatShowMostWatchedMovies' => true,
|
||||
'homepageJellyStatShowMostWatchedShows' => true,
|
||||
'homepageJellyStatShowMostListenedMusic' => true,
|
||||
'homepageJellyStatMostWatchedCount' => '10',
|
||||
'homepageJellyStatIframeHeight' => '800',
|
||||
'homepageJellyStatIframeScrolling' => true,
|
||||
'homepageShowStreamNames' => false,
|
||||
'homepageShowStreamNamesAuth' => '1',
|
||||
'homepageShowStreamNamesWithoutIp' => false,
|
||||
'homepageShowStreamNamesWithoutIpAuth' => '1',
|
||||
'homepageUseCustomStreamNames' => false,
|
||||
'homepageCustomStreamNames' => '',
|
||||
'homepageStreamRefresh' => '60000',
|
||||
'homepageRecentRefresh' => '60000',
|
||||
'homepageDownloadRefresh' => '60000',
|
||||
'homepageHealthChecksRefresh' => '60000',
|
||||
'homepagePlexStreams' => false,
|
||||
'homepagePlexStreamsAuth' => '1',
|
||||
'homepagePlexStreamsExclude' => '',
|
||||
'homepagePlexRecent' => false,
|
||||
'homepagePlexRecentExclude' => '',
|
||||
'homepageRecentLimit' => '50',
|
||||
'homepagePlexRecentAuth' => '1',
|
||||
'homepagePlexPlaylist' => false,
|
||||
'homepagePlexPlaylistAuth' => '1',
|
||||
'homepagePlexSearchExclude' => '',
|
||||
'homepageEmbyStreams' => false,
|
||||
'homepageEmbyStreamsAuth' => '1',
|
||||
'homepageEmbyRecent' => false,
|
||||
'homepageEmbyRecentAuth' => '1',
|
||||
'homepageEmbyLink' => ' http://app.emby.media/#!/item/item.html?id={id}&serverId={serverId}',
|
||||
'homepageJellyfinStreams' => false,
|
||||
'homepageJellyStreamsAuth' => '1',
|
||||
'homepageJellyfinRecent' => false,
|
||||
'homepageJellyfinRecentAuth' => '1',
|
||||
'homepageJellyfinLink' => 'http://hostname:port/jellyfin/web/index.html#!/details?id={id}&serverId={serverId}',
|
||||
'homepageBookmarksAuth' => '1',
|
||||
'homepageBookmarksEnabled' => false,
|
||||
'homepageBookmarksRefresh' => '3600000',
|
||||
'calendarDefault' => 'month',
|
||||
'calendarFirstDay' => '1',
|
||||
'calendarStart' => '14',
|
||||
'calendarEnd' => '14',
|
||||
'calendarRefresh' => '60000',
|
||||
'calendarTimeFormat' => 'h(:mm)t',
|
||||
'calendarLimit' => '1000',
|
||||
'calendarLocale' => 'en',
|
||||
'customCss' => '',
|
||||
'customThemeCss' => '',
|
||||
'mediaSearch' => false,
|
||||
'mediaSearchType' => '',
|
||||
'mediaSearchAuth' => '1',
|
||||
'registrationPassword' => '',
|
||||
'hideRegistration' => false,
|
||||
'favIcon' => '',
|
||||
'pingAuth' => '1',
|
||||
'pingAuthMessage' => '1',
|
||||
'adminPingRefresh' => '60000',
|
||||
'otherPingRefresh' => '600000',
|
||||
'pingOfflineSound' => 'plugins/sounds/default/open-ended.mp3',
|
||||
'pingOnlineSound' => 'plugins/sounds/default/awareness.mp3',
|
||||
'pingMs' => false,
|
||||
'pingAuthMs' => '1',
|
||||
'notificationBackbone' => 'izi',
|
||||
'notificationPosition' => 'br',
|
||||
'lockoutSystem' => false,
|
||||
'lockoutTimeout' => '60',
|
||||
'lockoutMaxAuth' => '0',
|
||||
'lockoutMinAuth' => '1',
|
||||
'themeInstalled' => '',
|
||||
'themeVersion' => '',
|
||||
'installedPlugins' => [],
|
||||
'installedThemes' => '',
|
||||
'authDebug' => false,
|
||||
'customJava' => '',
|
||||
'customThemeJava' => '',
|
||||
'minimalLoginScreen' => false,
|
||||
'unsortedTabs' => 'top',
|
||||
'cacheImageSize' => '2',
|
||||
'plexoAuth' => false,
|
||||
'statusSounds' => false,
|
||||
'rememberMeDays' => '7',
|
||||
'rememberMe' => true,
|
||||
'plexStrictFriends' => true,
|
||||
'ignoreTFAIfPlexOAuth' => false,
|
||||
'debugAreaAuth' => '1',
|
||||
'commit' => 'n/a',
|
||||
'ombiLimit' => '50',
|
||||
'overseerrLimit' => '50',
|
||||
'localIPFrom' => '',
|
||||
'localIPTo' => '',
|
||||
'localIPList' => '',
|
||||
'sandbox' => 'allow-presentation,allow-forms,allow-same-origin,allow-pointer-lock,allow-scripts,allow-popups,allow-modals,allow-top-navigation,allow-downloads,allow-orientation-lock,allow-popups-to-escape-sandbox,allow-top-navigation-by-user-activation',
|
||||
'iframeAllow' => 'fullscreen,autoplay,clipboard-read,clipboard-write,camera,microphone,speaker-selection,display-capture,web-share,encrypted-media,picture-in-picture',
|
||||
'description' => 'Organizr - Accept no others',
|
||||
'debugErrors' => false,
|
||||
'healthChecksURL' => 'https://healthchecks.io/api/v1/checks/',
|
||||
'gaTrackingID' => '',
|
||||
'loginAttempts' => '5',
|
||||
'loginLockout' => '60000',
|
||||
'ombiDefaultFilterAvailable' => true,
|
||||
'ombiDefaultFilterUnavailable' => true,
|
||||
'ombiDefaultFilterApproved' => true,
|
||||
'ombiDefaultFilterUnapproved' => true,
|
||||
'ombiDefaultFilterDenied' => true,
|
||||
'overseerrDefaultFilterAvailable' => true,
|
||||
'overseerrDefaultFilterUnavailable' => true,
|
||||
'overseerrDefaultFilterApproved' => true,
|
||||
'overseerrDefaultFilterUnapproved' => true,
|
||||
'overseerrDefaultFilterDenied' => true,
|
||||
'selfSignedCert' => '',
|
||||
'homepagePlexRecentlyAddedMethod' => 'legacy',
|
||||
'authProxyEnabled' => false,
|
||||
'authProxyHeaderName' => '',
|
||||
'authProxyHeaderNameEmail' => '',
|
||||
'authProxyWhitelist' => '',
|
||||
'authProxyOverrideLogout' => false,
|
||||
'authProxyLogoutURL' => '',
|
||||
'ignoreTFALocal' => false,
|
||||
'unifiURL' => '',
|
||||
'unifiUsername' => '',
|
||||
'unifiPassword' => '',
|
||||
'unifiSiteName' => '',
|
||||
'unifiCookie' => '',
|
||||
'unifiUseCustomCertificate' => false,
|
||||
'unifiDisableCertCheck' => true,
|
||||
'homepageUnifiEnabled' => false,
|
||||
'homepageUnifiAuth' => '1',
|
||||
'homepageUnifiRefresh' => '600000',
|
||||
'youtubeAPI' => '',
|
||||
'wanDomain' => '',
|
||||
'localAddress' => '',
|
||||
'enableLocalAddressForward' => false,
|
||||
'performanceDisableIconDropdown' => false,
|
||||
'performanceDisableImageDropdown' => false,
|
||||
'traefikAuthEnable' => false,
|
||||
'traefikDomainOverride' => '',
|
||||
'homepageTautulliEnabled' => false,
|
||||
'homepageTautulliAuth' => '1',
|
||||
'homepageTautulliLibraryAuth' => '1',
|
||||
'homepageTautulliLibraryStatsExclude' => '',
|
||||
'homepageTautulliViewsAuth' => '1',
|
||||
'homepageTautulliMiscAuth' => '1',
|
||||
'homepageTautulliViewingStatsExclude' => '',
|
||||
'homepageTautulliRefresh' => '60000',
|
||||
'tautulliApikey' => '',
|
||||
'tautulliLibraries' => true,
|
||||
'tautulliTopMovies' => true,
|
||||
'tautulliTopTV' => true,
|
||||
'tautulliTopUsers' => true,
|
||||
'tautulliTopPlatforms' => true,
|
||||
'tautulliPopularMovies' => true,
|
||||
'tautulliPopularTV' => true,
|
||||
'tautulliHeader' => 'Tautulli',
|
||||
'tautulliHeaderToggle' => true,
|
||||
'tautulliFriendlyName' => true,
|
||||
'tautulliSocksEnabled' => false,
|
||||
'tautulliSocksAuth' => '999',
|
||||
'tautulliUseCustomCertificate' => false,
|
||||
'tautulliDisableCertCheck' => false,
|
||||
'homepagePiholeEnabled' => false,
|
||||
'homepagePiholeAuth' => '1',
|
||||
'homepagePiholeRefresh' => '10000',
|
||||
'homepagePiholeCombine' => false,
|
||||
'piholeHeaderToggle' => true,
|
||||
'piholeURL' => '',
|
||||
'piholeToken' => '',
|
||||
'homepageAdGuardEnabled' => false,
|
||||
'homepageAdGuardAuth' => '1',
|
||||
'homepageAdGuardRefresh' => '10000',
|
||||
'homepageAdGuardCombine' => false,
|
||||
'adguardQueriesToggle' => true,
|
||||
'adguardQueriesBlockedToggle' => true,
|
||||
'adguardPercentToggle' => true,
|
||||
'adguardProcessingToggle' => true,
|
||||
'adguardDomainListToggle' => false,
|
||||
'adguardHeaderToggle' => true,
|
||||
'adguardURL' => '',
|
||||
'homepageMonitorrEnabled' => false,
|
||||
'homepageMonitorrAuth' => '1',
|
||||
'homepageMonitorrRefresh' => '60000',
|
||||
'monitorrURL' => '',
|
||||
'monitorrHeaderToggle' => true,
|
||||
'monitorrHeader' => 'Monitorr',
|
||||
'monitorrCompact' => false,
|
||||
'monitorrDisableCertCheck' => false,
|
||||
'monitorrUseCustomCertificate' => false,
|
||||
'homepageSpeedtestEnabled' => false,
|
||||
'homepageSpeedtestAuth' => '1',
|
||||
'homepageSpeedtestRefresh' => '1800000',
|
||||
'speedtestURL' => '',
|
||||
'speedtestHeaderToggle' => true,
|
||||
'speedtestHeader' => 'Speedtest',
|
||||
'speedtestDisableCertCheck' => false,
|
||||
'speedtestUseCustomCertificate' => false,
|
||||
'homepageNetdataEnabled' => false,
|
||||
'homepageNetdataRefresh' => '10000',
|
||||
'homepageNetdataAuth' => '1',
|
||||
'netdataDisableCertCheck' => false,
|
||||
'netdataUseCustomCertificate' => false,
|
||||
'netdataURL' => '',
|
||||
'netdata1Title' => '',
|
||||
'netdata1Chart' => '',
|
||||
'netdata1Data' => '',
|
||||
'netdata1Colour' => '',
|
||||
'netdata1Size' => '',
|
||||
'netdata1sm' => true,
|
||||
'netdata1md' => true,
|
||||
'netdata1lg' => true,
|
||||
'netdata2Title' => '',
|
||||
'netdata2Chart' => '',
|
||||
'netdata2Data' => '',
|
||||
'netdata2Colour' => '',
|
||||
'netdata2Size' => '',
|
||||
'netdata2sm' => true,
|
||||
'netdata2md' => true,
|
||||
'netdata2lg' => true,
|
||||
'netdata3Title' => '',
|
||||
'netdata3Chart' => '',
|
||||
'netdata3Data' => '',
|
||||
'netdata3Colour' => '',
|
||||
'netdata3Size' => '',
|
||||
'netdata3sm' => true,
|
||||
'netdata3md' => true,
|
||||
'netdata3lg' => true,
|
||||
'netdata4Title' => '',
|
||||
'netdata4Chart' => '',
|
||||
'netdata4Data' => '',
|
||||
'netdata4Colour' => '',
|
||||
'netdata4Size' => '',
|
||||
'netdata4sm' => true,
|
||||
'netdata4md' => true,
|
||||
'netdata4lg' => true,
|
||||
'netdata5Title' => '',
|
||||
'netdata5Chart' => '',
|
||||
'netdata5Data' => '',
|
||||
'netdata5Colour' => '',
|
||||
'netdata5Size' => '',
|
||||
'netdata5sm' => true,
|
||||
'netdata5md' => true,
|
||||
'netdata5lg' => true,
|
||||
'netdata6Title' => '',
|
||||
'netdata6Chart' => '',
|
||||
'netdata6Data' => '',
|
||||
'netdata6Colour' => '',
|
||||
'netdata6Size' => '',
|
||||
'netdata6sm' => true,
|
||||
'netdata6md' => true,
|
||||
'netdata6lg' => true,
|
||||
'netdata7Title' => '',
|
||||
'netdata7Chart' => '',
|
||||
'netdata7Data' => '',
|
||||
'netdata7Colour' => '',
|
||||
'netdata7Size' => '',
|
||||
'netdata7sm' => true,
|
||||
'netdata7md' => true,
|
||||
'netdata7lg' => true,
|
||||
'netdata1Enabled' => false,
|
||||
'netdata2Enabled' => false,
|
||||
'netdata3Enabled' => false,
|
||||
'netdata4Enabled' => false,
|
||||
'netdata5Enabled' => false,
|
||||
'netdata6Enabled' => false,
|
||||
'netdata7Enabled' => false,
|
||||
'netdataCustom' => '{}',
|
||||
'homepageOctoprintEnabled' => false,
|
||||
'homepageOctoprintAuth' => '1',
|
||||
'homepageOctoprintRefresh' => '10000',
|
||||
'octoprintURL' => '',
|
||||
'octoprintToken' => '',
|
||||
'octoprintHeaderToggle' => true,
|
||||
'octoprintHeader' => 'Octoprint',
|
||||
'octoprintDisableCertCheck' => false,
|
||||
'octoprintUseCustomCertificate' => false,
|
||||
'githubMenuLink' => true,
|
||||
'organizrSupportMenuLink' => true,
|
||||
'organizrDocsMenuLink' => true,
|
||||
'organizrSignoutMenuLink' => true,
|
||||
'organizrFeatureRequestLink' => true,
|
||||
'breezometerToken' => 'd95ab607392d4fa5bf64bb26a5cb2a06',
|
||||
'customForgotPassText' => '',
|
||||
'disableRecoverPass' => false,
|
||||
'expandCategoriesByDefault' => false,
|
||||
'ignoredNewsIds' => [],
|
||||
'homepageTraktEnabled' => false,
|
||||
'homepageTraktAuth' => '1',
|
||||
'calendarStartTrakt' => '14',
|
||||
'calendarEndTrakt' => '14',
|
||||
'traktClientId' => '',
|
||||
'traktClientSecret' => '',
|
||||
'traktAccessToken' => '',
|
||||
'traktAccessTokenExpires' => '',
|
||||
'traktRefreshToken' => '',
|
||||
'autoCollapseCategories' => false,
|
||||
'autoExpandNavBar' => true,
|
||||
'defaultSettingsTab' => '5',
|
||||
'blacklisted' => '',
|
||||
'blacklistedMessage' => 'You have been blacklisted from this site.',
|
||||
'defaultRequestService' => 'ombi',
|
||||
'easterEggs' => true,
|
||||
'allowCollapsableSideMenu' => false,
|
||||
'sideMenuCollapsed' => false,
|
||||
'collapseSideMenuOnClick' => false,
|
||||
'logLevel' => 'INFO',
|
||||
'maxLogFiles' => '7',
|
||||
'logLiveUpdateRefresh' => '5000',
|
||||
'logPageSize' => '50',
|
||||
'includeDatabaseQueriesInDebug' => false,
|
||||
'externalPluginMarketplaceRepos' => '',
|
||||
'externalThemeMarketplaceRepos' => '',
|
||||
'githubAccessToken' => '',
|
||||
'checkForPluginUpdate' => true,
|
||||
'checkForThemeUpdate' => true,
|
||||
'autoUpdateCronEnabled' => false,
|
||||
'autoUpdateCronSchedule' => '@weekly',
|
||||
'autoBackupCronEnabled' => false,
|
||||
'autoBackupCronSchedule' => '@weekly',
|
||||
'keepBackupsCountCron' => '20',
|
||||
'backupLocation' => '',
|
||||
'useRandomMediaImage' => false,
|
||||
'sendLogsToSlack' => false,
|
||||
'slackLogLevel' => 'WARNING',
|
||||
'slackLogWebhook' => '',
|
||||
'slackLogWebHookChannel' => '',
|
||||
'matchUserAgents' => false,
|
||||
'matchUserIP' => false,
|
||||
'disableHomepageModals' => false,
|
||||
'homepageUptimeKumaEnabled' => false,
|
||||
'homepageUptimeKumaAuth' => '1',
|
||||
'uptimeKumaURL' => '',
|
||||
'uptimeKumaToken' => '',
|
||||
'homepageUptimeKumaRefresh' => '60000',
|
||||
'homepageUptimeKumaHeader' => 'Uptime Kuma',
|
||||
'homepageUptimeKumaHeaderToggle' => true,
|
||||
'homepageUptimeKumaCompact' => true,
|
||||
'homepageUptimeKumaShowLatency' => true,
|
||||
'homepagePromPageEnabled' => false,
|
||||
'promPageURL' => '',
|
||||
'homepagePromPageRefresh' => '60000',
|
||||
'homepagePromPageHeader' => 'Status Page',
|
||||
'homepagePromPageHeaderToggle' => true,
|
||||
'homepagePromPageCompact' => true,
|
||||
'homepagePromPageShowUptime' => true,
|
||||
'checkForUpdate' => true,
|
||||
'socksDebug' => false,
|
||||
'maxSocksDebugSize' => 100,
|
||||
'bypassLoginForLocal' => false,
|
||||
'localLoginUserId' => "1"
|
||||
];
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 901 KiB |
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"response": {
|
||||
"result": "success",
|
||||
"message": null,
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tv",
|
||||
"title": "Ted Lasso",
|
||||
"secondaryTitle": "2020",
|
||||
"summary": "Ted Lasso, an American football coach, moves to England when he\u2019s hired to manage a soccer team\u2014despite having no experience. With cynical players and a doubtful town, will he get them to see the Ted Lasso Way?",
|
||||
"ratingKey": "718958",
|
||||
"thumb": "/library/metadata/718958/thumb/1630643017",
|
||||
"key": "718958-list",
|
||||
"nowPlayingThumb": "/library/metadata/718958/art/1630643017",
|
||||
"nowPlayingKey": "718958-np",
|
||||
"nowPlayingTitle": "Ted Lasso",
|
||||
"nowPlayingBottom": "2020",
|
||||
"metadataKey": "718958",
|
||||
"originalType": {
|
||||
"0": "show"
|
||||
},
|
||||
"uid": "718958",
|
||||
"elapsed": null,
|
||||
"duration": 1800000,
|
||||
"addedAt": 1597470170,
|
||||
"watched": 0,
|
||||
"transcoded": "",
|
||||
"stream": "",
|
||||
"id": "",
|
||||
"session": "",
|
||||
"bandwidth": "",
|
||||
"bandwidthType": "",
|
||||
"sessionType": "Direct Playing",
|
||||
"state": "play",
|
||||
"user": "",
|
||||
"userThumb": "",
|
||||
"userAddress": "",
|
||||
"address": "https://demo.organizr.app/plex/web/index.html#!/server/fake/details?key=/library/metadata/718958",
|
||||
"nowPlayingOriginalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/718958/art/1630643017&height=675&width=1200&key=718958-np$R1TLZH1BYF",
|
||||
"originalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/718958/thumb/1630643017&height=300&width=200&key=718958-list$9T6812YERV",
|
||||
"openTab": true,
|
||||
"tabName": "Plex (Watch me)",
|
||||
"userStream": {
|
||||
"platform": "",
|
||||
"product": "",
|
||||
"device": "",
|
||||
"stream": "",
|
||||
"videoResolution": "",
|
||||
"throttled": false,
|
||||
"sourceVideoCodec": "",
|
||||
"videoCodec": "",
|
||||
"audioCodec": "",
|
||||
"sourceAudioCodec": "",
|
||||
"videoDecision": "Direct Play",
|
||||
"audioDecision": "Direct Play",
|
||||
"container": "",
|
||||
"audioChannels": ""
|
||||
},
|
||||
"metadata": {
|
||||
"guid": "plex://show/5e204251072b9e003ca5c140",
|
||||
"summary": "Ted Lasso, an American football coach, moves to England when he\u2019s hired to manage a soccer team\u2014despite having no experience. With cynical players and a doubtful town, will he get them to see the Ted Lasso Way?",
|
||||
"rating": "",
|
||||
"duration": "1800000",
|
||||
"originallyAvailableAt": "2020-08-14",
|
||||
"year": "2020",
|
||||
"studio": "Doozer",
|
||||
"tagline": "Out of His League",
|
||||
"genres": [
|
||||
"Comedy",
|
||||
"Sport",
|
||||
"Drama"
|
||||
],
|
||||
"actors": [
|
||||
{
|
||||
"name": "Jason Sudeikis",
|
||||
"role": "Ted Lasso",
|
||||
"thumb": "https://metadata-static.plex.tv/b/people/be1b09bec843d46ad76743326ab0fbd1.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Hannah Waddingham",
|
||||
"role": "Rebecca Welton",
|
||||
"thumb": "https://metadata-static.plex.tv/0/people/0a2f43fc49d213f651c0d897d15ebb4e.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Juno Temple",
|
||||
"role": "Keeley Jones",
|
||||
"thumb": "https://metadata-static.plex.tv/4/people/486961ff94e5cb952cf3addb2f8aaf4a.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Swift",
|
||||
"role": "Leslie Higgins",
|
||||
"thumb": "https://metadata-static.plex.tv/people/5d7768253c3c2a001fbca9d2.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Phil Dunster",
|
||||
"role": "Jamie Tartt",
|
||||
"thumb": "https://metadata-static.plex.tv/2/people/2dad53cf273295fd29d7d089bb0373ef.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Brett Goldstein",
|
||||
"role": "Roy Kent",
|
||||
"thumb": "https://metadata-static.plex.tv/people/5d77682bf54112001f5bc4d3.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Brendan Hunt",
|
||||
"role": "Coach Beard",
|
||||
"thumb": "https://metadata-static.plex.tv/people/5d77695e9ab54400214f0e66.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Nick Mohammed",
|
||||
"role": "Nathan Shelley",
|
||||
"thumb": "https://metadata-static.plex.tv/c/people/c9b9b5f951dfa5cff88e98e3c8837df4.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"nowPlayingImageURL": "plugins/images/cache/718958-np.jpg",
|
||||
"imageURL": "plugins/images/cache/718958-list.jpg"
|
||||
}
|
||||
],
|
||||
"plexID": "fake",
|
||||
"showNames": true,
|
||||
"group": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
{
|
||||
"response": {
|
||||
"result": "success",
|
||||
"message": null,
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"type": "tv",
|
||||
"title": "Ted Lasso",
|
||||
"secondaryTitle": "Season 2 - Episode 9",
|
||||
"summary": "Beard After Hours",
|
||||
"ratingKey": "738933",
|
||||
"thumb": "/library/metadata/738933/thumb/1631895318",
|
||||
"key": "743046-list",
|
||||
"nowPlayingThumb": "/library/metadata/718958/art/1630643017",
|
||||
"nowPlayingKey": "718958-np",
|
||||
"nowPlayingTitle": "Ted Lasso - Beard After Hours",
|
||||
"nowPlayingBottom": "S2 \u00b7 E9",
|
||||
"metadataKey": "718958",
|
||||
"originalType": {
|
||||
"0": "episode"
|
||||
},
|
||||
"uid": "743046",
|
||||
"elapsed": 1735000,
|
||||
"duration": 2583584,
|
||||
"addedAt": 1631852183,
|
||||
"watched": 67,
|
||||
"transcoded": 12,
|
||||
"stream": "transcode",
|
||||
"id": "1abe5dd5b977bf954ccd9ccc9f91aebb",
|
||||
"session": "1abe5dd5b977bf954ccd9ccc9f91aebb",
|
||||
"bandwidth": "3980",
|
||||
"bandwidthType": "wan",
|
||||
"sessionType": "Transcoding",
|
||||
"state": "play",
|
||||
"user": "Method Man",
|
||||
"userThumb": "https://th.bing.com/th/id/R.31a64d5f67dced802bb06e5f4a067b13?rik=imGnVnLBY2iTxg",
|
||||
"userAddress": "10.0.0.114",
|
||||
"address": "https://demo.organizr.app/plex/web/index.html#!/server/something/details?key=/library/metadata/743046",
|
||||
"nowPlayingOriginalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/718958/art/1630643017&height=675&width=1200&key=718958-np$5LNUILHVMC",
|
||||
"originalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/738933/thumb/1631895318&height=300&width=200&key=743046-list$NE8FV3EVES",
|
||||
"openTab": true,
|
||||
"tabName": "Plex (Watch me)",
|
||||
"userStream": {
|
||||
"platform": "Roku",
|
||||
"product": "Plex for Roku",
|
||||
"device": "100005842",
|
||||
"stream": "transcode (Throttled)",
|
||||
"videoResolution": "720p",
|
||||
"throttled": true,
|
||||
"sourceVideoCodec": "h264",
|
||||
"videoCodec": "h264",
|
||||
"audioCodec": "ac3",
|
||||
"sourceAudioCodec": "eac3",
|
||||
"videoDecision": "Transcode",
|
||||
"audioDecision": "Transcode",
|
||||
"container": "mpegts",
|
||||
"audioChannels": "6"
|
||||
},
|
||||
"metadata": {
|
||||
"guid": "plex://episode/613cc0031fe69c7d644b94d0",
|
||||
"summary": "After the semifinal, Beard sets out on an all-night odyssey through London in an effort to collect his thoughts.",
|
||||
"rating": "",
|
||||
"duration": "2583584",
|
||||
"originallyAvailableAt": "2021-09-17",
|
||||
"year": "",
|
||||
"studio": "",
|
||||
"tagline": "",
|
||||
"genres": "",
|
||||
"actors": ""
|
||||
},
|
||||
"nowPlayingImageURL": "plugins/images/cache/718958-np.jpg",
|
||||
"imageURL": "api/v2/homepage/image?source=plex&img=/library/metadata/738933/thumb/1631895318&height=300&width=200&key=743046-list"
|
||||
},
|
||||
{
|
||||
"type": "movie",
|
||||
"title": "Moana",
|
||||
"secondaryTitle": "2016",
|
||||
"summary": "In Ancient Polynesia, when a terrible curse incurred by Maui reaches an impetuous Chieftain's daughter's island, she answers the Ocean's call to seek out the demigod to set things right.",
|
||||
"ratingKey": "629519",
|
||||
"thumb": "/library/metadata/629519/thumb/1598244654",
|
||||
"key": "629519-list",
|
||||
"nowPlayingThumb": "/library/metadata/629519/art/1598244654",
|
||||
"nowPlayingKey": "629519-np",
|
||||
"nowPlayingTitle": "Moana",
|
||||
"nowPlayingBottom": "2016",
|
||||
"metadataKey": "629519",
|
||||
"originalType": {
|
||||
"0": "movie"
|
||||
},
|
||||
"uid": "629519",
|
||||
"elapsed": 2666000,
|
||||
"duration": 6432811,
|
||||
"addedAt": 1487623948,
|
||||
"watched": 41,
|
||||
"transcoded": 2,
|
||||
"stream": "transcode",
|
||||
"id": "6ejx7gkos73ipivzocta2a61",
|
||||
"session": "nw0bu3kysxyx6pujqxmsjddm",
|
||||
"bandwidth": "3978",
|
||||
"bandwidthType": "wan",
|
||||
"sessionType": "Transcoding",
|
||||
"state": "play",
|
||||
"user": "RZA",
|
||||
"userThumb": "https://th.bing.com/th/id/R.f8d56c8ff8920ab7bc85cc0ed42d66b0?rik=d%2bZbeC%2bPUcIH0g",
|
||||
"userAddress": "10.0.0.105",
|
||||
"address": "https://demo.organizr.app/plex/web/index.html#!/server/asdsadsadsaddas/details?key=/library/metadata/629519",
|
||||
"nowPlayingOriginalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/629519/art/1598244654&height=675&width=1200&key=629519-np$FKTWEPXEDU",
|
||||
"originalImage": "api/v2/homepage/image?source=plex&img=/library/metadata/629519/thumb/1598244654&height=300&width=200&key=629519-list$8JSXWW3JLM",
|
||||
"openTab": true,
|
||||
"tabName": "Plex (Watch me)",
|
||||
"userStream": {
|
||||
"platform": "webOS",
|
||||
"product": "Plex for LG",
|
||||
"device": "",
|
||||
"stream": "transcode",
|
||||
"videoResolution": "720p",
|
||||
"throttled": false,
|
||||
"sourceVideoCodec": "h264",
|
||||
"videoCodec": "h264",
|
||||
"audioCodec": "aac",
|
||||
"sourceAudioCodec": "dca",
|
||||
"videoDecision": "Transcode",
|
||||
"audioDecision": "Transcode",
|
||||
"container": "mpegts",
|
||||
"audioChannels": "6"
|
||||
},
|
||||
"metadata": {
|
||||
"guid": "plex://movie/5d776af2ad5437001f78dad6",
|
||||
"summary": "In Ancient Polynesia, when a terrible curse incurred by Maui reaches an impetuous Chieftain's daughter's island, she answers the Ocean's call to seek out the demigod to set things right.",
|
||||
"rating": "",
|
||||
"duration": "6432811",
|
||||
"originallyAvailableAt": "2016-11-07",
|
||||
"year": "2016",
|
||||
"studio": "Walt Disney Pictures",
|
||||
"tagline": "The ocean is calling.",
|
||||
"genres": [
|
||||
"Animation",
|
||||
"Action/Adventure",
|
||||
"Comedy",
|
||||
"Family",
|
||||
"Fantasy",
|
||||
"Musical",
|
||||
"Thriller"
|
||||
],
|
||||
"actors": [
|
||||
{
|
||||
"name": "Auli'i Cravalho",
|
||||
"role": "Moana (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/people/5d776af2ad5437001f78db11.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Dwayne Johnson",
|
||||
"role": "Maui (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/0/people/0740957f34be813b907cc513a4cffe6d.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Rachel House",
|
||||
"role": "Gramma Tala (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/f/people/f7f7556172c45f10222690a5faa74c11.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Temuera Morrison",
|
||||
"role": "Chief Tui (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/4/people/461e9278f779c9589b50075f20d32178.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Jemaine Clement",
|
||||
"role": "Tamatoa (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/people/5d7768322ec6b5001f6bb740.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Nicole Scherzinger",
|
||||
"role": "Sina (voice)",
|
||||
"thumb": "https://artworks.thetvdb.com/banners/actors/495988.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Alan Tudyk",
|
||||
"role": "Heihei / Villager #3 (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/e/people/e4f3693273868092da3e44d7c8f4243e.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Christopher Jackson",
|
||||
"role": "Chief Tui (singing voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/6/people/6fa8635916eff2a19968707d67e3eb57.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Oscar Kightley",
|
||||
"role": "Fisherman (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/people/5d77683b961905001eb95197.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Troy Polamalu",
|
||||
"role": "Villager #1 (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/people/5d7768883ab0e7001f503de4.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Puanani Cravalho",
|
||||
"role": "Villager #2 (voice)",
|
||||
"thumb": "https://metadata-static.plex.tv/people/5d776af2ad5437001f78db13.jpg"
|
||||
},
|
||||
{
|
||||
"name": "Phillipa Soo",
|
||||
"role": "Unspecified Villager/Sina",
|
||||
"thumb": "https://metadata-static.plex.tv/f/people/fb5f0ecc0838c82d1c052f41c61725e0.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"nowPlayingImageURL": "plugins/images/cache/629519-np.jpg",
|
||||
"imageURL": "api/v2/homepage/image?source=plex&img=/library/metadata/629519/thumb/1598244654&height=300&width=200&key=629519-list"
|
||||
}
|
||||
],
|
||||
"plexID": "sadsadsadsad",
|
||||
"showNames": true,
|
||||
"group": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
58
docker/test/tvtracker/config/www/organizr/api/functions.php
Normal file
58
docker/test/tvtracker/config/www/organizr/api/functions.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// Set UTC timezone
|
||||
date_default_timezone_set("UTC");
|
||||
// Autoload frameworks
|
||||
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php');
|
||||
// Include all function and class files
|
||||
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
|
||||
require_once $filename;
|
||||
}
|
||||
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'homepage' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
|
||||
require_once $filename;
|
||||
}
|
||||
// Include EmbyLiveTVTracker trait before class loading
|
||||
if (file_exists(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'embyLiveTVTracker' . DIRECTORY_SEPARATOR . 'api.php')) {
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'embyLiveTVTracker' . DIRECTORY_SEPARATOR . 'api.php';
|
||||
}
|
||||
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
|
||||
require_once $filename;
|
||||
}
|
||||
// Include all pages files
|
||||
foreach (glob(__DIR__ . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . "*.php") as $filename) {
|
||||
require_once $filename;
|
||||
}
|
||||
// Include all custom pages files
|
||||
if (file_exists(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'pages')) {
|
||||
foreach (glob(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . '*.php') as $filename) {
|
||||
require_once $filename;
|
||||
}
|
||||
}
|
||||
|
||||
// Include all plugin files
|
||||
try {
|
||||
$folder = __DIR__ . DIRECTORY_SEPARATOR . 'plugins';
|
||||
$directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
|
||||
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
|
||||
foreach ($iteratorIterator as $info) {
|
||||
if ($info->getFilename() == 'plugin.php' || strpos($info->getFilename(), 'page.php') !== false || $info->getFilename() == 'cron.php') {
|
||||
require_once $info->getPathname();
|
||||
}
|
||||
}
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// Folder doesn't exist or permission denied
|
||||
}
|
||||
// Include all custom plugin files
|
||||
try {
|
||||
if (file_exists(dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins')) {
|
||||
$folder = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins';
|
||||
$directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
|
||||
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
|
||||
foreach ($iteratorIterator as $info) {
|
||||
if ($info->getFilename() == 'plugin.php' || strpos($info->getFilename(), 'page.php') !== false || $info->getFilename() == 'cron.php') {
|
||||
require_once $info->getPathname();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UnexpectedValueException $e) {
|
||||
// Permission denied
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
trait TwoFAFunctions
|
||||
{
|
||||
public function create2FA($type)
|
||||
{
|
||||
$result['type'] = $type;
|
||||
switch ($type) {
|
||||
case 'google':
|
||||
try {
|
||||
$google2fa = new PragmaRX\Google2FA\Google2FA();
|
||||
$google2fa->setAllowInsecureCallToGoogleApis(true);
|
||||
$result['secret'] = $google2fa->generateSecretKey();
|
||||
$result['url'] = $google2fa->getQRCodeGoogleUrl(
|
||||
$this->config['title'],
|
||||
$this->user['username'],
|
||||
$result['secret']
|
||||
);
|
||||
} catch (PragmaRX\Google2FA\Exceptions\InsecureCallException $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$this->setAPIResponse('error', $type . ' is not an available to be setup', 404);
|
||||
return null;
|
||||
}
|
||||
$this->setAPIResponse('success', '2FA code created - awaiting verification', 200);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function verify2FA($secret, $code, $type)
|
||||
{
|
||||
if (!$secret || $secret == '') {
|
||||
$this->setAPIResponse('error', 'Secret was not supplied or left blank', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$code || $code == '') {
|
||||
$this->setAPIResponse('error', 'Code was not supplied or left blank', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$type || $type == '') {
|
||||
$this->setAPIResponse('error', 'Type was not supplied or left blank', 422);
|
||||
return false;
|
||||
}
|
||||
switch ($type) {
|
||||
case 'google':
|
||||
$google2fa = new PragmaRX\Google2FA\Google2FA();
|
||||
$google2fa->setWindow(5);
|
||||
$valid = $google2fa->verifyKey($secret, $code);
|
||||
break;
|
||||
default:
|
||||
$this->setAPIResponse('error', $type . ' is not an available to be setup', 404);
|
||||
return false;
|
||||
}
|
||||
if ($valid) {
|
||||
$this->setAPIResponse('success', 'Verification code verified', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('success', 'Verification code invalid', 401);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function save2FA($secret, $type)
|
||||
{
|
||||
if (!$secret || $secret == '') {
|
||||
$this->setAPIResponse('error', 'Secret was not supplied or left blank', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$type || $type == '') {
|
||||
$this->setAPIResponse('error', 'Type was not supplied or left blank', 422);
|
||||
return false;
|
||||
}
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => array(
|
||||
'UPDATE users SET',
|
||||
['auth_service' => $type . '::' . $secret],
|
||||
'WHERE id = ?',
|
||||
$this->user['userID']
|
||||
)
|
||||
),
|
||||
];
|
||||
$this->setLoggerChannel('Users')->info('User added 2FA');
|
||||
$this->setAPIResponse('success', '2FA Added', 200);
|
||||
return $this->processQueries($response);
|
||||
}
|
||||
|
||||
public function remove2FA()
|
||||
{
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => array(
|
||||
'UPDATE users SET',
|
||||
['auth_service' => 'internal'],
|
||||
'WHERE id = ?',
|
||||
$this->user['userID']
|
||||
)
|
||||
),
|
||||
];
|
||||
$this->setLoggerChannel('Users')->info('User removed 2FA');
|
||||
$this->setAPIResponse('success', '2FA deleted', 204);
|
||||
return $this->processQueries($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
class UptimeKumaMetrics
|
||||
{
|
||||
protected string $raw;
|
||||
private array $monitors = [];
|
||||
|
||||
public function __construct(string $raw)
|
||||
{
|
||||
$this->raw = $raw;
|
||||
}
|
||||
|
||||
public function process(): self
|
||||
{
|
||||
$processed = explode(PHP_EOL, $this->raw);
|
||||
|
||||
$monitors = array_filter($processed, function (string $item) {
|
||||
return str_starts_with($item, 'monitor_status');
|
||||
});
|
||||
// TODO: parse the latencies and add them on to the info card
|
||||
$latencies = array_filter($processed, function (string $item) {
|
||||
return str_starts_with($item, 'monitor_response_time');
|
||||
});
|
||||
|
||||
$monitors = array_map(function (string $item) {
|
||||
return $this->parseMonitorStatus($item);
|
||||
}, $monitors);
|
||||
$this->addLatencyToMonitors($monitors, $latencies);
|
||||
$this->monitors = array_values(array_filter($monitors));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMonitors(): array
|
||||
{
|
||||
return $this->monitors;
|
||||
}
|
||||
|
||||
private function parseMonitorStatus(string $status): ?array
|
||||
{
|
||||
if (substr($status, -1) === '2') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$up = (substr($status, -1)) == '0' ? false : true;
|
||||
$status = substr($status, 15);
|
||||
$status = substr($status, 0, -4);
|
||||
$status = explode(',', $status);
|
||||
$data = [
|
||||
'name' => $this->getStringBetweenQuotes($status[0]),
|
||||
'url' => $this->getStringBetweenQuotes($status[2]),
|
||||
'type' => $this->getStringBetweenQuotes($status[1]),
|
||||
'status' => $up,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function addLatencyToMonitors(array &$monitors, array $latencies)
|
||||
{
|
||||
$latencies = $this->getLatenciesByName($latencies);
|
||||
foreach ($monitors as &$monitor) {
|
||||
$monitor['latency'] = $latencies[$monitor['name']] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
private function getLatenciesByName(array $latencies): array
|
||||
{
|
||||
$l = [];
|
||||
|
||||
foreach ($latencies as $latency) {
|
||||
if (preg_match('/monitor_name="(.*)",monitor_type.* ([0-9]{1,})$/', $latency, $match)) {
|
||||
$l[$match[1]] = (int) $match[2];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return $l;
|
||||
}
|
||||
|
||||
private function getStringBetweenQuotes(string $input): string
|
||||
{
|
||||
if (preg_match('/"(.*?)"/', $input, $match) == 1) {
|
||||
return $match[1];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
trait ApiFunctions
|
||||
{
|
||||
// Nothing
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
<?php
|
||||
|
||||
trait AuthFunctions
|
||||
{
|
||||
public function testConnectionLdap()
|
||||
{
|
||||
if (!empty($this->config['authBaseDN']) && !empty($this->config['authBackendHost'])) {
|
||||
$ad = new \Adldap\Adldap();
|
||||
// Create a configuration array.
|
||||
$ldapServers = explode(',', $this->config['authBackendHost']);
|
||||
$i = 0;
|
||||
foreach ($ldapServers as $key => $value) {
|
||||
// Calculate parts
|
||||
$digest = parse_url(trim($value));
|
||||
$scheme = strtolower(($digest['scheme'] ?? 'ldap'));
|
||||
$host = ($digest['host'] ?? ($digest['path'] ?? ''));
|
||||
$port = ($digest['port'] ?? (strtolower($scheme) == 'ldap' ? 389 : 636));
|
||||
// Reassign
|
||||
$ldapHosts[] = $host;
|
||||
if ($i == 0) {
|
||||
$ldapPort = $port;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$config = [
|
||||
// Mandatory Configuration Options
|
||||
'hosts' => $ldapHosts,
|
||||
'base_dn' => $this->config['authBaseDN'],
|
||||
'username' => (empty($this->config['ldapBindUsername'])) ? null : $this->config['ldapBindUsername'],
|
||||
'password' => (empty($this->config['ldapBindPassword'])) ? null : $this->decrypt($this->config['ldapBindPassword']),
|
||||
// Optional Configuration Options
|
||||
'schema' => (($this->config['ldapType'] == '1') ? Adldap\Schemas\ActiveDirectory::class : (($this->config['ldapType'] == '2') ? Adldap\Schemas\OpenLDAP::class : Adldap\Schemas\FreeIPA::class)),
|
||||
'account_prefix' => '',
|
||||
'account_suffix' => '',
|
||||
'port' => $ldapPort,
|
||||
'follow_referrals' => false,
|
||||
'use_ssl' => $this->config['ldapSSL'],
|
||||
'use_tls' => $this->config['ldapTLS'],
|
||||
'version' => 3,
|
||||
'timeout' => 5,
|
||||
// Custom LDAP Options
|
||||
'custom_options' => [
|
||||
// See: http://php.net/ldap_set_option
|
||||
LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_ALLOW
|
||||
]
|
||||
];
|
||||
// Add a connection provider to Adldap.
|
||||
$ad->addProvider($config);
|
||||
try {
|
||||
// If a successful connection is made to your server, the provider will be returned.
|
||||
$provider = $ad->connect();
|
||||
} catch (\Adldap\Auth\BindException $e) {
|
||||
$detailedError = $e->getDetailedError();
|
||||
$this->setLoggerChannel('LDAP')->error($e);
|
||||
$this->setAPIResponse('error', $detailedError->getErrorMessage(), 409);
|
||||
return $detailedError->getErrorMessage();
|
||||
// There was an issue binding / connecting to the server.
|
||||
}
|
||||
if ($provider) {
|
||||
$this->setAPIResponse('success', 'LDAP connection successful', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Could not connect', 500);
|
||||
return false;
|
||||
}
|
||||
return ($provider) ? true : false;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'authBaseDN and/or BackendHost not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnectionLdapLogin($array)
|
||||
{
|
||||
$username = $array['username'] ?? null;
|
||||
$password = $array['password'] ?? null;
|
||||
if (empty($username) || empty($password)) {
|
||||
$this->setAPIResponse('error', 'Username and/or Password not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!empty($this->config['authBaseDN']) && !empty($this->config['authBackendHost'])) {
|
||||
$ad = new \Adldap\Adldap();
|
||||
// Create a configuration array.
|
||||
$ldapServers = explode(',', $this->config['authBackendHost']);
|
||||
$i = 0;
|
||||
foreach ($ldapServers as $key => $value) {
|
||||
// Calculate parts
|
||||
$digest = parse_url(trim($value));
|
||||
$scheme = strtolower(($digest['scheme'] ?? 'ldap'));
|
||||
$host = ($digest['host'] ?? ($digest['path'] ?? ''));
|
||||
$port = ($digest['port'] ?? (strtolower($scheme) == 'ldap' ? 389 : 636));
|
||||
// Reassign
|
||||
$ldapHosts[] = $host;
|
||||
$ldapServersNew[$key] = $scheme . '://' . $host . ':' . $port; // May use this later
|
||||
if ($i == 0) {
|
||||
$ldapPort = $port;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$config = [
|
||||
// Mandatory Configuration Options
|
||||
'hosts' => $ldapHosts,
|
||||
'base_dn' => $this->config['authBaseDN'],
|
||||
'username' => (empty($this->config['ldapBindUsername'])) ? null : $this->config['ldapBindUsername'],
|
||||
'password' => (empty($this->config['ldapBindPassword'])) ? null : $this->decrypt($this->config['ldapBindPassword']),
|
||||
// Optional Configuration Options
|
||||
'schema' => (($this->config['ldapType'] == '1') ? Adldap\Schemas\ActiveDirectory::class : (($this->config['ldapType'] == '2') ? Adldap\Schemas\OpenLDAP::class : Adldap\Schemas\FreeIPA::class)),
|
||||
'account_prefix' => (empty($this->config['authBackendHostPrefix'])) ? null : $this->config['authBackendHostPrefix'],
|
||||
'account_suffix' => (empty($this->config['authBackendHostSuffix'])) ? null : $this->config['authBackendHostSuffix'],
|
||||
'port' => $ldapPort,
|
||||
'follow_referrals' => false,
|
||||
'use_ssl' => $this->config['ldapSSL'],
|
||||
'use_tls' => $this->config['ldapTLS'],
|
||||
'version' => 3,
|
||||
'timeout' => 5,
|
||||
// Custom LDAP Options
|
||||
'custom_options' => [
|
||||
// See: http://php.net/ldap_set_option
|
||||
LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_ALLOW
|
||||
]
|
||||
];
|
||||
// Add a connection provider to Adldap.
|
||||
$ad->addProvider($config);
|
||||
try {
|
||||
// If a successful connection is made to your server, the provider will be returned.
|
||||
$provider = $ad->connect();
|
||||
//prettyPrint($provider);
|
||||
if ($provider->auth()->attempt($username, $password, true)) {
|
||||
// Passed.
|
||||
$user = $provider->search()->find($username);
|
||||
//return $user->getFirstAttribute('cn');
|
||||
//return $user->getGroups(['cn']);
|
||||
//return $user;
|
||||
//return $user->getUserPrincipalName();
|
||||
//return $user->getGroups(['cn']);
|
||||
$this->setResponse(200, 'LDAP connection successful');
|
||||
return true;
|
||||
} else {
|
||||
// Failed.
|
||||
$this->setResponse(401, 'Username/Password Failed to authenticate');
|
||||
return false;
|
||||
}
|
||||
} catch (\Adldap\Auth\BindException $e) {
|
||||
$detailedError = $e->getDetailedError();
|
||||
$this->setLoggerChannel('LDAP')->error($e);
|
||||
$this->setAPIResponse('error', $detailedError->getErrorMessage(), 500);
|
||||
return $detailedError->getErrorMessage();
|
||||
// There was an issue binding / connecting to the server.
|
||||
} catch (Adldap\Auth\UsernameRequiredException $e) {
|
||||
$detailedError = $e->getDetailedError();
|
||||
$this->setLoggerChannel('LDAP')->error($e);
|
||||
$this->setAPIResponse('error', $detailedError->getErrorMessage(), 422);
|
||||
return $detailedError->getErrorMessage();
|
||||
// The user didn't supply a username.
|
||||
} catch (Adldap\Auth\PasswordRequiredException $e) {
|
||||
$detailedError = $e->getDetailedError();
|
||||
$this->setLoggerChannel('LDAP')->error($e);
|
||||
$this->setAPIResponse('error', $detailedError->getErrorMessage(), 422);
|
||||
return $detailedError->getErrorMessage();
|
||||
// The user didn't supply a password.
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'authBaseDN and/or BackendHost not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function checkPlexToken($token = '')
|
||||
{
|
||||
try {
|
||||
if (($token !== '')) {
|
||||
$url = 'https://plex.tv/users/account.json';
|
||||
$headers = array(
|
||||
'X-Plex-Token' => $token,
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json'
|
||||
);
|
||||
$response = Requests::get($url, $headers);
|
||||
if ($response->success) {
|
||||
return json_decode($response->body, true);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Plex')->error($e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkPlexUser($username)
|
||||
{
|
||||
try {
|
||||
if (!empty($this->config['plexToken'])) {
|
||||
$url = 'https://plex.tv/api/users';
|
||||
$headers = array(
|
||||
'X-Plex-Token' => $this->config['plexToken'],
|
||||
);
|
||||
$response = Requests::get($url, $headers);
|
||||
if ($response->success) {
|
||||
libxml_use_internal_errors(true);
|
||||
$userXML = simplexml_load_string($response->body);
|
||||
if (is_array($userXML) || is_object($userXML)) {
|
||||
$usernameLower = strtolower($username);
|
||||
foreach ($userXML as $child) {
|
||||
if (isset($child['username']) && strtolower($child['username']) == $usernameLower || isset($child['email']) && strtolower($child['email']) == $usernameLower) {
|
||||
$this->setLoggerChannel('Plex')->info('Found User on Friends List');
|
||||
$machineMatches = false;
|
||||
if ($this->config['plexStrictFriends']) {
|
||||
foreach ($child->Server as $server) {
|
||||
if ((string)$server['machineIdentifier'] == $this->config['plexID']) {
|
||||
$machineMatches = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$machineMatches = true;
|
||||
}
|
||||
if ($machineMatches) {
|
||||
$this->setLoggerChannel('Plex')->info('User Approved for Login');
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Plex')->warning('User not Approved User');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Plex')->error($e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function plugin_auth_plex($username, $password)
|
||||
{
|
||||
try {
|
||||
$usernameLower = strtolower($username);
|
||||
//Login User
|
||||
$url = 'https://plex.tv/users/sign_in.json';
|
||||
$headers = array(
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'X-Plex-Product' => 'Organizr',
|
||||
'X-Plex-Version' => '2.0',
|
||||
'X-Plex-Client-Identifier' => $this->config['uuid'],
|
||||
);
|
||||
$data = array(
|
||||
'user[login]' => $username,
|
||||
'user[password]' => $password,
|
||||
);
|
||||
$options = array('timeout' => 30);
|
||||
$response = Requests::post($url, $headers, $data, $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
if ((is_array($json) && isset($json['user']) && isset($json['user']['username'])) && strtolower($json['user']['username']) == $usernameLower || strtolower($json['user']['email']) == $usernameLower) {
|
||||
if ((!empty($this->config['plexAdmin']) && (strtolower($this->config['plexAdmin']) == strtolower($json['user']['username']) || strtolower($this->config['plexAdmin']) == strtolower($json['user']['email']))) || $this->checkPlexUser($json['user']['username'])) {
|
||||
return array(
|
||||
'username' => $json['user']['username'],
|
||||
'email' => $json['user']['email'],
|
||||
'image' => $json['user']['thumb'],
|
||||
'token' => $json['user']['authToken']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Plex')->error($e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pass credentials to LDAP backend
|
||||
public function plugin_auth_ldap($username, $password)
|
||||
{
|
||||
if (!empty($this->config['authBaseDN']) && !empty($this->config['authBackendHost'])) {
|
||||
$ad = new \Adldap\Adldap();
|
||||
// Create a configuration array.
|
||||
$ldapServers = explode(',', $this->config['authBackendHost']);
|
||||
$i = 0;
|
||||
foreach ($ldapServers as $key => $value) {
|
||||
// Calculate parts
|
||||
$digest = parse_url(trim($value));
|
||||
$scheme = strtolower((isset($digest['scheme']) ? $digest['scheme'] : 'ldap'));
|
||||
$host = (isset($digest['host']) ? $digest['host'] : (isset($digest['path']) ? $digest['path'] : ''));
|
||||
$port = (isset($digest['port']) ? $digest['port'] : (strtolower($scheme) == 'ldap' ? 389 : 636));
|
||||
// Reassign
|
||||
$ldapHosts[] = $host;
|
||||
$ldapServersNew[$key] = $scheme . '://' . $host . ':' . $port; // May use this later
|
||||
if ($i == 0) {
|
||||
$ldapPort = $port;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$config = [
|
||||
// Mandatory Configuration Options
|
||||
'hosts' => $ldapHosts,
|
||||
'base_dn' => $this->config['authBaseDN'],
|
||||
'username' => (empty($this->config['ldapBindUsername'])) ? null : $this->config['ldapBindUsername'],
|
||||
'password' => (empty($this->config['ldapBindPassword'])) ? null : $this->decrypt($this->config['ldapBindPassword']),
|
||||
// Optional Configuration Options
|
||||
'schema' => (($this->config['ldapType'] == '1') ? Adldap\Schemas\ActiveDirectory::class : (($this->config['ldapType'] == '2') ? Adldap\Schemas\OpenLDAP::class : Adldap\Schemas\FreeIPA::class)),
|
||||
'account_prefix' => (empty($this->config['authBackendHostPrefix'])) ? null : $this->config['authBackendHostPrefix'],
|
||||
'account_suffix' => (empty($this->config['authBackendHostSuffix'])) ? null : $this->config['authBackendHostSuffix'],
|
||||
'port' => $ldapPort,
|
||||
'follow_referrals' => false,
|
||||
'use_ssl' => $this->config['ldapSSL'],
|
||||
'use_tls' => $this->config['ldapTLS'],
|
||||
'version' => 3,
|
||||
'timeout' => 5,
|
||||
// Custom LDAP Options
|
||||
'custom_options' => [
|
||||
// See: http://php.net/ldap_set_option
|
||||
LDAP_OPT_X_TLS_REQUIRE_CERT => LDAP_OPT_X_TLS_ALLOW
|
||||
]
|
||||
];
|
||||
// Add a connection provider to Adldap.
|
||||
$ad->addProvider($config);
|
||||
try {
|
||||
// If a successful connection is made to your server, the provider will be returned.
|
||||
$provider = $ad->connect();
|
||||
//prettyPrint($provider);
|
||||
if ($provider->auth()->attempt($username, $password)) {
|
||||
try {
|
||||
// Try and get email from LDAP server
|
||||
$accountDN = ((empty($this->config['authBackendHostPrefix'])) ? null : $this->config['authBackendHostPrefix']) . $username . ((empty($this->config['authBackendHostSuffix'])) ? null : $this->config['authBackendHostSuffix']);
|
||||
$record = $provider->search()->findByDnOrFail($accountDN);
|
||||
$email = $record->getFirstAttribute('mail');
|
||||
} catch (Adldap\Models\ModelNotFoundException $e) {
|
||||
// Record wasn't found!
|
||||
$email = null;
|
||||
}
|
||||
// Passed.
|
||||
return array(
|
||||
'email' => $email
|
||||
);
|
||||
} else {
|
||||
// Failed.
|
||||
return false;
|
||||
}
|
||||
} catch (\Adldap\Auth\BindException $e) {
|
||||
$this->setLoggerChannel('LDAP')->error($e);
|
||||
// There was an issue binding / connecting to the server.
|
||||
} catch (Adldap\Auth\UsernameRequiredException $e) {
|
||||
$this->setLoggerChannel('LDAP')->error($e);
|
||||
// The user didn't supply a username.
|
||||
} catch (Adldap\Auth\PasswordRequiredException $e) {
|
||||
$this->setLoggerChannel('LDAP')->error($e);
|
||||
// The user didn't supply a password.
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ldap Auth Missing Dependency
|
||||
public function plugin_auth_ldap_disabled()
|
||||
{
|
||||
return 'LDAP - Disabled (Dependency: php-ldap missing!)';
|
||||
}
|
||||
|
||||
// Pass credentials to FTP backend
|
||||
public function plugin_auth_ftp($username, $password)
|
||||
{
|
||||
// Calculate parts
|
||||
$digest = parse_url($this->config['authBackendHost']);
|
||||
$scheme = strtolower((isset($digest['scheme']) ? $digest['scheme'] : (function_exists('ftp_ssl_connect') ? 'ftps' : 'ftp')));
|
||||
$host = (isset($digest['host']) ? $digest['host'] : (isset($digest['path']) ? $digest['path'] : ''));
|
||||
$port = (isset($digest['port']) ? $digest['port'] : 21);
|
||||
// Determine Connection Type
|
||||
if ($scheme == 'ftps') {
|
||||
$conn_id = ftp_ssl_connect($host, $port, 20);
|
||||
} elseif ($scheme == 'ftp') {
|
||||
$conn_id = ftp_connect($host, $port, 20);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// Check if valid FTP connection
|
||||
if ($conn_id) {
|
||||
// Attempt login
|
||||
@$login_result = ftp_login($conn_id, $username, $password);
|
||||
ftp_close($conn_id);
|
||||
// Return Result
|
||||
if ($login_result) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass credentials to Emby Backend
|
||||
public function plugin_auth_emby_local($username, $password)
|
||||
{
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['embyURL']) . '/Users/AuthenticateByName';
|
||||
$headers = array(
|
||||
'Authorization' => 'Emby UserId="e8837bc1-ad67-520e-8cd2-f629e3155721", Client="None", Device="Organizr", DeviceId="xxx", Version="1.0.0.0"',
|
||||
'Content-Type' => 'application/json',
|
||||
);
|
||||
$data = array(
|
||||
'Username' => $username,
|
||||
'pw' => $password,
|
||||
'Password' => sha1($password),
|
||||
'PasswordMd5' => md5($password),
|
||||
);
|
||||
$response = Requests::post($url, $headers, json_encode($data));
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
if (is_array($json) && isset($json['SessionInfo']) && isset($json['User']) && $json['User']['HasPassword'] == true) {
|
||||
// Login Success - Now Logout Emby Session As We No Longer Need It
|
||||
$headers = array(
|
||||
'X-Emby-Token' => $json['AccessToken'],
|
||||
'X-Mediabrowser-Token' => $json['AccessToken'],
|
||||
);
|
||||
$response = Requests::post($this->qualifyURL($this->config['embyURL']) . '/Sessions/Logout', $headers, array());
|
||||
if ($response->success) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Emby')->error($e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pass credentials to JellyFin Backend
|
||||
public function plugin_auth_jellyfin($username, $password)
|
||||
{
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['jellyfinURL']) . '/Users/authenticatebyname';
|
||||
$headers = array(
|
||||
'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Auth", Device="Organizr", DeviceId="orgv2", Version="2.0"',
|
||||
'Content-Type' => 'application/json',
|
||||
);
|
||||
$data = array(
|
||||
'Username' => $username,
|
||||
'Pw' => $password
|
||||
);
|
||||
$response = Requests::post($url, $headers, json_encode($data));
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
if (is_array($json) && isset($json['SessionInfo']) && isset($json['User']) && $json['User']['HasPassword'] == true) {
|
||||
$this->setLoggerChannel('JellyFin')->info('Found User and Logged In');
|
||||
// Login Success - Now Logout JellyFin Session As We No Longer Need It
|
||||
$headers = array(
|
||||
'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Auth", Device="Organizr", DeviceId="orgv2", Version="2.0", Token="' . $json['AccessToken'] . '"',
|
||||
'Content-Type' => 'application/json',
|
||||
);
|
||||
$response = Requests::post($this->qualifyURL($this->config['jellyfinURL']) . '/Sessions/Logout', $headers, array());
|
||||
if ($response->success) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('JellyFin')->error($e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Authenticate against emby connect
|
||||
public function plugin_auth_emby_connect($username, $password)
|
||||
{
|
||||
// Emby disabled EmbyConnect on their API
|
||||
// https://github.com/MediaBrowser/Emby/issues/3553
|
||||
//return plugin_auth_emby_local($username, $password);
|
||||
try {
|
||||
$this->setLoggerChannel('Emby')->info('Attempting to Login with Emby Connect for user: ' . $username);
|
||||
$connectURL = 'https://connect.emby.media/service/user/authenticate';
|
||||
$headers = array(
|
||||
'Accept' => 'application/json',
|
||||
'X-Application' => 'Organizr/2.0'
|
||||
);
|
||||
$data = array(
|
||||
'nameOrEmail' => $username,
|
||||
'rawpw' => $password,
|
||||
);
|
||||
$response = Requests::post($connectURL, $headers, $data);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
if (is_array($json) && isset($json['AccessToken']) && isset($json['User'])) {
|
||||
$connectUser = $json['User'];
|
||||
} else {
|
||||
$this->setLoggerChannel('Emby')->warning('Bad Response');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('Emby')->warning('401 From Emby Connect');
|
||||
return false;
|
||||
}
|
||||
// Get A User
|
||||
if ($connectUser) {
|
||||
$url = $this->qualifyURL($this->config['embyURL']) . '/Users?api_key=' . $this->config['embyToken'];
|
||||
$response = Requests::get($url);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
if (is_array($json)) {
|
||||
foreach ($json as $key => $value) { // Scan for this user
|
||||
if (isset($value['ConnectUserName']) && isset($value['ConnectLinkType'])) { // Qualify as connect account
|
||||
if (strtolower($value['ConnectUserName']) == strtolower($connectUser['Name']) || strtolower($value['ConnectUserName']) == strtolower($connectUser['Email'])) {
|
||||
$this->setLoggerChannel('Emby')->info('Found User');
|
||||
return array(
|
||||
'email' => $connectUser['Email'],
|
||||
'username' => $connectUser['Name']
|
||||
//'image' => $json['User']['ImageUrl'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Emby')->error($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate Against Emby Local (first) and Emby Connect
|
||||
public function plugin_auth_emby_all($username, $password)
|
||||
{
|
||||
// Emby disabled EmbyConnect on their API
|
||||
// https://github.com/MediaBrowser/Emby/issues/3553
|
||||
$localResult = $this->plugin_auth_emby_local($username, $password);
|
||||
//return $localResult;
|
||||
if ($localResult) {
|
||||
return $localResult;
|
||||
} else {
|
||||
return $this->plugin_auth_emby_connect($username, $password);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
trait BackupFunctions
|
||||
{
|
||||
public function getOrganizrBackupLocation()
|
||||
{
|
||||
$defaultPath = $this->config['dbLocation'] . 'backups' . DIRECTORY_SEPARATOR;
|
||||
$userPath = $this->config['backupLocation'];
|
||||
if ($this->config['backupLocation'] !== '') {
|
||||
if (file_exists($userPath)) {
|
||||
return $userPath;
|
||||
}
|
||||
}
|
||||
return $defaultPath;
|
||||
}
|
||||
|
||||
public function fileArray($files)
|
||||
{
|
||||
foreach ($files as $file) {
|
||||
if (file_exists($file)) {
|
||||
$list[] = $file;
|
||||
}
|
||||
}
|
||||
if (!empty($list)) {
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteBackup($filename)
|
||||
{
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
$path = $this->getOrganizrBackupLocation();
|
||||
$filename = $path . $filename;
|
||||
if ($ext == 'zip') {
|
||||
if (file_exists($filename)) {
|
||||
$this->setLoggerChannel('Backup')->info('Deleted Backup [' . pathinfo($filename, PATHINFO_BASENAME) . ']');
|
||||
$this->setAPIResponse(null, pathinfo($filename, PATHINFO_BASENAME) . ' has been deleted', null);
|
||||
return (unlink($filename));
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'File does not exist', 404);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', pathinfo($filename, PATHINFO_BASENAME) . ' is not approved to be deleted', 409);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function downloadBackup($filename)
|
||||
{
|
||||
$path = $this->getOrganizrBackupLocation();
|
||||
$filename = $path . $filename;
|
||||
if (file_exists($filename)) {
|
||||
header('Content-Type: application/zip');
|
||||
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');
|
||||
header('Content-Length: ' . filesize($filename));
|
||||
flush();
|
||||
readfile($filename);
|
||||
exit();
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'File does not exist', 404);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function backupOrganizr($type = 'config')
|
||||
{
|
||||
$directory = $this->getOrganizrBackupLocation();
|
||||
@mkdir($directory, 0770, true);
|
||||
switch ($type) {
|
||||
case 'config':
|
||||
break;
|
||||
case 'full':
|
||||
break;
|
||||
default:
|
||||
}
|
||||
$this->setLoggerChannel('Backup')->notice('Backing up Organizr');
|
||||
$zipName = $directory . 'backup[' . date('Y-m-d_H-i') . ' - ' . $this->random_ascii_string(2) . '][' . $this->version . '].zip';
|
||||
$zip = new ZipArchive;
|
||||
$zip->open($zipName, ZipArchive::CREATE);
|
||||
if ($this->config['driver'] == 'sqlite3') {
|
||||
$zip->addFile($this->config['dbLocation'] . $this->config['dbName'], basename($this->config['dbLocation'] . $this->config['dbName']));
|
||||
}
|
||||
$rootPath = $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR;
|
||||
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($rootPath), RecursiveIteratorIterator::LEAVES_ONLY);
|
||||
|
||||
foreach ($files as $name => $file) {
|
||||
// Skip directories (they would be added automatically)
|
||||
if (!$file->isDir()) {
|
||||
if (!stripos($name, 'data' . DIRECTORY_SEPARATOR . 'cache') && !stripos($name, 'backups')) {
|
||||
// Get real and relative path for current file
|
||||
$filePath = $file->getRealPath();
|
||||
$relativePath = substr($filePath, strlen($rootPath));
|
||||
// Add current file to archive
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
$zip->close();
|
||||
$this->setLoggerChannel('Backup')->notice('Backup process finished');
|
||||
$this->setAPIResponse('success', 'Backup has been created', 200);
|
||||
$this->deleteBackupsLimit();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteBackupsLimit()
|
||||
{
|
||||
$backups = $this->getBackups();
|
||||
if ($backups) {
|
||||
$list = array_reverse($backups['files']);
|
||||
$killCount = count($list) - $this->config['keepBackupsCountCron'];
|
||||
if ($killCount >= 1) {
|
||||
foreach ($list as $count => $backup) {
|
||||
$count++;
|
||||
if ($count <= $killCount) {
|
||||
$this->log('Cron')->notice('Deleting organizr backup file as it is over limit', ['file' => $backup['name']]);
|
||||
$this->deleteBackup($backup['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getBackups()
|
||||
{
|
||||
$path = $this->getOrganizrBackupLocation();
|
||||
@mkdir($path, 0770, true);
|
||||
$files = array_diff(scandir($path), array('.', '..'));
|
||||
$fileList = [];
|
||||
$totalFiles = 0;
|
||||
$totalFileSize = 0;
|
||||
foreach ($files as $file) {
|
||||
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
if (file_exists($path . $file) && $ext == 'zip') {
|
||||
$size = filesize($path . $file);
|
||||
$totalFileSize = $totalFileSize + $size;
|
||||
$totalFiles = $totalFiles + 1;
|
||||
try {
|
||||
$fileList['files'][] = [
|
||||
'name' => $file,
|
||||
'size' => $this->human_filesize($size, 0),
|
||||
'date' => gmdate("Y-m-d\TH:i:s\Z", (filemtime($path . $file)))
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', 'Backup list failed', 409, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$fileList['total_files'] = $totalFiles;
|
||||
$fileList['total_size'] = $this->human_filesize($totalFileSize, 2);
|
||||
$fileList['files'] = $totalFiles > 0 ? array_reverse($fileList['files']) : null;
|
||||
$this->setAPIResponse('success', null, 200, array_reverse($fileList));
|
||||
return array_reverse($fileList);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cert file in this directory
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
trait ConfigFunctions
|
||||
{
|
||||
public function getConfigItem($item, $term = null)
|
||||
{
|
||||
if (strtolower($item) == 'search') {
|
||||
$configItems = $this->config;
|
||||
$results = [];
|
||||
foreach ($configItems as $configItem => $configItemValue) {
|
||||
if (stripos($configItem, $term) !== false) {
|
||||
$results[$configItem] = $configItemValue;
|
||||
if ($configItem == 'organizrHash') {
|
||||
$results[$configItem] = '***Secure***';
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->setAPIResponse('success', 'Search results for term: ' . $term, 200, $results);
|
||||
return $results;
|
||||
}
|
||||
if (isset($this->config[$item])) {
|
||||
$configItem = $this->config[$item];
|
||||
if ($item == 'organizrHash') {
|
||||
$configItem = '***Secure***';
|
||||
}
|
||||
$this->setAPIResponse('success', 'The value for ' . $item, 200, $configItem);
|
||||
return $this->config[$item];
|
||||
} else {
|
||||
$this->setAPIResponse('error', $item . ' is not defined or is blank', 404);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getConfigItems()
|
||||
{
|
||||
$configItems = $this->config;
|
||||
/*
|
||||
foreach ($configItems as $configItem => $configItemValue) {
|
||||
// should we keep this to filter more items?
|
||||
if ($configItem == 'organizrHash') {
|
||||
$configItems[$configItem] = '***Secure***';
|
||||
}
|
||||
}
|
||||
*/
|
||||
$configItems['organizrHash'] = '***Secure***';
|
||||
$this->setAPIResponse('success', null, 200, $configItems);
|
||||
return $configItems;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/* Depreciated */
|
||||
/*
|
||||
__
|
||||
w c(..)o (
|
||||
\__(-) __)
|
||||
/\ (
|
||||
/(_)___)
|
||||
w /|
|
||||
| \
|
||||
rox m m
|
||||
*/
|
||||
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
/* Depreciated */
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
|
||||
trait DemoFunctions
|
||||
{
|
||||
public function demoData($file = null)
|
||||
{
|
||||
if (!$file) {
|
||||
$this->setResponse(422, 'Demo file was not supplied');
|
||||
return false;
|
||||
}
|
||||
$path = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR . 'demo_data' . DIRECTORY_SEPARATOR . $file;
|
||||
if (file_exists($path)) {
|
||||
$data = file_get_contents($path);
|
||||
$path = (strpos($file, '/') !== false) ? explode('/', $file)[0] . '/' : '';
|
||||
$data = $this->userDefinedIdReplacementLink($data, ['data/cache/' => 'api/demo_data/' . $path . 'images/']);
|
||||
$data = json_decode($data, true);
|
||||
$this->setResponse(200, 'Demo data for file: ' . $file, $data['response']['data']);
|
||||
return $data['response']['data'];
|
||||
} else {
|
||||
$this->setResponse(404, 'Demo data was not found for file: ' . $file);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
|
||||
trait HomepageConnectFunctions
|
||||
{
|
||||
public function csvHomepageUrlToken($url, $token)
|
||||
{
|
||||
$list = array();
|
||||
$urlList = explode(',', $url);
|
||||
$tokenList = explode(',', $token);
|
||||
foreach ($urlList as $key => $value) {
|
||||
if (isset($tokenList[$key])) {
|
||||
$list[$key] = array(
|
||||
'url' => $this->qualifyURL($value),
|
||||
'token' => $tokenList[$key]
|
||||
);
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function streamType($value)
|
||||
{
|
||||
if ($value == "transcode" || $value == "Transcode") {
|
||||
return "Transcode";
|
||||
} elseif ($value == "copy" || $value == "DirectStream") {
|
||||
return "Direct Stream";
|
||||
} elseif ($value == "directplay" || $value == "DirectPlay") {
|
||||
return "Direct Play";
|
||||
} else {
|
||||
return "Direct Play";
|
||||
}
|
||||
}
|
||||
|
||||
public function getCacheImageSize($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'height':
|
||||
case 'h':
|
||||
return round(300 * $this->config['cacheImageSize']);
|
||||
case 'width':
|
||||
case 'w':
|
||||
return round(200 * $this->config['cacheImageSize']);
|
||||
case 'nowPlayingHeight':
|
||||
case 'nph':
|
||||
return round(675 * $this->config['cacheImageSize']);
|
||||
case 'nowPlayingWidth':
|
||||
case 'npw':
|
||||
return round(1200 * $this->config['cacheImageSize']);
|
||||
}
|
||||
}
|
||||
|
||||
public function ombiImport($type = null)
|
||||
{
|
||||
if (!empty($this->config['ombiURL']) && !empty($this->config['ombiToken']) && !empty($type)) {
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['ombiURL']);
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json",
|
||||
"Apikey" => $this->config['ombiToken']
|
||||
);
|
||||
$options = ($this->localURL($url)) ? array('verify' => false) : array();
|
||||
switch ($type) {
|
||||
case 'emby':
|
||||
case 'emby_local':
|
||||
case 'emby_connect':
|
||||
case 'emby_all':
|
||||
$response = Requests::post($url . "/api/v1/Job/embyuserimporter", $headers, $options);
|
||||
break;
|
||||
case 'plex':
|
||||
$response = Requests::post($url . "/api/v1/Job/plexuserimporter", $headers, $options);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
if ($response->success) {
|
||||
$this->setLoggerChannel('Ombi')->info('Ran User Import');
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Ombi')->warning('Unsuccessful connection');
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Ombi')->error($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
trait HomepageFunctions
|
||||
{
|
||||
public function homepageCheckKeyPermissions($key, $permissions)
|
||||
{
|
||||
if (array_key_exists($key, $permissions)) {
|
||||
return $permissions[$key];
|
||||
} elseif ($key == 'all') {
|
||||
return $permissions;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function getHomepageSettingsList()
|
||||
{
|
||||
$methods = get_class_methods($this);
|
||||
$searchTerm = 'SettingsArray';
|
||||
return array_filter($methods, function ($k) use ($searchTerm) {
|
||||
return stripos($k, $searchTerm) !== false;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public function getHomepageSettingsCombined()
|
||||
{
|
||||
$list = $this->getHomepageSettingsList();
|
||||
$combined = [];
|
||||
foreach ($list as $item) {
|
||||
$combined[] = $this->$item(true);
|
||||
}
|
||||
return $combined;
|
||||
}
|
||||
|
||||
public function homepageItemPermissions($settings = false, $api = false)
|
||||
{
|
||||
if (!$settings) {
|
||||
if ($api) {
|
||||
$this->setAPIResponse('error', 'No settings were supplied', 422);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
foreach ($settings as $type => $setting) {
|
||||
$settingsType = gettype($setting);
|
||||
switch ($type) {
|
||||
case 'enabled':
|
||||
if ($settingsType == 'string') {
|
||||
if (!$this->config[$setting]) {
|
||||
if ($api) {
|
||||
$this->setAPIResponse('error', $setting . ' module is not enabled', 409);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
foreach ($setting as $item) {
|
||||
if (!$this->config[$item]) {
|
||||
if ($api) {
|
||||
$this->setAPIResponse('error', $item . ' module is not enabled', 409);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'auth':
|
||||
if ($settingsType == 'string') {
|
||||
if (!$this->qualifyRequest($this->config[$setting])) {
|
||||
if ($api) {
|
||||
$this->setAPIResponse('error', 'User not approved to view this homepage item', 401);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
foreach ($setting as $item) {
|
||||
if (!$this->qualifyRequest($this->config[$item])) {
|
||||
if ($api) {
|
||||
$this->setAPIResponse('error', 'User not approved to view this homepage item', 401);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'not_empty':
|
||||
if ($settingsType == 'string') {
|
||||
if (empty($this->config[$setting])) {
|
||||
if ($api) {
|
||||
$this->setAPIResponse('error', $setting . ' was not supplied', 422);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
foreach ($setting as $item) {
|
||||
if (empty($this->config[$item])) {
|
||||
if ($api) {
|
||||
$this->setAPIResponse('error', $item . ' was not supplied', 422);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
//return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
<?php
|
||||
|
||||
trait LogFunctions
|
||||
{
|
||||
public function logLocation()
|
||||
{
|
||||
return isset($this->config['logLocation']) && $this->config['logLocation'] !== '' ? $this->config['logLocation'] : $this->config['dbLocation'] . 'logs' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
public function debug($msg, $context = [])
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->debug($msg, $context);
|
||||
}
|
||||
}
|
||||
|
||||
public function info($msg, $context = [])
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->info($msg, $context);
|
||||
}
|
||||
}
|
||||
|
||||
public function notice($msg, $context = [])
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->notice($msg, $context);
|
||||
}
|
||||
}
|
||||
|
||||
public function warning($msg, $context = [])
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->warning($msg, $context);
|
||||
}
|
||||
}
|
||||
|
||||
public function error($msg, $context = [])
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->error($msg, $context);
|
||||
}
|
||||
}
|
||||
|
||||
public function critical($msg, $context = [])
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->critical($msg, $context);
|
||||
}
|
||||
}
|
||||
|
||||
public function alert($msg, $context = [])
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->alert($msg, $context);
|
||||
}
|
||||
}
|
||||
|
||||
public function emergency($msg, $context = [])
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->emergency($msg, $context);
|
||||
}
|
||||
}
|
||||
|
||||
public function setOrganizrLog()
|
||||
{
|
||||
if ($this->hasDB()) {
|
||||
$this->makeDir($this->logLocation());
|
||||
$logPath = $this->logLocation();
|
||||
return $logPath . 'organizr.log';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function readLog($file, $pageSize = 10, $offset = 0, $filter = 'NONE', $trace_id = null)
|
||||
{
|
||||
$combinedLogs = false;
|
||||
if ($file == 'combined-logs') {
|
||||
$combinedLogs = true;
|
||||
}
|
||||
if (file_exists($file) || $combinedLogs) {
|
||||
$filter = strtoupper($filter);
|
||||
switch ($filter) {
|
||||
case 'DEBUG':
|
||||
case 'INFO':
|
||||
case 'NOTICE':
|
||||
case 'WARNING':
|
||||
case 'ERROR':
|
||||
case 'CRITICAL':
|
||||
case 'ALERT':
|
||||
case 'EMERGENCY':
|
||||
break;
|
||||
case 'NONE':
|
||||
$filter = null;
|
||||
break;
|
||||
default:
|
||||
$filter = 'DEBUG';
|
||||
break;
|
||||
}
|
||||
if ($combinedLogs) {
|
||||
$logs = $this->getLogFiles();
|
||||
$lines = [];
|
||||
if ($logs) {
|
||||
foreach ($logs as $log) {
|
||||
if (file_exists($log)) {
|
||||
$lineGenerator = Bcremer\LineReader\LineReader::readLinesBackwards($log);
|
||||
$lines = array_merge($lines, iterator_to_array($lineGenerator));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$lineGenerator = Bcremer\LineReader\LineReader::readLinesBackwards($file);
|
||||
$lines = iterator_to_array($lineGenerator);
|
||||
}
|
||||
if ($filter || $trace_id) {
|
||||
$results = [];
|
||||
foreach ($lines as $line) {
|
||||
if ($filter) {
|
||||
if (stripos($line, '"' . $filter . '"') !== false) {
|
||||
$results[] = $line;
|
||||
}
|
||||
} elseif ($trace_id) {
|
||||
if (stripos($line, '"' . $trace_id . '"') !== false) {
|
||||
$results = $line;
|
||||
}
|
||||
}
|
||||
}
|
||||
$lines = $results;
|
||||
}
|
||||
return $this->formatLogResults($lines, $pageSize, $offset);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function formatLogResults($lines, $pageSize, $offset)
|
||||
{
|
||||
if (is_array($lines)) {
|
||||
$totalLines = count($lines);
|
||||
$totalPages = $totalLines / $pageSize;
|
||||
$results = array_slice($lines, $offset, $pageSize);
|
||||
$lines = [];
|
||||
foreach ($results as $line) {
|
||||
$lines[] = json_decode($line, true);
|
||||
}
|
||||
return [
|
||||
'pageInfo' => [
|
||||
'results' => $totalLines,
|
||||
'totalPages' => ceil($totalPages),
|
||||
'pageSize' => $pageSize,
|
||||
'page' => $offset >= $totalPages ? -1 : ceil($offset / $pageSize) + 1
|
||||
],
|
||||
'results' => $lines
|
||||
];
|
||||
} else {
|
||||
return json_decode($lines, true);
|
||||
}
|
||||
}
|
||||
|
||||
public function getLatestLogFile()
|
||||
{
|
||||
if ($this->logFile) {
|
||||
if (isset($this->logFile)) {
|
||||
$folder = $this->logLocation();
|
||||
$directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
|
||||
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
|
||||
$files = [];
|
||||
foreach ($iteratorIterator as $info) {
|
||||
$files[] = $info->getPathname();
|
||||
}
|
||||
if (count($files) > 0) {
|
||||
usort($files, function ($x, $y) {
|
||||
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $x, $xArray);
|
||||
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $y, $yArray);
|
||||
return strtotime($xArray[0]) < strtotime($yArray[0]);
|
||||
});
|
||||
if (file_exists($files[0])) {
|
||||
return $files[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getLogFiles()
|
||||
{
|
||||
if ($this->logFile) {
|
||||
if (isset($this->logFile)) {
|
||||
$folder = $this->logLocation();
|
||||
$directoryIterator = new RecursiveDirectoryIterator($folder, FilesystemIterator::SKIP_DOTS);
|
||||
$iteratorIterator = new RecursiveIteratorIterator($directoryIterator);
|
||||
$files = [];
|
||||
foreach ($iteratorIterator as $info) {
|
||||
$files[] = $info->getPathname();
|
||||
}
|
||||
if (count($files) > 0) {
|
||||
usort($files, function ($x, $y) {
|
||||
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $x, $xArray);
|
||||
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $y, $yArray);
|
||||
return strtotime($xArray[0]) < strtotime($yArray[0]);
|
||||
});
|
||||
return $files;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function log(...$params)
|
||||
{
|
||||
// Alias of setLoggerChannel
|
||||
return $this->setLoggerChannel(...$params);
|
||||
}
|
||||
|
||||
public function setLoggerChannel($channel = 'Organizr', $username = null)
|
||||
{
|
||||
|
||||
if ($this->hasDB()) {
|
||||
$channel = $channel ?: 'Organizr';
|
||||
$setLogger = false;
|
||||
if ($username) {
|
||||
$username = $this->sanitizeUserString($username);
|
||||
}
|
||||
if ($this->loggerSetup) {
|
||||
if ($channel) {
|
||||
if (strtolower($this->logger->getChannel()) !== strtolower($channel)) {
|
||||
$this->logger->setChannel($channel);
|
||||
$setLogger = true;
|
||||
}
|
||||
}
|
||||
if ($username) {
|
||||
$currentUsername = $this->logger->getTraceId() !== '' ? strtolower($this->logger->getTraceId()) : '';
|
||||
if ($currentUsername !== strtolower($username)) {
|
||||
$this->logger->setUsername($username);
|
||||
$setLogger = true;
|
||||
}
|
||||
}
|
||||
if ($setLogger) {
|
||||
return $this->setupLogger($channel, $username);
|
||||
} else {
|
||||
return $this->logger;
|
||||
}
|
||||
} else {
|
||||
return $this->setupLogger($channel, $username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getLogLevelClass($level, $slack = false)
|
||||
{
|
||||
switch ($level) {
|
||||
case 'DEBUG':
|
||||
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::DEBUG;
|
||||
break;
|
||||
case 'INFO':
|
||||
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::INFO;
|
||||
break;
|
||||
case 'NOTICE':
|
||||
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::NOTICE;
|
||||
break;
|
||||
case 'ERROR':
|
||||
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::ERROR;
|
||||
break;
|
||||
case 'CRITICAL':
|
||||
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::CRITICAL;
|
||||
break;
|
||||
case 'ALERT':
|
||||
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::ALERT;
|
||||
break;
|
||||
case 'EMERGENCY':
|
||||
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::EMERGENCY;
|
||||
break;
|
||||
default:
|
||||
$logLevel = Nekonomokochan\PhpJsonLogger\LoggerBuilder::WARNING;
|
||||
break;
|
||||
}
|
||||
if ($slack) {
|
||||
$organizrLogLevel = $this->getLogLevelClass($this->config['logLevel']);
|
||||
if ($logLevel < $organizrLogLevel) {
|
||||
$logLevel = $organizrLogLevel;
|
||||
}
|
||||
}
|
||||
return $logLevel;
|
||||
}
|
||||
|
||||
public function setupLogger($channel = 'Organizr', $username = null)
|
||||
{
|
||||
if (!$username) {
|
||||
$username = $this->user['username'] ?? 'System';
|
||||
}
|
||||
$loggerBuilder = new OrganizrLogger();
|
||||
$loggerBuilder->setReadyStatus($this->hasDB() && $this->logFile);
|
||||
$loggerBuilder->setMaxFiles($this->config['maxLogFiles']);
|
||||
$loggerBuilder->setFileName($this->tempLogIfNeeded());
|
||||
$loggerBuilder->setTraceId($username);
|
||||
$loggerBuilder->setChannel(ucwords(strtolower($channel)));
|
||||
$loggerBuilder->setLogLevel($this->getLogLevelClass($this->config['logLevel']));
|
||||
try {
|
||||
if ($this->config['sendLogsToSlack']) {
|
||||
if ($this->config['slackLogWebhook'] !== '') {
|
||||
$slackHandlerBuilder = new Nekonomokochan\PhpJsonLogger\SlackWebhookHandlerBuilder($this->config['slackLogWebhook'], $this->config['slackLogWebHookChannel']);
|
||||
$slackHandlerBuilder->setLevel($this->getLogLevelClass($this->config['slackLogLevel'], true));
|
||||
$loggerBuilder->setSlackWebhookHandler($slackHandlerBuilder->build());
|
||||
}
|
||||
}
|
||||
$this->logger = $loggerBuilder->build();
|
||||
$this->loggerSetup = true;
|
||||
return $this->logger;
|
||||
} catch (Exception $e) {
|
||||
// nothing so far
|
||||
return $this->logger;
|
||||
}
|
||||
/*
|
||||
Setup:
|
||||
set the log channel before you send log (You can set an optional Username (2nd Variable) | If user is logged already logged in, it will use their username):
|
||||
normal log:
|
||||
$this->log('Plex Homepage')->info('test');
|
||||
normal log with context ($context must be an array):
|
||||
$this->log('Plex Homepage')->info('test', $context);
|
||||
exception:
|
||||
$this->log('Plex Homepage')->critical($exception, $context);
|
||||
*/
|
||||
}
|
||||
|
||||
public function tempLogIfNeeded()
|
||||
{
|
||||
if (!$this->logFile) {
|
||||
return $this->root . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . 'organizr-' . $this->randString() . '.log';
|
||||
} else {
|
||||
return $this->logFile;
|
||||
}
|
||||
}
|
||||
|
||||
public function getLog($pageSize = 10, $offset = 0, $filter = 'NONE', $number = 0, $trace_id = null)
|
||||
{
|
||||
if ($this->logFile) {
|
||||
if (isset($this->logFile)) {
|
||||
if ($number !== 0) {
|
||||
if ($number == 'all' || $number == 'combined-logs') {
|
||||
$log = 'combined-logs';
|
||||
} else {
|
||||
$logs = $this->getLogFiles();
|
||||
$log = $logs[$number] ?? $this->getLatestLogFile();
|
||||
}
|
||||
} else {
|
||||
$log = $this->getLatestLogFile();
|
||||
}
|
||||
$readLog = $this->readLog($log, $pageSize, $offset, $filter, $trace_id);
|
||||
$msg = ($trace_id) ? 'Results for trace_id: ' . $trace_id : 'Results for log: ' . $log;
|
||||
$this->setResponse(200, $msg, $readLog);
|
||||
return $readLog;
|
||||
} else {
|
||||
$this->setResponse(404, 'Log not found');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setResponse(409, 'Logging not setup');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function purgeLog($number)
|
||||
{
|
||||
$this->setLoggerChannel('Logger');
|
||||
$this->logger->debug('Starting log purge function');
|
||||
if ($this->logFile) {
|
||||
$this->logger->debug('Checking if log id exists');
|
||||
if ($number !== 0) {
|
||||
if ($number == 'all' || $number == 'combined-logs') {
|
||||
$this->logger->debug('Cannot delete log [all] as it is not a real log');
|
||||
$this->setResponse(409, 'Cannot delete log [all] as it is not a real log');
|
||||
return false;
|
||||
}
|
||||
$logs = $this->getLogFiles();
|
||||
$file = $logs[$number] ?? false;
|
||||
if (!$file) {
|
||||
$this->setResponse(404, 'Log not found');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$file = $this->getLatestLogFile();
|
||||
}
|
||||
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $file, $log);
|
||||
$log = $log[0];
|
||||
$this->logger->debug('Checking if log exists');
|
||||
if (file_exists($file)) {
|
||||
$this->logger->debug('Log: ' . $log . ' does exist');
|
||||
$this->logger->debug('Attempting to purge log: ' . $log);
|
||||
if (unlink($file)) {
|
||||
$this->logger->info('Log: ' . $log . ' has been purged/deleted');
|
||||
$this->setResponse(200, 'Log purged');
|
||||
return true;
|
||||
} else {
|
||||
$this->logger->warning('Log: ' . $log . ' could not be purged/deleted');
|
||||
$this->setResponse(500, 'Log could not be purged');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->logger->debug('Log does not exist');
|
||||
$this->setResponse(404, 'Log does not exist');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setResponse(409, 'Logging not setup');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function logArray($context)
|
||||
{
|
||||
if (!is_array($context)) {
|
||||
if (is_string($context)) {
|
||||
return ['data' => $context];
|
||||
} else {
|
||||
$context = (string)$context;
|
||||
return ['data' => $context];
|
||||
}
|
||||
} else {
|
||||
return $context;
|
||||
}
|
||||
}
|
||||
|
||||
function buildLogDropdown()
|
||||
{
|
||||
$logs = $this->getLogFiles();
|
||||
//<select class='form-control settings-dropdown-box system-settings-menu'><option value=''>About</option></select>
|
||||
if ($logs) {
|
||||
if (count($logs) > 0) {
|
||||
$options = '';
|
||||
$i = 0;
|
||||
foreach ($logs as $k => $log) {
|
||||
$selected = $i == 0 ? 'selected' : '';
|
||||
preg_match('/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/', $log, $name);
|
||||
$options .= '<option data-id="' . $k . '" value="api/v2/log/' . $k . '?filter=NONE&pageSize=1000&offset=0" ' . $selected . '>' . $name[0] . '</option>';
|
||||
$i++;
|
||||
}
|
||||
return '<select class="form-control choose-organizr-log"><option data-id="all" value="api/v2/log/all?filter=NONE&pageSize=1000&offset=0">All</option>' . $options . '</select>';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function buildFilterDropdown()
|
||||
{
|
||||
$dropdownItems = '<li><a href="javascript:toggleLogFilter(\'DEBUG\')"><span lang="en">Debug</span></a></li>';
|
||||
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'INFO\')"><span lang="en">Info</span></a></li>';
|
||||
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'NOTICE\')"><span lang="en">Notice</span></a></li>';
|
||||
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'WARNING\')"><span lang="en">Warning</span></a></li>';
|
||||
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'ERROR\')"><span lang="en">Error</span></a></li>';
|
||||
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'CRITICAL\')"><span lang="en">Critical</span></a></li>';
|
||||
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'ALERT\')"><span lang="en">Alert</span></a></li>';
|
||||
$dropdownItems .= '<li><a href="javascript:toggleLogFilter(\'EMERGENCY\')"><span lang="en">Emergency</span></a></li>';
|
||||
$dropdownItems .= '<li class="divider"></li><li><a href="javascript:toggleLogFilter(\'NONE\')"><span lang="en">None</span></a></li>';
|
||||
return '<button aria-expanded="false" data-toggle="dropdown" class="btn btn-inverse dropdown-toggle waves-effect waves-light pull-right m-r-5 hidden-xs" type="button"> <span class="log-filter-text m-r-5" lang="en">NONE</span><i class="fa fa-filter m-r-5"></i></button><ul role="menu" class="dropdown-menu log-filter-dropdown pull-right">' . $dropdownItems . '</ul>';
|
||||
}
|
||||
|
||||
public function testConnectionSlackLogs()
|
||||
{
|
||||
if (!$this->config['sendLogsToSlack']) {
|
||||
$this->setResponse(409, 'sendLogsToSlack is disabled');
|
||||
return false;
|
||||
}
|
||||
if ($this->config['slackLogWebhook'] == '') {
|
||||
$this->setResponse(409, 'slackLogWebhook is empty');
|
||||
return false;
|
||||
}
|
||||
if ($this->config['slackLogWebHookChannel'] == '' && stripos($this->config['slackLogWebhook'], 'discord') === false) {
|
||||
$this->setResponse(409, 'slackLogWebhook is empty');
|
||||
return false;
|
||||
}
|
||||
$context = [
|
||||
'test' => 'success',
|
||||
];
|
||||
$this->setupLogger('Slack Tester', $this->user['username'])->warning('Warning Test', $context);
|
||||
$this->setResponse(200, 'Slack test connection completed - Please check Slack/Discord Channel');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
trait NetDataFunctions
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,917 @@
|
||||
<?php
|
||||
|
||||
trait NormalFunctions
|
||||
{
|
||||
public function formatSeconds($seconds)
|
||||
{
|
||||
$hours = 0;
|
||||
$milliseconds = str_replace("0.", '', $seconds - floor($seconds));
|
||||
if ($seconds > 3600) {
|
||||
$hours = floor($seconds / 3600);
|
||||
}
|
||||
$seconds = $seconds % 3600;
|
||||
$time = str_pad($hours, 2, '0', STR_PAD_LEFT)
|
||||
. gmdate(':i:s', $seconds)
|
||||
. ($milliseconds ? '.' . $milliseconds : '');
|
||||
$parts = explode(':', $time);
|
||||
$timeExtra = explode('.', $parts[2]);
|
||||
if ($parts[0] !== '00') { // hours
|
||||
return $time;
|
||||
} elseif ($parts[1] !== '00') { // mins
|
||||
return $parts[1] . 'min(s) ' . $timeExtra[0] . 's';
|
||||
} elseif ($timeExtra[0] !== '00') { // secs
|
||||
return substr($parts[2], 0, 5) . 's | ' . substr($parts[2], 0, 7) * 1000 . 'ms';
|
||||
} else {
|
||||
return substr($parts[2], 0, 7) * 1000 . 'ms';
|
||||
}
|
||||
//return $timeExtra[0] . 's ' . (number_format(('0.' . substr($timeExtra[1], 0, 4)), 4, '.', '') * 1000) . 'ms';
|
||||
//return (number_format(('0.' . substr($timeExtra[1], 0, 4)), 4, '.', '') * 1000) . 'ms';
|
||||
}
|
||||
|
||||
public function getExtension($string)
|
||||
{
|
||||
return preg_replace("#(.+)?\.(\w+)(\?.+)?#", "$2", $string);
|
||||
}
|
||||
|
||||
public function get_browser_name()
|
||||
{
|
||||
$user_agent = $_SERVER['HTTP_USER_AGENT'];
|
||||
if (strpos($user_agent, 'Opera') || strpos($user_agent, 'OPR/')) {
|
||||
return 'Opera';
|
||||
} elseif (strpos($user_agent, 'Edge')) {
|
||||
return 'Edge';
|
||||
} elseif (strpos($user_agent, 'Chrome')) {
|
||||
return 'Chrome';
|
||||
} elseif (strpos($user_agent, 'Safari')) {
|
||||
return 'Safari';
|
||||
} elseif (strpos($user_agent, 'Firefox')) {
|
||||
return 'Firefox';
|
||||
} elseif (strpos($user_agent, 'MSIE') || strpos($user_agent, 'Trident/7')) {
|
||||
return 'Internet Explorer';
|
||||
}
|
||||
return 'Other';
|
||||
}
|
||||
|
||||
public function array_filter_key(array $array, $callback)
|
||||
{
|
||||
$matchedKeys = array_filter(array_keys($array), $callback);
|
||||
return array_intersect_key($array, array_flip($matchedKeys));
|
||||
}
|
||||
|
||||
public function getOS()
|
||||
{
|
||||
if (PHP_SHLIB_SUFFIX == "dll") {
|
||||
return "win";
|
||||
} else {
|
||||
return "*nix";
|
||||
}
|
||||
}
|
||||
|
||||
// Get Gravatar Email Image
|
||||
public function gravatar($email = '')
|
||||
{
|
||||
$email = md5(strtolower(trim($email)));
|
||||
return "https://www.gravatar.com/avatar/$email?s=100&d=mm";
|
||||
}
|
||||
|
||||
// Clean Directory string
|
||||
public function cleanDirectory($path)
|
||||
{
|
||||
$path = str_replace(array('/', '\\'), '/', $path);
|
||||
if (substr($path, -1) != '/') {
|
||||
$path = $path . '/';
|
||||
}
|
||||
if ($path[0] != '/' && $path[1] != ':') {
|
||||
$path = '/' . $path;
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
// Print output all purrty
|
||||
public function prettyPrint($v, $error = false)
|
||||
{
|
||||
if ($error) {
|
||||
$background = 'red';
|
||||
$border = 'black';
|
||||
$text = 'white';
|
||||
} else {
|
||||
$background = '#f2f2f2';
|
||||
$border = 'black';
|
||||
$text = 'black';
|
||||
}
|
||||
$trace = debug_backtrace()[0];
|
||||
echo '<pre style="white-space: pre; text-overflow: ellipsis; overflow: hidden; color: ' . $text . '; background-color: ' . $background . '; border: 2px solid ' . $border . '; border-radius: 5px; padding: 5px; margin: 5px;">' . $trace['file'] . ':' . $trace['line'] . ' ' . gettype($v) . "\n\n" . print_r($v, 1) . '</pre><br/>';
|
||||
}
|
||||
|
||||
public function gen_uuid()
|
||||
{
|
||||
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
// 32 bits for "time_low"
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
// 16 bits for "time_mid"
|
||||
mt_rand(0, 0xffff),
|
||||
// 16 bits for "time_hi_and_version",
|
||||
// four most significant bits holds version number 4
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
// 16 bits, 8 bits for "clk_seq_hi_res",
|
||||
// 8 bits for "clk_seq_low",
|
||||
// two most significant bits holds zero and one for variant DCE1.1
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
// 48 bits for "node"
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
|
||||
public function dbExtension($string)
|
||||
{
|
||||
return (substr($string, -3) == '.db') ? $string : $string . '.db';
|
||||
}
|
||||
|
||||
public function removeDbExtension($string)
|
||||
{
|
||||
return substr($string, 0, -3);
|
||||
}
|
||||
|
||||
public function cleanPath($path)
|
||||
{
|
||||
$path = preg_replace('/([^:])(\/{2,})/', '$1/', $path);
|
||||
$path = rtrim($path, '/');
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function searchArray($array, $field, $value)
|
||||
{
|
||||
foreach ($array as $key => $item) {
|
||||
if ($item[$field] === $value)
|
||||
return $key;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function localURL($url, $force = false)
|
||||
{
|
||||
if ($force) {
|
||||
return true;
|
||||
}
|
||||
if (strpos($url, 'https') !== false) {
|
||||
preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/", $url, $result);
|
||||
$result = !empty($result);
|
||||
return $result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function arrayIP($string)
|
||||
{
|
||||
if (strpos($string, ',') !== false) {
|
||||
$result = explode(",", $string);
|
||||
} else {
|
||||
$result = array($string);
|
||||
}
|
||||
foreach ($result as &$ip) {
|
||||
$ip = is_numeric(substr($ip, 0, 1)) ? $ip : gethostbyname($ip);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function timeExecution($previous = null)
|
||||
{
|
||||
if (!$previous) {
|
||||
return microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"];
|
||||
} else {
|
||||
return (microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]) - $previous;
|
||||
}
|
||||
}
|
||||
|
||||
public function getallheaders()
|
||||
{
|
||||
if (!function_exists('getallheaders')) {
|
||||
function getallheaders()
|
||||
{
|
||||
$headers = array();
|
||||
foreach ($_SERVER as $name => $value) {
|
||||
if (substr($name, 0, 5) == 'HTTP_') {
|
||||
$headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||
}
|
||||
}
|
||||
return $headers;
|
||||
}
|
||||
} else {
|
||||
return getallheaders();
|
||||
}
|
||||
}
|
||||
|
||||
public function getallheadersi()
|
||||
{
|
||||
return array_change_key_case($this->getallheaders(), CASE_LOWER);
|
||||
}
|
||||
|
||||
public function random_ascii_string($length)
|
||||
{
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$charactersLength = strlen($characters);
|
||||
$randomString = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters[rand(0, $charactersLength - 1)];
|
||||
}
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
// Generate Random string
|
||||
public function randString($length = 10, $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||
{
|
||||
$tmp = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$tmp .= substr(str_shuffle($chars), 0, 1);
|
||||
}
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
public function isEncrypted($password = null)
|
||||
{
|
||||
return ($password == null || $password == '') ? false : $this->decrypt($password);
|
||||
}
|
||||
|
||||
public function fillString($string, $length)
|
||||
{
|
||||
$filler = '0123456789abcdefghijklmnopqrstuvwxyz!@#$%^&*';
|
||||
if (strlen($string) < $length) {
|
||||
$diff = $length - strlen($string);
|
||||
$filler = substr($filler, 0, $diff);
|
||||
return $string . $filler;
|
||||
} elseif (strlen($string) > $length) {
|
||||
return substr($string, 0, $length);
|
||||
} else {
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
public function userIP()
|
||||
{
|
||||
if (isset($_SERVER['HTTP_CLIENT_IP'])) {
|
||||
$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
|
||||
} elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
} elseif (isset($_SERVER['HTTP_X_FORWARDED'])) {
|
||||
$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
|
||||
} elseif (isset($_SERVER['HTTP_FORWARDED_FOR'])) {
|
||||
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
|
||||
} elseif (isset($_SERVER['HTTP_FORWARDED'])) {
|
||||
$ipaddress = $_SERVER['HTTP_FORWARDED'];
|
||||
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
|
||||
$ipaddress = $_SERVER['REMOTE_ADDR'];
|
||||
} else {
|
||||
$ipaddress = '127.0.0.1';
|
||||
}
|
||||
if (strpos($ipaddress, ',') !== false) {
|
||||
list($first, $last) = explode(",", $ipaddress);
|
||||
unset($last);
|
||||
return $first;
|
||||
} else {
|
||||
return $ipaddress;
|
||||
}
|
||||
}
|
||||
|
||||
public function serverIP()
|
||||
{
|
||||
if (array_key_exists('SERVER_ADDR', $_SERVER)) {
|
||||
return $_SERVER['SERVER_ADDR'];
|
||||
}
|
||||
return '127.0.0.1';
|
||||
}
|
||||
|
||||
public function parseDomain($value, $force = false)
|
||||
{
|
||||
$badDomains = array('ddns.net', 'ddnsking.com', '3utilities.com', 'bounceme.net', 'freedynamicdns.net', 'freedynamicdns.org', 'gotdns.ch', 'hopto.org', 'myddns.me', 'myds.me', 'myftp.biz', 'myftp.org', 'myvnc.com', 'noip.com', 'onthewifi.com', 'redirectme.net', 'serveblog.net', 'servecounterstrike.com', 'serveftp.com', 'servegame.com', 'servehalflife.com', 'servehttp.com', 'serveirc.com', 'serveminecraft.net', 'servemp3.com', 'servepics.com', 'servequake.com', 'sytes.net', 'viewdns.net', 'webhop.me', 'zapto.org');
|
||||
$Domain = $value;
|
||||
$Port = strpos($Domain, ':');
|
||||
if ($Port !== false) {
|
||||
$Domain = substr($Domain, 0, $Port);
|
||||
$value = $Domain;
|
||||
}
|
||||
$check = substr_count($Domain, '.');
|
||||
if ($check >= 3) {
|
||||
if (is_numeric($Domain[0])) {
|
||||
$Domain = '';
|
||||
} else {
|
||||
if (in_array(strtolower(explode('.', $Domain)[2] . '.' . explode('.', $Domain)[3]), $badDomains)) {
|
||||
$Domain = '.' . explode('.', $Domain)[0] . '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2] . '.' . explode('.', $Domain)[3];
|
||||
} else {
|
||||
$Domain = '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2] . '.' . explode('.', $Domain)[3];
|
||||
}
|
||||
}
|
||||
} elseif ($check == 2) {
|
||||
if (in_array(strtolower(explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2]), $badDomains)) {
|
||||
$Domain = '.' . explode('.', $Domain)[0] . '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2];
|
||||
} elseif (explode('.', $Domain)[0] == 'www') {
|
||||
$Domain = '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2];
|
||||
} elseif (explode('.', $Domain)[1] == 'co') {
|
||||
$Domain = '.' . explode('.', $Domain)[0] . '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2];
|
||||
} else {
|
||||
$Domain = '.' . explode('.', $Domain)[1] . '.' . explode('.', $Domain)[2];
|
||||
}
|
||||
} elseif ($check == 1) {
|
||||
$Domain = '.' . $Domain;
|
||||
} else {
|
||||
$Domain = '';
|
||||
}
|
||||
return ($force) ? $value : $Domain;
|
||||
}
|
||||
|
||||
// Cookie Custom Function
|
||||
public function coookie($type, $name, $value = '', $days = -1, $http = true, $path = '/')
|
||||
{
|
||||
$days = ($days > 365) ? 365 : $days;
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
|
||||
$Secure = true;
|
||||
$HTTPOnly = true;
|
||||
} elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' && $_SERVER['HTTPS'] !== '') {
|
||||
$Secure = true;
|
||||
$HTTPOnly = true;
|
||||
} else {
|
||||
$Secure = false;
|
||||
$HTTPOnly = false;
|
||||
}
|
||||
if (!$http) {
|
||||
$HTTPOnly = false;
|
||||
}
|
||||
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$Domain = $this->parseDomain($_SERVER['HTTP_HOST']);
|
||||
$DomainTest = $this->parseDomain($_SERVER['HTTP_HOST'], true);
|
||||
if ($type == 'set') {
|
||||
$_COOKIE[$name] = $value;
|
||||
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
|
||||
. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
|
||||
. (empty($path) ? '' : '; path=' . $path)
|
||||
. (empty($Domain) ? '' : '; domain=' . $Domain)
|
||||
. (!$Secure ? '' : '; SameSite=None; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
|
||||
. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + (86400 * $days)) . ' GMT')
|
||||
. (empty($path) ? '' : '; path=' . $path)
|
||||
. (empty($Domain) ? '' : '; domain=' . $DomainTest)
|
||||
. (!$Secure ? '' : '; SameSite=None; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
} elseif ($type == 'delete') {
|
||||
unset($_COOKIE[$name]);
|
||||
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
|
||||
. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
|
||||
. (empty($path) ? '' : '; path=' . $path)
|
||||
. (empty($Domain) ? '' : '; domain=' . $Domain)
|
||||
. (!$Secure ? '' : '; SameSite=None; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
|
||||
. (empty($days) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
|
||||
. (empty($path) ? '' : '; path=' . $path)
|
||||
. (empty($Domain) ? '' : '; domain=' . $DomainTest)
|
||||
. (!$Secure ? '' : '; SameSite=None; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
}
|
||||
}
|
||||
|
||||
public function coookieSeconds($type, $name, $value = '', $ms = null, $http = true, $path = '/')
|
||||
{
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
|
||||
$Secure = true;
|
||||
$HTTPOnly = true;
|
||||
} elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' && $_SERVER['HTTPS'] !== '') {
|
||||
$Secure = true;
|
||||
$HTTPOnly = true;
|
||||
} else {
|
||||
$Secure = false;
|
||||
$HTTPOnly = false;
|
||||
}
|
||||
if (!$http) {
|
||||
$HTTPOnly = false;
|
||||
}
|
||||
$_SERVER['HTTP_HOST'] = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$Domain = $this->parseDomain($_SERVER['HTTP_HOST']);
|
||||
$DomainTest = $this->parseDomain($_SERVER['HTTP_HOST'], true);
|
||||
if ($type == 'set') {
|
||||
$_COOKIE[$name] = $value;
|
||||
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
|
||||
. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + ($ms / 1000)) . ' GMT')
|
||||
. (empty($path) ? '' : '; path=' . $path)
|
||||
. (empty($Domain) ? '' : '; domain=' . $Domain)
|
||||
. (!$Secure ? '' : '; SameSite=None; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
|
||||
. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() + ($ms / 1000)) . ' GMT')
|
||||
. (empty($path) ? '' : '; path=' . $path)
|
||||
. (empty($Domain) ? '' : '; domain=' . $DomainTest)
|
||||
. (!$Secure ? '' : '; SameSite=None; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
} elseif ($type == 'delete') {
|
||||
unset($_COOKIE[$name]);
|
||||
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
|
||||
. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
|
||||
. (empty($path) ? '' : '; path=' . $path)
|
||||
. (empty($Domain) ? '' : '; domain=' . $Domain)
|
||||
. (!$Secure ? '' : '; SameSite=None; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
header('Set-Cookie: ' . rawurlencode($name) . '=' . rawurlencode($value)
|
||||
. (empty($ms) ? '' : '; expires=' . gmdate('D, d-M-Y H:i:s', time() - 3600) . ' GMT')
|
||||
. (empty($path) ? '' : '; path=' . $path)
|
||||
. (empty($Domain) ? '' : '; domain=' . $DomainTest)
|
||||
. (!$Secure ? '' : '; SameSite=None; Secure')
|
||||
. (!$HTTPOnly ? '' : '; HttpOnly'), false);
|
||||
}
|
||||
}
|
||||
|
||||
// Qualify URL
|
||||
public function qualifyURL($url, $return = false, $includeTrailing = false)
|
||||
{
|
||||
//local address?
|
||||
if (substr($url, 0, 1) == "/") {
|
||||
if ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') || (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] != 'off') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] != 'http')) {
|
||||
$protocol = "https://";
|
||||
} else {
|
||||
$protocol = "http://";
|
||||
}
|
||||
$url = $protocol . $this->getServer() . $url;
|
||||
}
|
||||
// Get Digest
|
||||
$digest = $includeTrailing ? parse_url($url) : parse_url(rtrim(preg_replace('/\s+/', '', $url), '/'));
|
||||
// http/https
|
||||
if (!isset($digest['scheme'])) {
|
||||
$scheme = 'http';
|
||||
} else {
|
||||
$scheme = $digest['scheme'];
|
||||
}
|
||||
// Host
|
||||
$host = ($digest['host'] ?? '');
|
||||
// Port
|
||||
$port = (isset($digest['port']) ? ':' . $digest['port'] : '');
|
||||
// Path
|
||||
$path = ($digest['path'] ?? '');
|
||||
// Query
|
||||
$query = (isset($digest['query']) ? '?' . $digest['query'] : '');
|
||||
// Fragment
|
||||
$fragment = (isset($digest['fragment']) ? '#' . $digest['fragment'] : '');
|
||||
// Output
|
||||
$array = [
|
||||
'scheme' => $scheme,
|
||||
'host' => $host,
|
||||
'port' => $port,
|
||||
'path' => $path,
|
||||
'query' => $query,
|
||||
'fragment' => $fragment,
|
||||
'digest' => $digest
|
||||
];
|
||||
return ($return) ? $array : $scheme . '://' . $host . $port . $path . $query . $fragment;
|
||||
}
|
||||
|
||||
public function getServer($over = false)
|
||||
{
|
||||
if ($over) {
|
||||
if ($this->config['PHPMAILER-domain'] !== '') {
|
||||
return $this->config['PHPMAILER-domain'];
|
||||
}
|
||||
}
|
||||
return isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : $_SERVER["SERVER_NAME"];
|
||||
}
|
||||
|
||||
public function getServerPath($over = false)
|
||||
{
|
||||
if ($over) {
|
||||
if ($this->config['PHPMAILER-domain'] !== '') {
|
||||
return $this->config['PHPMAILER-domain'];
|
||||
}
|
||||
}
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
|
||||
$protocol = "https://";
|
||||
} elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
|
||||
$protocol = "https://";
|
||||
} else {
|
||||
$protocol = "http://";
|
||||
}
|
||||
$domain = '';
|
||||
if (isset($_SERVER['SERVER_NAME']) && strpos($_SERVER['SERVER_NAME'], '.') !== false) {
|
||||
$domain = $_SERVER['SERVER_NAME'];
|
||||
} elseif (isset($_SERVER['HTTP_HOST'])) {
|
||||
if (strpos($_SERVER['HTTP_HOST'], ':') !== false) {
|
||||
$domain = explode(':', $_SERVER['HTTP_HOST'])[0];
|
||||
$port = explode(':', $_SERVER['HTTP_HOST'])[1];
|
||||
if ($port !== "80" && $port !== "443") {
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
}
|
||||
} else {
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
}
|
||||
}
|
||||
$path = str_replace("\\", "/", dirname($_SERVER['REQUEST_URI']));
|
||||
$path = ($path !== '.') ? $path : '';
|
||||
$url = $protocol . $domain . $path;
|
||||
if (strpos($url, '/api') !== false) {
|
||||
$url = explode('/api', $url);
|
||||
return $url[0] . '/';
|
||||
} else {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
public function convertIPToRange($ip)
|
||||
{
|
||||
$ip = trim($ip);
|
||||
if (strpos($ip, '/') !== false) {
|
||||
$explodeIP = explode('/', $ip);
|
||||
$prefix = $explodeIP[1];
|
||||
$start_ip = $explodeIP[0];
|
||||
$explodeStart = explode('.', $start_ip);
|
||||
if (count($explodeStart) == 4) {
|
||||
$explodeStart[3] = $prefix == 32 ? $explodeStart[3] : 0;
|
||||
$start_ip = implode('.', $explodeStart);
|
||||
}
|
||||
$ip_count = 1 << (32 - $prefix);
|
||||
$start_ip_long = long2ip(ip2long($start_ip));
|
||||
$last_ip_long = long2ip(ip2long($start_ip) + $ip_count - 1);
|
||||
} elseif (substr_count($ip, '.') == 3) {
|
||||
$start_ip_long = long2ip(ip2long($ip));
|
||||
$last_ip_long = long2ip(ip2long($ip));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return [
|
||||
'from' => $start_ip_long,
|
||||
'to' => $last_ip_long
|
||||
];
|
||||
}
|
||||
|
||||
public function convertIPStringToRange($string = null)
|
||||
{
|
||||
$ips = [];
|
||||
if ($string) {
|
||||
$ipListing = explode(',', $string);
|
||||
if (count($ipListing) > 0) {
|
||||
foreach ($ipListing as $ip) {
|
||||
$ips[] = $this->convertIPToRange($ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ips;
|
||||
}
|
||||
|
||||
public function localIPRanges()
|
||||
{
|
||||
$mainArray = [
|
||||
[
|
||||
'from' => '10.0.0.0',
|
||||
'to' => '10.255.255.255'
|
||||
],
|
||||
[
|
||||
'from' => '172.16.0.0',
|
||||
'to' => '172.31.255.255'
|
||||
],
|
||||
[
|
||||
'from' => '192.168.0.0',
|
||||
'to' => '192.168.255.255'
|
||||
],
|
||||
[
|
||||
'from' => '127.0.0.1',
|
||||
'to' => '127.255.255.255'
|
||||
],
|
||||
];
|
||||
if (isset($this->config['localIPList'])) {
|
||||
if ($this->config['localIPList'] !== '') {
|
||||
$ipListing = explode(',', $this->config['localIPList']);
|
||||
if (count($ipListing) > 0) {
|
||||
foreach ($ipListing as $ip) {
|
||||
$ipInfo = $this->convertIPToRange($ip);
|
||||
if ($ipInfo) {
|
||||
array_push($mainArray, $ipInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
if ($this->config['localIPFrom']) {
|
||||
$from = trim($this->config['localIPFrom']);
|
||||
$override = true;
|
||||
}
|
||||
if ($this->config['localIPTo']) {
|
||||
$to = trim($this->config['localIPTo']);
|
||||
}
|
||||
|
||||
if ($override) {
|
||||
$newArray = array(
|
||||
'from' => $from,
|
||||
'to' => (isset($to)) ? $to : $from
|
||||
);
|
||||
array_push($mainArray, $newArray);
|
||||
}
|
||||
*/
|
||||
return $mainArray;
|
||||
}
|
||||
|
||||
public function isLocal($checkIP = null)
|
||||
{
|
||||
$isLocal = false;
|
||||
$userIP = ($checkIP) ? ip2long($checkIP) : ip2long($this->userIP());
|
||||
$range = $this->localIPRanges();
|
||||
foreach ($range as $ip) {
|
||||
$low = ip2long($ip['from']);
|
||||
$high = ip2long($ip['to']);
|
||||
if ($userIP <= $high && $low <= $userIP) {
|
||||
$isLocal = true;
|
||||
}
|
||||
}
|
||||
return $isLocal;
|
||||
}
|
||||
|
||||
public function isLocalOrServer()
|
||||
{
|
||||
$isLocalOrServer = false;
|
||||
$isLocal = $this->isLocal();
|
||||
if (!$isLocal) {
|
||||
if ($this->userIP() == $this->serverIP()) {
|
||||
$isLocalOrServer = true;
|
||||
}
|
||||
} else {
|
||||
$isLocalOrServer = true;
|
||||
}
|
||||
return $isLocalOrServer;
|
||||
}
|
||||
|
||||
public function human_filesize($bytes, $dec = 2)
|
||||
{
|
||||
$bytes = number_format($bytes, 0, '.', '');
|
||||
$size = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
|
||||
$factor = floor((strlen($bytes) - 1) / 3);
|
||||
return sprintf("%.{$dec}f %s", $bytes / (1024 ** $factor), $size[$factor]);
|
||||
}
|
||||
|
||||
public function apiResponseFormatter($response)
|
||||
{
|
||||
if (is_array($response)) {
|
||||
return $response;
|
||||
}
|
||||
if (empty($response) || $response == '') {
|
||||
return ['api_response' => 'No data'];
|
||||
}
|
||||
if ($this->json_validator($response)) {
|
||||
return json_decode($response, true);
|
||||
}
|
||||
return ['api_response' => 'No data'];
|
||||
}
|
||||
|
||||
public function json_validator($data = null)
|
||||
{
|
||||
if (!empty($data)) {
|
||||
@json_decode($data);
|
||||
return (json_last_error() === JSON_ERROR_NONE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function replace_first($search_str, $replacement_str, $src_str)
|
||||
{
|
||||
return (false !== ($pos = strpos($src_str, $search_str))) ? substr_replace($src_str, $replacement_str, $pos, strlen($search_str)) : $src_str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an array is a multidimensional array.
|
||||
*
|
||||
* @param array $arr The array to check
|
||||
* @return boolean Whether the the array is a multidimensional array or not
|
||||
*/
|
||||
public function is_multi_array($x)
|
||||
{
|
||||
if (count(array_filter($x, 'is_array')) > 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an object to an array.
|
||||
*
|
||||
* @param array $object The object to convert
|
||||
* @return array The converted array
|
||||
*/
|
||||
public function object_to_array($object)
|
||||
{
|
||||
if (!is_object($object) && !is_array($object)) return $object;
|
||||
return array_map(array($this, 'object_to_array'), (array)$object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value exists in the array/object.
|
||||
*
|
||||
* @param mixed $needle The value that you are searching for
|
||||
* @param mixed $haystack The array/object to search
|
||||
* @param boolean $strict Whether to use strict search or not
|
||||
* @return boolean Whether the value was found or not
|
||||
*/
|
||||
public function search_for_value($needle, $haystack, $strict = true)
|
||||
{
|
||||
$haystack = $this->object_to_array($haystack);
|
||||
if (is_array($haystack)) {
|
||||
if ($this->is_multi_array($haystack)) { // Multidimensional array
|
||||
foreach ($haystack as $subhaystack) {
|
||||
if ($this->search_for_value($needle, $subhaystack, $strict)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} elseif (array_keys($haystack) !== range(0, count($haystack) - 1)) { // Associative array
|
||||
foreach ($haystack as $key => $val) {
|
||||
if ($needle == $val && !$strict) {
|
||||
return true;
|
||||
} elseif ($needle === $val && $strict) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else { // Normal array
|
||||
if ($needle == $haystack && !$strict) {
|
||||
return true;
|
||||
} elseif ($needle === $haystack && $strict) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function makeDir($dirPath, $mode = 0777)
|
||||
{
|
||||
return is_dir($dirPath) || @mkdir($dirPath, $mode, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Leave for deluge class
|
||||
function getCert()
|
||||
{
|
||||
$url = 'http://curl.haxx.se/ca/cacert.pem';
|
||||
$file = __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'cacert.pem';
|
||||
$file2 = __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'cacert-initial.pem';
|
||||
$useCert = (file_exists($file)) ? $file : $file2;
|
||||
$context = stream_context_create(
|
||||
array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => true,
|
||||
'cafile' => $useCert
|
||||
)
|
||||
)
|
||||
);
|
||||
if (!file_exists($file)) {
|
||||
file_put_contents($file, fopen($url, 'r', false, $context));
|
||||
} elseif (file_exists($file) && time() - 2592000 > filemtime($file)) {
|
||||
file_put_contents($file, fopen($url, 'r', false, $context));
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
// Leave for deluge class
|
||||
function localURL($url, $force = false)
|
||||
{
|
||||
if ($force) {
|
||||
return true;
|
||||
}
|
||||
if (strpos($url, 'https') !== false) {
|
||||
preg_match("/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/", $url, $result);
|
||||
$result = (!empty($result) ? true : false);
|
||||
return $result;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Maybe use later?
|
||||
function curl($curl, $url, $headers = array(), $data = array())
|
||||
{
|
||||
// Initiate cURL
|
||||
$curlReq = curl_init($url);
|
||||
if (in_array(trim(strtoupper($curl)), ["GET", "POST", "PUT", "DELETE"])) {
|
||||
curl_setopt($curlReq, CURLOPT_CUSTOMREQUEST, trim(strtoupper($curl)));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
curl_setopt($curlReq, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($curlReq, CURLOPT_CAINFO, getCert());
|
||||
curl_setopt($curlReq, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
if (localURL($url)) {
|
||||
curl_setopt($curlReq, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($curlReq, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
}
|
||||
// Format Headers
|
||||
$cHeaders = array();
|
||||
foreach ($headers as $k => $v) {
|
||||
$cHeaders[] = $k . ': ' . $v;
|
||||
}
|
||||
if (count($cHeaders)) {
|
||||
curl_setopt($curlReq, CURLOPT_HTTPHEADER, $cHeaders);
|
||||
}
|
||||
// Format Data
|
||||
switch (isset($headers['Content-Type']) ? $headers['Content-Type'] : '') {
|
||||
case 'application/json':
|
||||
curl_setopt($curlReq, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
break;
|
||||
case 'application/x-www-form-urlencoded':
|
||||
curl_setopt($curlReq, CURLOPT_POSTFIELDS, http_build_query($data));
|
||||
break;
|
||||
default:
|
||||
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
curl_setopt($curlReq, CURLOPT_POSTFIELDS, http_build_query($data));
|
||||
}
|
||||
// Execute
|
||||
$result = curl_exec($curlReq);
|
||||
$httpcode = curl_getinfo($curlReq);
|
||||
// Close
|
||||
curl_close($curlReq);
|
||||
// Return
|
||||
return array('content' => $result, 'http_code' => $httpcode);
|
||||
}
|
||||
|
||||
// Maybe use later?
|
||||
function getHeaders($url)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_NOBODY, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
|
||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
|
||||
curl_setopt($ch, CURLOPT_CAINFO, getCert());
|
||||
if (localURL($url)) {
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
}
|
||||
curl_exec($ch);
|
||||
$headers = curl_getinfo($ch);
|
||||
curl_close($ch);
|
||||
return $headers;
|
||||
}
|
||||
|
||||
// Maybe use later?
|
||||
function download($url, $path)
|
||||
{
|
||||
ini_set('max_execution_time', 0);
|
||||
set_time_limit(0);
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 1);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($ch, CURLOPT_CAINFO, getCert());
|
||||
if (localURL($url)) {
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
}
|
||||
$raw_file_data = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
file_put_contents($path, $raw_file_data);
|
||||
return (filesize($path) > 0) ? true : false;
|
||||
}
|
||||
|
||||
// swagger
|
||||
function getServerPath($over = false)
|
||||
{
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == "https") {
|
||||
$protocol = "https://";
|
||||
} elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off') {
|
||||
$protocol = "https://";
|
||||
} else {
|
||||
$protocol = "http://";
|
||||
}
|
||||
$domain = '';
|
||||
if (isset($_SERVER['SERVER_NAME']) && strpos($_SERVER['SERVER_NAME'], '.') !== false) {
|
||||
$domain = $_SERVER['SERVER_NAME'];
|
||||
} elseif (isset($_SERVER['HTTP_HOST'])) {
|
||||
if (strpos($_SERVER['HTTP_HOST'], ':') !== false) {
|
||||
$domain = explode(':', $_SERVER['HTTP_HOST'])[0];
|
||||
$port = explode(':', $_SERVER['HTTP_HOST'])[1];
|
||||
if ($port !== "80" && $port !== "443") {
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
}
|
||||
} else {
|
||||
$domain = $_SERVER['HTTP_HOST'];
|
||||
}
|
||||
}
|
||||
$url = $protocol . $domain . str_replace("\\", "/", dirname($_SERVER['REQUEST_URI']));
|
||||
if (strpos($url, '/api') !== false) {
|
||||
$url = explode('/api', $url);
|
||||
return $url[0] . '/';
|
||||
} else {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
// used for api return
|
||||
function safe_json_encode($value, $options = 0, $depth = 512)
|
||||
{
|
||||
$encoded = json_encode($value, $options, $depth);
|
||||
if ($encoded === false && $value && json_last_error() == JSON_ERROR_UTF8) {
|
||||
$encoded = json_encode(utf8ize($value), $options, $depth);
|
||||
}
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
// used for api return
|
||||
function utf8ize($mixed)
|
||||
{
|
||||
if (is_array($mixed)) {
|
||||
foreach ($mixed as $key => $value) {
|
||||
$mixed[$key] = utf8ize($value);
|
||||
}
|
||||
} elseif (is_string($mixed)) {
|
||||
return mb_convert_encoding($mixed, "UTF-8", "UTF-8");
|
||||
}
|
||||
return $mixed;
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
trait OAuthFunctions
|
||||
{
|
||||
public function traktOAuth()
|
||||
{
|
||||
$provider = new Bogstag\OAuth2\Client\Provider\Trakt(
|
||||
[
|
||||
'clientId' => $this->config['traktClientId'],
|
||||
'clientSecret' => $this->config['traktClientSecret'],
|
||||
'redirectUri' => $this->getServerPath() . 'api/v2/oauth/trakt'
|
||||
],
|
||||
[
|
||||
'httpClient' => new GuzzleHttp\Client(['verify' => $this->getCert()]),
|
||||
]
|
||||
);
|
||||
if (!isset($_GET['code'])) {
|
||||
$authUrl = $provider->getAuthorizationUrl();
|
||||
header('Location: ' . $authUrl);
|
||||
exit;
|
||||
} elseif (empty($_GET['state'])) {
|
||||
exit('Invalid state');
|
||||
} else {
|
||||
try {
|
||||
$token = $provider->getAccessToken('authorization_code', [
|
||||
'code' => $_GET['code']
|
||||
]);
|
||||
$traktDetails = [
|
||||
'traktAccessToken' => $token->getToken(),
|
||||
'traktAccessTokenExpires' => gmdate('Y-m-d\TH:i:s\Z', $token->getExpires()),
|
||||
'traktRefreshToken' => $token->getRefreshToken()
|
||||
];
|
||||
$this->updateConfig($traktDetails);
|
||||
echo '
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" href="' . $this->getServerPath() . '/css/mvp.css">
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="Trakt OAuth">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Trakt OAuth</title>
|
||||
</head>
|
||||
<script language=javascript>
|
||||
function closemyself() {
|
||||
window.opener=self;
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
<body onLoad="setTimeout(\'closemyself()\',3000);">
|
||||
<main>
|
||||
<section>
|
||||
<aside>
|
||||
<h3>Details Saved</h3>
|
||||
<p><sup>(This window will close automatically)</sup></p>
|
||||
</aside>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
';
|
||||
exit;
|
||||
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
|
||||
exit($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function traktOAuthRefresh()
|
||||
{
|
||||
$exp = $this->config['traktAccessTokenExpires'];
|
||||
$exp = date('Y-m-d\TH:i:s\Z', strtotime($exp . ' - 30 days'));
|
||||
if (time() - 2592000 > strtotime($exp)) {
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json'
|
||||
];
|
||||
$data = [
|
||||
'refresh_token' => $this->config['traktRefreshToken'],
|
||||
'clientId' => $this->config['traktClientId'],
|
||||
'clientSecret' => $this->config['traktClientSecret'],
|
||||
'redirectUri' => $this->getServerPath() . 'api/v2/oauth/trakt',
|
||||
'grant_type' => 'refresh_token'
|
||||
];
|
||||
$url = $this->qualifyURL('https://api.trakt.tv/oauth/token');
|
||||
try {
|
||||
$response = Requests::post($url, $headers, json_encode($data), []);
|
||||
if ($response->success) {
|
||||
$data = json_decode($response->body, true);
|
||||
$newExp = date('Y-m-d\TH:i:s\Z', strtotime($this->currentTime . ' + 90 days'));
|
||||
$traktDetails = [
|
||||
'traktAccessToken' => $data['access_token'],
|
||||
'traktAccessTokenExpires' => $newExp,
|
||||
'traktRefreshToken' => $data['refresh_token']
|
||||
];
|
||||
$this->updateConfig($traktDetails);
|
||||
return true;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Trakt')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,918 @@
|
||||
<?php
|
||||
|
||||
trait OrganizrFunctions
|
||||
{
|
||||
public function docs($path): string
|
||||
{
|
||||
return 'https://organizr.gitbook.io/organizr/' . $path;
|
||||
}
|
||||
|
||||
public function loadResources($files = [], $rootPath = '')
|
||||
{
|
||||
$scripts = '';
|
||||
if (count($files) > 0) {
|
||||
foreach ($files as $file) {
|
||||
if (strtolower(pathinfo($file, PATHINFO_EXTENSION)) == 'js') {
|
||||
$scripts .= $this->loadJavaResource($file, $rootPath);
|
||||
} elseif (strtolower(pathinfo($file, PATHINFO_EXTENSION)) == 'css') {
|
||||
$scripts .= $this->loadStyleResource($file, $rootPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
public function loadJavaResource($file = '', $rootPath = '')
|
||||
{
|
||||
return ($file !== '') ? '<script src="' . $rootPath . $file . '?v=' . trim($this->fileHash) . '"></script>' . "\n" : '';
|
||||
}
|
||||
|
||||
public function loadStyleResource($file = '', $rootPath = '')
|
||||
{
|
||||
return ($file !== '') ? '<link href="' . $rootPath . $file . '?v=' . trim($this->fileHash) . '" rel="stylesheet">' . "\n" : '';
|
||||
}
|
||||
|
||||
public function loadDefaultJavascriptFiles()
|
||||
{
|
||||
$javaFiles = [
|
||||
'js/jquery-2.2.4.min.js',
|
||||
'bootstrap/dist/js/bootstrap.min.js',
|
||||
'plugins/bower_components/sidebar-nav/dist/sidebar-nav.min.js',
|
||||
'js/jquery.slimscroll.js',
|
||||
'plugins/bower_components/styleswitcher/jQuery.style.switcher.js',
|
||||
'plugins/bower_components/moment/moment.js',
|
||||
'plugins/bower_components/moment/moment-timezone.js',
|
||||
'plugins/bower_components/jquery-wizard-master/dist/jquery-wizard.min.js',
|
||||
'plugins/bower_components/jquery-wizard-master/libs/formvalidation/formValidation.min.js',
|
||||
'plugins/bower_components/jquery-wizard-master/libs/formvalidation/bootstrap.min.js',
|
||||
'js/bowser.min.js',
|
||||
'js/jasny-bootstrap.js'
|
||||
];
|
||||
$scripts = '';
|
||||
foreach ($javaFiles as $file) {
|
||||
$scripts .= '<script src="' . $file . '?v=' . trim($this->fileHash) . '"></script>' . "\n";
|
||||
}
|
||||
return $scripts;
|
||||
}
|
||||
|
||||
public function loadJavascriptFile($file)
|
||||
{
|
||||
return '<script>loadJavascript("' . $file . '?v=' . trim($this->fileHash) . '");' . "</script>\n";
|
||||
}
|
||||
|
||||
public function embyJoinAPI($array)
|
||||
{
|
||||
$username = ($array['username']) ?? null;
|
||||
$email = ($array['email']) ?? null;
|
||||
$password = ($array['password']) ?? null;
|
||||
if (!$username) {
|
||||
$this->setAPIResponse('error', 'Username not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$email) {
|
||||
$this->setAPIResponse('error', 'Email not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$password) {
|
||||
$this->setAPIResponse('error', 'Password not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
return $this->embyJoin($username, $email, $password);
|
||||
}
|
||||
|
||||
public function embyJoin($username, $email, $password)
|
||||
{
|
||||
try {
|
||||
#create user in emby.
|
||||
$headers = array(
|
||||
"Accept" => "application/json"
|
||||
);
|
||||
$data = array();
|
||||
$url = $this->config['embyURL'] . '/emby/Users/New?name=' . $username . '&api_key=' . $this->config['embyToken'];
|
||||
$response = Requests::Post($url, $headers, json_encode($data), array());
|
||||
$response = $response->body;
|
||||
//return($response);
|
||||
$response = json_decode($response, true);
|
||||
//return($response);
|
||||
$userID = $response["Id"];
|
||||
//return($userID);
|
||||
#authenticate as user to update password.
|
||||
//randomizer four digits of DeviceId
|
||||
// I dont think ther would be security problems with hardcoding deviceID but randomizing it would mitigate any issue.
|
||||
$deviceIdSeceret = rand(0, 9) . "" . rand(0, 9) . "" . rand(0, 9) . "" . rand(0, 9);
|
||||
//hardcoded device id with the first three digits random 0-9,0-9,0-9,0-9
|
||||
$embyAuthHeader = 'MediaBrowser Client="Emby Mobile", Device="Firefox", DeviceId="' . $deviceIdSeceret . 'aWxssS81LgAggFdpbmRvd3MgTlQgMTAuMDsgV2luNjxx7IHf2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzcyLjAuMzYyNi4xMTkgU2FmYXJpLzUzNy4zNnwxNTUxNTczMTAyNDI4", Version="4.0.2.0"';
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json",
|
||||
"X-Emby-Authorization" => $embyAuthHeader
|
||||
);
|
||||
$data = array(
|
||||
"Pw" => "",
|
||||
"Username" => $username
|
||||
);
|
||||
$url = $this->config['embyURL'] . '/emby/Users/AuthenticateByName';
|
||||
$response = Requests::Post($url, $headers, json_encode($data), array());
|
||||
$response = $response->body;
|
||||
$response = json_decode($response, true);
|
||||
$userToken = $response["AccessToken"];
|
||||
#update password
|
||||
$embyAuthHeader = 'MediaBrowser Client="Emby Mobile", Device="Firefox", Token="' . $userToken . '", DeviceId="' . $deviceIdSeceret . 'aWxssS81LgAggFdpbmRvd3MgTlQgMTAuMDsgV2luNjxx7IHf2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzcyLjAuMzYyNi4xMTkgU2FmYXJpLzUzNy4zNnwxNTUxNTczMTAyNDI4", Version="4.0.2.0"';
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json",
|
||||
"X-Emby-Authorization" => $embyAuthHeader
|
||||
);
|
||||
$data = array(
|
||||
"CurrentPw" => "",
|
||||
"NewPw" => $password,
|
||||
"Id" => $userID
|
||||
);
|
||||
$url = $this->config['embyURL'] . '/emby/Users/' . $userID . '/Password';
|
||||
Requests::Post($url, $headers, json_encode($data), array());
|
||||
#update config
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json"
|
||||
);
|
||||
$url = $this->config['embyURL'] . '/emby/Users/' . $userID . '/Policy?api_key=' . $this->config['embyToken'];
|
||||
$response = Requests::Post($url, $headers, $this->getEmbyTemplateUserJson(), array());
|
||||
#add emby.media
|
||||
try {
|
||||
#seperate because this is not required
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"X-Emby-Authorization" => $embyAuthHeader
|
||||
);
|
||||
$data = array(
|
||||
"ConnectUsername " => $email
|
||||
);
|
||||
$url = $this->config['embyURL'] . '/emby/Users/' . $userID . '/Connect/Link';
|
||||
Requests::Post($url, $headers, json_encode($data), array());
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Emby')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
$this->setAPIResponse('success', 'User has joined Emby', 200);
|
||||
return true;
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Emby')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*loads users from emby and returns a correctly formated policy for a new user.
|
||||
*/
|
||||
public function getEmbyTemplateUserJson()
|
||||
{
|
||||
$headers = array(
|
||||
"Accept" => "application/json"
|
||||
);
|
||||
$data = array();
|
||||
$url = $this->config['embyURL'] . '/emby/Users?api_key=' . $this->config['embyToken'];
|
||||
$response = Requests::Get($url, $headers, array());
|
||||
$response = $response->body;
|
||||
$response = json_decode($response, true);
|
||||
//$correct stores the template users object
|
||||
$correct = null;
|
||||
foreach ($response as $element) {
|
||||
if ($element['Name'] == $this->config['INVITES-EmbyTemplate']) {
|
||||
$correct = $element;
|
||||
}
|
||||
}
|
||||
if ($correct == null) {
|
||||
//return empty JSON if user incorrectly configured template
|
||||
return "{}";
|
||||
}
|
||||
//select policy section and remove possibly dangerous rows.
|
||||
$policy = $correct['Policy'];
|
||||
unset($policy['AuthenticationProviderId']);
|
||||
unset($policy['InvalidLoginAttemptCount']);
|
||||
unset($policy['DisablePremiumFeatures']);
|
||||
unset($policy['DisablePremiumFeatures']);
|
||||
return (json_encode($policy));
|
||||
}
|
||||
|
||||
public function checkHostPrefix($s)
|
||||
{
|
||||
if (empty($s)) {
|
||||
return $s;
|
||||
}
|
||||
return (substr($s, -1, 1) == '\\') ? $s : $s . '\\';
|
||||
}
|
||||
|
||||
public function approvedFileExtension($filename, $type = 'image')
|
||||
{
|
||||
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
||||
if ($type == 'image') {
|
||||
switch ($ext) {
|
||||
case 'gif':
|
||||
case 'png':
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
} elseif ($type == 'cert') {
|
||||
switch ($ext) {
|
||||
case 'pem':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function approvedFileType($file, $type = 'image')
|
||||
{
|
||||
$finfo = new finfo(FILEINFO_MIME_TYPE);
|
||||
$ext = $finfo->file($file);
|
||||
if ($type == 'image') {
|
||||
switch ($ext) {
|
||||
case 'image/gif':
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/pjpeg':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getImages()
|
||||
{
|
||||
$allIconsPrep = array();
|
||||
$allIcons = array();
|
||||
$ignore = array(".", "..", "._.DS_Store", ".DS_Store", ".pydio_id", "index.html");
|
||||
$dirname = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'tabs' . DIRECTORY_SEPARATOR;
|
||||
$path = 'plugins/images/tabs/';
|
||||
$images = scandir($dirname);
|
||||
foreach ($images as $image) {
|
||||
if (!in_array($image, $ignore)) {
|
||||
$allIconsPrep[$image] = array(
|
||||
'path' => $path,
|
||||
'name' => $image
|
||||
);
|
||||
}
|
||||
}
|
||||
$dirname = $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'userTabs' . DIRECTORY_SEPARATOR;
|
||||
$path = 'data/userTabs/';
|
||||
$images = scandir($dirname);
|
||||
foreach ($images as $image) {
|
||||
if (!in_array($image, $ignore)) {
|
||||
$allIconsPrep[$image] = array(
|
||||
'path' => $path,
|
||||
'name' => $image
|
||||
);
|
||||
}
|
||||
}
|
||||
ksort($allIconsPrep);
|
||||
foreach ($allIconsPrep as $item) {
|
||||
$allIcons[] = $item['path'] . $item['name'];
|
||||
}
|
||||
return $allIcons;
|
||||
}
|
||||
|
||||
public function imageSelect($form)
|
||||
{
|
||||
$i = 1;
|
||||
$images = $this->getImages();
|
||||
$return = '<select class="form-control tabIconImageList" id="' . $form . '-chooseImage" name="chooseImage"><option lang="en">Select or type Icon</option>';
|
||||
foreach ($images as $image) {
|
||||
$i++;
|
||||
$return .= '<option value="' . $image . '">' . basename($image) . '</option>';
|
||||
}
|
||||
return $return . '</select>';
|
||||
}
|
||||
|
||||
public function getThemes()
|
||||
{
|
||||
$themes = array();
|
||||
foreach (glob(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . "*.css") as $filename) {
|
||||
$themes[] = array(
|
||||
'name' => preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($filename)),
|
||||
'value' => preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($filename))
|
||||
);
|
||||
}
|
||||
return $themes;
|
||||
}
|
||||
|
||||
public function getSounds()
|
||||
{
|
||||
$sounds = array();
|
||||
foreach (glob(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'sounds' . DIRECTORY_SEPARATOR . 'default' . DIRECTORY_SEPARATOR . "*.mp3") as $filename) {
|
||||
$sounds[] = array(
|
||||
'name' => preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($filename)),
|
||||
'value' => preg_replace('/\\.[^.\\s]{3,4}$/', '', 'plugins/sounds/default/' . basename($filename) . '.mp3')
|
||||
);
|
||||
}
|
||||
foreach (glob(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'sounds' . DIRECTORY_SEPARATOR . 'custom' . DIRECTORY_SEPARATOR . "*.mp3") as $filename) {
|
||||
$sounds[] = array(
|
||||
'name' => preg_replace('/\\.[^.\\s]{3,4}$/', '', basename($filename)),
|
||||
'value' => preg_replace('/\\.[^.\\s]{3,4}$/', '', 'plugins/sounds/custom/' . basename($filename) . '.mp3')
|
||||
);
|
||||
}
|
||||
return $sounds;
|
||||
}
|
||||
|
||||
public function getBranches()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'name' => 'Develop',
|
||||
'value' => 'v2-develop'
|
||||
),
|
||||
array(
|
||||
'name' => 'Master',
|
||||
'value' => 'v2-master'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function getSettingsTabs()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'name' => 'Tab Editor',
|
||||
'value' => '0'
|
||||
),
|
||||
array(
|
||||
'name' => 'Customize',
|
||||
'value' => '1'
|
||||
),
|
||||
array(
|
||||
'name' => 'User Management',
|
||||
'value' => '2'
|
||||
),
|
||||
array(
|
||||
'name' => 'Image Manager',
|
||||
'value' => '3'
|
||||
),
|
||||
array(
|
||||
'name' => 'Plugins',
|
||||
'value' => '4'
|
||||
),
|
||||
array(
|
||||
'name' => 'System Settings',
|
||||
'value' => '5'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function getAuthTypes()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'name' => 'Organizr DB',
|
||||
'value' => 'internal'
|
||||
),
|
||||
array(
|
||||
'name' => 'Organizr DB + Backend',
|
||||
'value' => 'both'
|
||||
),
|
||||
array(
|
||||
'name' => 'Backend Only',
|
||||
'value' => 'external'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function getLDAPOptions()
|
||||
{
|
||||
return array(
|
||||
array(
|
||||
'name' => 'Active Directory',
|
||||
'value' => '1'
|
||||
),
|
||||
array(
|
||||
'name' => 'OpenLDAP',
|
||||
'value' => '2'
|
||||
),
|
||||
array(
|
||||
'name' => 'Free IPA',
|
||||
'value' => '3'
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function getAuthBackends()
|
||||
{
|
||||
$backendOptions = array();
|
||||
$backendOptions[] = array(
|
||||
'name' => 'Choose Backend',
|
||||
'value' => false,
|
||||
'disabled' => true
|
||||
);
|
||||
foreach (array_filter(get_class_methods('Organizr'), function ($v) {
|
||||
return strpos($v, 'plugin_auth_') === 0;
|
||||
}) as $value) {
|
||||
$name = str_replace('plugin_auth_', '', $value);
|
||||
if ($name == 'ldap') {
|
||||
if (!function_exists('ldap_connect')) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ($name == 'ldap_disabled') {
|
||||
if (function_exists('ldap_connect')) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (strpos($name, 'disabled') === false) {
|
||||
$backendOptions[] = array(
|
||||
'name' => ucwords(str_replace('_', ' ', $name)),
|
||||
'value' => $name
|
||||
);
|
||||
} else {
|
||||
$backendOptions[] = array(
|
||||
'name' => $this->$value(),
|
||||
'value' => 'none',
|
||||
'disabled' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
ksort($backendOptions);
|
||||
return $backendOptions;
|
||||
}
|
||||
|
||||
public function importUserButtons()
|
||||
{
|
||||
$emptyButtons = '
|
||||
<div class="col-md-12">
|
||||
<div class="white-box bg-org">
|
||||
<h3 class="box-title m-0" lang="en">Currently User import is available for Plex only.</h3> </div>
|
||||
</div>
|
||||
';
|
||||
$buttons = '';
|
||||
if (!empty($this->config['plexToken'])) {
|
||||
$buttons .= '<button class="btn m-b-20 m-r-20 bg-plex text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'plex\')" type="button"><span class="btn-label"><i class="mdi mdi-plex"></i></span><span lang="en">Import Plex Users</span></button>';
|
||||
}
|
||||
if (!empty($this->config['jellyfinURL']) && !empty($this->config['jellyfinToken'])) {
|
||||
$buttons .= '<button class="btn m-b-20 m-r-20 bg-primary text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'jellyfin\')" type="button"><span class="btn-label"><i class="mdi mdi-fish"></i></span><span lang="en">Import Jellyfin Users</span></button>';
|
||||
}
|
||||
if (!empty($this->config['embyURL']) && !empty($this->config['embyToken'])) {
|
||||
$buttons .= '<button class="btn m-b-20 m-r-20 bg-emby text-muted waves-effect waves-light importUsersButton" onclick="importUsers(\'emby\')" type="button"><span class="btn-label"><i class="mdi mdi-emby"></i></span><span lang="en">Import Emby Users</span></button>';
|
||||
}
|
||||
return ($buttons !== '') ? $buttons : $emptyButtons;
|
||||
}
|
||||
|
||||
public function getHomepageMediaImage()
|
||||
{
|
||||
$refresh = false;
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
if (!file_exists($cacheDirectory)) {
|
||||
mkdir($cacheDirectory, 0777, true);
|
||||
}
|
||||
@$image_url = $_GET['img'];
|
||||
@$key = $_GET['key'];
|
||||
@$image_height = $_GET['height'];
|
||||
@$image_width = $_GET['width'];
|
||||
@$source = $_GET['source'];
|
||||
@$itemType = $_GET['type'];
|
||||
if (strpos($key, '$') !== false) {
|
||||
$key = explode('$', $key)[0];
|
||||
$refresh = true;
|
||||
}
|
||||
switch ($source) {
|
||||
case 'plex':
|
||||
$plexAddress = $this->qualifyURL($this->config['plexURL']);
|
||||
$image_src = $plexAddress . '/photo/:/transcode?height=' . $image_height . '&width=' . $image_width . '&upscale=1&url=' . $image_url . '&X-Plex-Token=' . $this->config['plexToken'];
|
||||
break;
|
||||
case 'emby':
|
||||
$embyAddress = $this->qualifyURL($this->config['embyURL']);
|
||||
$imgParams = array();
|
||||
if (isset($_GET['height'])) {
|
||||
$imgParams['height'] = 'maxHeight=' . $_GET['height'];
|
||||
}
|
||||
if (isset($_GET['width'])) {
|
||||
$imgParams['width'] = 'maxWidth=' . $_GET['width'];
|
||||
}
|
||||
$image_src = $embyAddress . '/Items/' . $image_url . '/Images/' . $itemType . '?' . implode('&', $imgParams);
|
||||
break;
|
||||
case 'jellyfin':
|
||||
$jellyfinAddress = $this->qualifyURL($this->config['jellyfinURL']);
|
||||
$imgParams = array();
|
||||
if (isset($_GET['height'])) {
|
||||
$imgParams['height'] = 'maxHeight=' . $_GET['height'];
|
||||
}
|
||||
if (isset($_GET['width'])) {
|
||||
$imgParams['width'] = 'maxWidth=' . $_GET['width'];
|
||||
}
|
||||
$image_src = $jellyfinAddress . '/Items/' . $image_url . '/Images/' . $itemType . '?' . implode('&', $imgParams);
|
||||
break;
|
||||
default:
|
||||
# code...
|
||||
break;
|
||||
}
|
||||
if (strpos($key, '-') !== false) {
|
||||
$noImage = 'no-' . explode('-', $key)[1] . '.png';
|
||||
} else {
|
||||
$noImage = 'no-np.png';
|
||||
}
|
||||
$noImage = $this->root . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'homepage' . DIRECTORY_SEPARATOR . $noImage;
|
||||
if (isset($image_url) && isset($image_height) && isset($image_width) && isset($image_src)) {
|
||||
$cachefile = $cacheDirectory . $key . '.jpg';
|
||||
$cachetime = 604800;
|
||||
// Serve from the cache if it is younger than $cachetime
|
||||
if (file_exists($cachefile) && (time() - $cachetime < filemtime($cachefile)) && $refresh == false) {
|
||||
header('Content-type: image/jpeg');
|
||||
if (filesize($cachefile) > 0) {
|
||||
@readfile($cachefile);
|
||||
} else {
|
||||
@readfile($noImage);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
$options = array('verify' => false);
|
||||
$response = Requests::get($image_src, array(), $options);
|
||||
if ($response->success) {
|
||||
ob_start(); // Start the output buffer
|
||||
header('Content-type: image/jpeg');
|
||||
echo $response->body;
|
||||
// Cache the output to a file
|
||||
$fp = fopen($cachefile, 'wb');
|
||||
fwrite($fp, ob_get_contents());
|
||||
fclose($fp);
|
||||
ob_end_flush(); // Send the output to the browser
|
||||
die();
|
||||
} else {
|
||||
header('Content-type: image/jpeg');
|
||||
@readfile($noImage);
|
||||
}
|
||||
} else {
|
||||
header('Content-type: image/jpeg');
|
||||
@readfile($noImage);
|
||||
}
|
||||
}
|
||||
|
||||
public function cacheImage($url, $name, $extension = 'jpg')
|
||||
{
|
||||
$cacheDirectory = $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
if (!file_exists($cacheDirectory)) {
|
||||
mkdir($cacheDirectory, 0777, true);
|
||||
}
|
||||
$cacheFile = $cacheDirectory . $name . '.' . $extension;
|
||||
$cacheTime = 604800;
|
||||
$ctx = stream_context_create(array(
|
||||
'http' => array(
|
||||
'timeout' => 5,
|
||||
'protocol_version' => 1.1,
|
||||
'header' => 'Connection: close'
|
||||
)
|
||||
));
|
||||
if ((file_exists($cacheFile) && (time() - $cacheTime) > filemtime($cacheFile)) || !file_exists($cacheFile)) {
|
||||
@copy($url, $cacheFile, $ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public function checkFrame($array, $url)
|
||||
{
|
||||
if (array_key_exists("x-frame-options", $array)) {
|
||||
if (gettype($array['x-frame-options']) == 'array') {
|
||||
$array['x-frame-options'] = $array['x-frame-options'][0];
|
||||
}
|
||||
$array['x-frame-options'] = strtolower($array['x-frame-options']);
|
||||
if ($array['x-frame-options'] == "deny") {
|
||||
return false;
|
||||
} elseif ($array['x-frame-options'] == "sameorgin") {
|
||||
$digest = parse_url($url);
|
||||
$host = ($digest['host'] ?? '');
|
||||
if ($this->getServer() == $host) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} elseif (strpos($array['x-frame-options'], 'allow-from') !== false) {
|
||||
$explodeServers = explode(' ', $array['x-frame-options']);
|
||||
$allowed = false;
|
||||
foreach ($explodeServers as $server) {
|
||||
$digest = parse_url($server);
|
||||
$host = ($digest['host'] ?? '');
|
||||
if ($this->getServer() == $host) {
|
||||
$allowed = true;
|
||||
}
|
||||
}
|
||||
return $allowed;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!$array) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function frameTest($url)
|
||||
{
|
||||
if (!$url || $url == '') {
|
||||
$this->setAPIResponse('error', 'URL not supplied', 404);
|
||||
return false;
|
||||
}
|
||||
$array = array_change_key_case(get_headers($this->qualifyURL($url), 1));
|
||||
$url = $this->qualifyURL($url);
|
||||
if ($this->checkFrame($array, $url)) {
|
||||
$this->setAPIResponse('success', 'URL approved for iFrame', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'URL failed approval for iFrame', 409);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function groupSelect()
|
||||
{
|
||||
$groups = $this->getAllGroups();
|
||||
$select = array();
|
||||
foreach ($groups as $key => $value) {
|
||||
$select[] = array(
|
||||
'name' => $value['group'],
|
||||
'value' => $value['group_id']
|
||||
);
|
||||
}
|
||||
return $select;
|
||||
}
|
||||
|
||||
public function userSelect()
|
||||
{
|
||||
$users = $this->getAllUsers();
|
||||
$select = [];
|
||||
foreach ($users as $key => $value) {
|
||||
$select[] = array(
|
||||
'name' => $value['username'],
|
||||
'value' => $value['id']
|
||||
);
|
||||
}
|
||||
return $select;
|
||||
}
|
||||
|
||||
public function showLogin()
|
||||
{
|
||||
if ($this->config['hideRegistration'] == false) {
|
||||
return '<p><span lang="en">Don\'t have an account?</span><a href="#" class="text-primary m-l-5 to-register"><b lang="en">Sign Up</b></a></p>';
|
||||
}
|
||||
}
|
||||
|
||||
public function checkoAuth()
|
||||
{
|
||||
return $this->config['plexoAuth'] && $this->config['authBackend'] == 'plex' && $this->config['authType'] !== 'internal';
|
||||
}
|
||||
|
||||
public function checkoAuthOnly()
|
||||
{
|
||||
return $this->config['plexoAuth'] && $this->config['authBackend'] == 'plex' && $this->config['authType'] == 'external';
|
||||
}
|
||||
|
||||
public function showoAuth()
|
||||
{
|
||||
$buttons = '';
|
||||
if ($this->config['plexoAuth'] && $this->config['authBackend'] == 'plex' && $this->config['authType'] !== 'internal') {
|
||||
$buttons .= '<a href="javascript:void(0)" onclick="oAuthStart(\'plex\')" class="btn btn-lg btn-block text-uppercase waves-effect waves-light bg-plex text-muted" data-toggle="tooltip" title="" data-original-title="Login with Plex"> <span>Login</span><i aria-hidden="true" class="mdi mdi-plex m-l-5"></i> </a>';
|
||||
}
|
||||
return ($buttons) ? '
|
||||
<div class="panel">
|
||||
<div class="panel-heading bg-org" id="plex-login-heading" role="tab">
|
||||
<a class="panel-title" data-toggle="collapse" href="#plex-login-collapse" data-parent="#login-panels" aria-expanded="false" aria-controls="organizr-login-collapse">
|
||||
<img class="lazyload loginTitle" data-src="plugins/images/tabs/plex.png">
|
||||
<span class="text-uppercase fw300" lang="en">Login with Plex</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="panel-collapse collapse in" id="plex-login-collapse" aria-labelledby="plex-login-heading" role="tabpanel">
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-12 col-md-12 text-center">
|
||||
<div class="social m-b-0">' . $buttons . '</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
' : '';
|
||||
}
|
||||
|
||||
public function logoOrText()
|
||||
{
|
||||
$showLogo = $this->config['minimalLoginScreen'] ? '' : 'visible-xs';
|
||||
if ($this->config['useLogoLogin'] == false) {
|
||||
$html = '<h1>' . $this->config['title'] . '</h1>';
|
||||
} else {
|
||||
$html = '<img class="loginLogo" src="' . $this->config['loginLogo'] . '" alt="Home" />';
|
||||
}
|
||||
return '<a href="javascript:void(0)" class="text-center db ' . $showLogo . '" id="login-logo">' . $html . '</a>';
|
||||
}
|
||||
|
||||
public function settingsDocker()
|
||||
{
|
||||
$type = ($this->docker) ? 'Official Docker' : 'Native';
|
||||
return '<li><div class="bg-info"><i class="mdi mdi-flag mdi-24px text-white"></i></div><span class="text-muted hidden-xs m-t-10" lang="en">Install Type</span> ' . $type . '</li>';
|
||||
}
|
||||
|
||||
public function settingsPathChecks()
|
||||
{
|
||||
$paths = $this->pathsWritable($this->paths);
|
||||
$items = '';
|
||||
$type = (array_search(false, $paths)) ? 'Not Writable' : 'Writable';
|
||||
$result = '<li class="mouse" onclick="toggleWritableFolders();"><div class="bg-info"><i class="mdi mdi-folder mdi-24px text-white"></i></div><span class="text-muted hidden-xs m-t-10" lang="en">Organizr Paths</span> ' . $type . '</li>';
|
||||
foreach ($paths as $k => $v) {
|
||||
$items .= '<li class="folders-writable hidden"><div class="bg-primary"><i class="mdi mdi-folder mdi-24px text-white"></i></div><a tabindex="0" type="button" class="btn btn-default btn-outline popover-info pull-right clipboard" lang="en" data-container="body" title="" data-toggle="popover" data-placement="left" data-content="' . $v['path'] . '" data-original-title="File Path" data-clipboard-text="' . $v['path'] . '">' . $k . '</a> ' . (($v['writable']) ? 'Writable' : 'Not Writable') . '</li>';
|
||||
}
|
||||
return $result . $items;
|
||||
}
|
||||
|
||||
public function pathsWritable($paths)
|
||||
{
|
||||
$results = array();
|
||||
foreach ($paths as $k => $v) {
|
||||
$results[$k] = [
|
||||
'writable' => is_writable($v),
|
||||
'path' => $v
|
||||
];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function clearTautulliTokens()
|
||||
{
|
||||
foreach (array_keys($_COOKIE) as $k => $v) {
|
||||
if (strpos($v, 'tautulli') !== false) {
|
||||
$this->coookie('delete', $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function clearJellyfinTokens()
|
||||
{
|
||||
foreach (array_keys($_COOKIE) as $k => $v) {
|
||||
if (strpos($v, 'user-') !== false) {
|
||||
$this->coookie('delete', $v);
|
||||
}
|
||||
}
|
||||
$this->coookie('delete', 'jellyfin_credentials');
|
||||
}
|
||||
|
||||
public function clearKomgaToken()
|
||||
{
|
||||
if (isset($_COOKIE['komga_token'])) {
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['komgaURL']);
|
||||
$options = $this->requestOptions($url, 60000, true, false);
|
||||
$response = Requests::post($url . '/api/v1/users/logout', ['X-Auth-Token' => $_COOKIE['komga_token']], $options);
|
||||
if ($response->success) {
|
||||
$this->setLoggerChannel('Komga')->info('Logged User out');
|
||||
} else {
|
||||
$this->setLoggerChannel('Komga')->warning('Unable to Logged User out');
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Komga')->error($e);
|
||||
}
|
||||
$this->coookie('delete', 'komga_token');
|
||||
}
|
||||
}
|
||||
|
||||
public function analyzeIP($ip)
|
||||
{
|
||||
if (strpos($ip, '/') !== false) {
|
||||
$explodeIP = explode('/', $ip);
|
||||
$prefix = $explodeIP[1];
|
||||
$start_ip = $explodeIP[0];
|
||||
$ip_count = 1 << (32 - $prefix);
|
||||
$start_ip_long = ip2long($start_ip);
|
||||
$last_ip_long = ip2long($start_ip) + $ip_count - 1;
|
||||
} elseif (substr_count($ip, '.') == 3) {
|
||||
$start_ip_long = ip2long($ip);
|
||||
$last_ip_long = ip2long($ip);
|
||||
}
|
||||
return (isset($start_ip_long) && isset($last_ip_long)) ? array('from' => $start_ip_long, 'to' => $last_ip_long) : false;
|
||||
}
|
||||
|
||||
public function authProxyRangeCheck($from, $to)
|
||||
{
|
||||
$approved = false;
|
||||
$userIP = ip2long($_SERVER['REMOTE_ADDR']);
|
||||
$low = $from;
|
||||
$high = $to;
|
||||
if ($userIP <= $high && $low <= $userIP) {
|
||||
$approved = true;
|
||||
}
|
||||
$this->logger->debug('authProxy range check', ['server_ip' => ['long' => $userIP, 'short' => long2ip($userIP)], 'range_from' => ['long' => $from, 'short' => long2ip($from)], 'range_to' => ['long' => $to, 'short' => long2ip($to)], 'approved' => $approved]);
|
||||
return $approved;
|
||||
}
|
||||
|
||||
public function userDefinedIdReplacementLink($link, $variables)
|
||||
{
|
||||
if (!isset($link) || $link == '') {
|
||||
return null;
|
||||
}
|
||||
return strtr($link, $variables);
|
||||
}
|
||||
|
||||
public function requestOptions($url, $timeout = null, $override = false, $customCertificate = false, $extras = null)
|
||||
{
|
||||
$options = [];
|
||||
if (is_numeric($timeout)) {
|
||||
if ($timeout >= 1000) {
|
||||
$timeout = $timeout / 1000;
|
||||
}
|
||||
$options = array_merge($options, array('timeout' => $timeout));
|
||||
}
|
||||
if ($customCertificate) {
|
||||
if ($this->hasCustomCert()) {
|
||||
$options = array_merge($options, array('verify' => $this->getCustomCert(), 'verifyname' => false));
|
||||
}
|
||||
}
|
||||
if ($this->localURL($url, $override)) {
|
||||
$options = array_merge($options, array('verify' => false, 'verifyname' => false));
|
||||
}
|
||||
if ($extras) {
|
||||
if (gettype($extras) == 'array') {
|
||||
$options = array_merge($options, $extras);
|
||||
}
|
||||
}
|
||||
return array_merge($options, array('useragent' => 'organizr/' . $this->version, 'connect_timeout' => 5));
|
||||
}
|
||||
|
||||
public function showHTML(string $title = 'Organizr Alert', string $notice = '', bool $autoClose = false)
|
||||
{
|
||||
$close = $autoClose ? 'onLoad="setTimeout(\'closemyself()\',3000);"' : '';
|
||||
$closeMessage = $autoClose ? '<p><sup>(This window will close automatically)</sup></p>' : '';
|
||||
return
|
||||
'<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style>' . file_get_contents($this->root . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'mvp.css') . '</style>
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="' . $title . '">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>' . $title . '</title>
|
||||
</head>
|
||||
<script language=javascript>
|
||||
function closemyself() {
|
||||
window.opener=self;
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
<body ' . $close . '>
|
||||
<main>
|
||||
<section>
|
||||
<div>
|
||||
<h3>' . $title . '</h3>
|
||||
<p>' . $notice . '</p>
|
||||
' . $closeMessage . '
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>';
|
||||
}
|
||||
|
||||
public function buildSettingsMenus($menuItems, $menuName)
|
||||
{
|
||||
$selectMenuItems = '';
|
||||
$unorderedListMenuItems = '';
|
||||
$menuNameLower = strtolower(str_replace(' ', '-', $menuName));
|
||||
foreach ($menuItems as $menuItem) {
|
||||
$anchorShort = str_replace('-anchor', '', $menuItem['anchor']);
|
||||
$active = ($menuItem['active']) ? 'active' : '';
|
||||
$apiPage = ($menuItem['api']) ? 'loadSettingsPage2(\'' . $menuItem['api'] . '\',\'#' . $anchorShort . '\',\'' . $menuItem['name'] . '\');' : '';
|
||||
$onClick = (isset($menuItem['onclick'])) ? $menuItem['onclick'] : '';
|
||||
$selectMenuItems .= '<option value="#' . $menuItem['anchor'] . '" lang="en">' . $menuItem['name'] . '</option>';
|
||||
$unorderedListMenuItems .= '
|
||||
<li onclick="changeSettingsMenu(\'Settings::' . $menuName . '::' . $menuItem['name'] . '\'); ' . $apiPage . $onClick . '" role="presentation" class="' . $active . '">
|
||||
<a id="' . $menuItem['anchor'] . '" href="#' . $anchorShort . '" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true">
|
||||
<span lang="en">' . $menuItem['name'] . '</span>
|
||||
</a>
|
||||
</li>';
|
||||
}
|
||||
$selectMenu = '<select class="form-control settings-dropdown-box ' . $menuNameLower . '-menu w-100 visible-xs">' . $selectMenuItems . '</select>';
|
||||
$unorderedListMenu = '<ul class="nav customtab2 nav-tabs nav-non-mobile hidden-xs" data-dropdown="' . $menuNameLower . '-menu" role="tablist">' . $unorderedListMenuItems . '</ul>';
|
||||
return $selectMenu . $unorderedListMenu;
|
||||
}
|
||||
|
||||
public function isJSON($string)
|
||||
{
|
||||
return is_string($string) && is_array(json_decode($string, true)) && (json_last_error() == JSON_ERROR_NONE);
|
||||
}
|
||||
|
||||
public function isXML($string)
|
||||
{
|
||||
libxml_use_internal_errors(true);
|
||||
return (bool)simplexml_load_string($string);
|
||||
}
|
||||
|
||||
public function testAndFormatString($string)
|
||||
{
|
||||
if ($this->isJSON($string)) {
|
||||
return ['type' => 'json', 'data' => json_decode($string, true)];
|
||||
} elseif ($this->isXML($string)) {
|
||||
libxml_use_internal_errors(true);
|
||||
return ['type' => 'xml', 'data' => simplexml_load_string($string)];
|
||||
} else {
|
||||
return ['type' => 'string', 'data' => $string];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
/* Depreciated */
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
trait PluginFunctions
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
<?php
|
||||
|
||||
trait SSOFunctions
|
||||
{
|
||||
public function ssoCookies()
|
||||
{
|
||||
$cookies = array(
|
||||
'myPlexAccessToken' => $_COOKIE['mpt'] ?? false,
|
||||
'id_token' => $_COOKIE['Auth'] ?? false,
|
||||
'jellyfin_credentials' => $_COOKIE['jellyfin_credentials'] ?? false,
|
||||
'komga_token' => $_COOKIE['komga_token'] ?? false
|
||||
);
|
||||
// Jellyfin cookie
|
||||
foreach (array_keys($_COOKIE) as $k => $v) {
|
||||
if (strpos($v, 'user-') !== false) {
|
||||
$cookiesToAdd = [
|
||||
$v => $_COOKIE[$v]
|
||||
];
|
||||
$cookies = array_merge($cookies, $cookiesToAdd);
|
||||
}
|
||||
}
|
||||
return $cookies;
|
||||
}
|
||||
|
||||
public function getSSOList($enabledOnly = false)
|
||||
{
|
||||
$searchTerm = 'sso';
|
||||
$list = array_filter($this->config, function ($k) use ($searchTerm) {
|
||||
return stripos($k, $searchTerm) !== false;
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
foreach ($list as $key => $sso) {
|
||||
if (stripos(substr($key, 0, 3), $searchTerm) === false) {
|
||||
unset($list[$key]);
|
||||
continue;
|
||||
}
|
||||
if (gettype($sso) !== 'boolean') {
|
||||
unset($list[$key]);
|
||||
}
|
||||
if ($enabledOnly) {
|
||||
if (!$sso) {
|
||||
unset($list[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function getSSOEnabledCount()
|
||||
{
|
||||
$count = count($this->getSSOList(true));
|
||||
return ($count <= 0) ? 1 : $count;
|
||||
}
|
||||
|
||||
public function getSSOTimeout()
|
||||
{
|
||||
return (60000 / $this->getSSOEnabledCount());
|
||||
}
|
||||
|
||||
public function getSSOUserFor($app, $userobj)
|
||||
{
|
||||
$map = array(
|
||||
'jellyfin' => 'username',
|
||||
'ombi' => 'username',
|
||||
'overseerr' => 'email',
|
||||
'tautulli' => 'username',
|
||||
'petio' => 'username',
|
||||
'komga' => 'email'
|
||||
);
|
||||
return (gettype($userobj) == 'string') ? $userobj : $userobj[$map[$app]];
|
||||
}
|
||||
|
||||
public function ssoCheck($userobj, $password, $token = null)
|
||||
{
|
||||
$this->setCurrentUser(false);
|
||||
$this->setLoggerChannel('Authentication', $this->user['username']);
|
||||
$this->logger->debug('Starting SSO check function');
|
||||
if ($this->config['ssoPlex'] && $token) {
|
||||
$this->logger->debug('Setting Plex SSO cookie');
|
||||
$this->coookie('set', 'mpt', $token, $this->config['rememberMeDays'], false);
|
||||
}
|
||||
if ($this->config['ssoOmbi']) {
|
||||
$this->logger->debug('Starting Ombi SSO check function');
|
||||
$fallback = ($this->config['ombiFallbackUser'] !== '' && $this->config['ombiFallbackPassword'] !== '');
|
||||
$ombiToken = $this->getOmbiToken($this->getSSOUserFor('ombi', $userobj), $password, $token, $fallback);
|
||||
if ($ombiToken) {
|
||||
$this->logger->debug('Setting Ombi SSO cookie');
|
||||
$this->coookie('set', 'Auth', $ombiToken, $this->config['rememberMeDays'], false);
|
||||
} else {
|
||||
$this->logger->debug('No Ombi token received from backend');
|
||||
}
|
||||
}
|
||||
if ($this->config['ssoTautulli'] && $this->qualifyRequest($this->config['ssoTautulliAuth'])) {
|
||||
$this->logger->debug('Starting Tautulli SSO check function');
|
||||
$tautulliToken = $this->getTautulliToken($this->getSSOUserFor('tautulli', $userobj), $password, $token);
|
||||
if ($tautulliToken) {
|
||||
foreach ($tautulliToken as $key => $value) {
|
||||
$this->logger->debug('Setting Tautulli SSO cookie');
|
||||
$this->coookie('set', 'tautulli_token_' . $value['uuid'], $value['token'], $this->config['rememberMeDays'], true, $value['path']);
|
||||
}
|
||||
} else {
|
||||
$this->logger->debug('No Tautulli token received from backend');
|
||||
}
|
||||
}
|
||||
if ($this->config['ssoJellyfin']) {
|
||||
$this->logger->debug('Starting Jellyfin SSO check function');
|
||||
$jellyfinToken = $this->getJellyfinToken($this->getSSOUserFor('jellyfin', $userobj), $password);
|
||||
if ($jellyfinToken) {
|
||||
foreach ($jellyfinToken as $k => $v) {
|
||||
$this->logger->debug('Setting Jellyfin SSO cookie');
|
||||
$this->coookie('set', $k, $v, $this->config['rememberMeDays'], false);
|
||||
}
|
||||
} else {
|
||||
$this->logger->debug('No Jellyfin token received from backend');
|
||||
}
|
||||
}
|
||||
if ($this->config['ssoOverseerr']) {
|
||||
$this->logger->debug('Starting Overseerr SSO check function');
|
||||
$fallback = ($this->config['overseerrFallbackUser'] !== '' && $this->config['overseerrFallbackPassword'] !== '');
|
||||
$overseerrToken = $this->getOverseerrToken($this->getSSOUserFor('overseerr', $userobj), $password, $token, $fallback);
|
||||
if ($overseerrToken) {
|
||||
$this->logger->debug('Setting Overseerr SSO cookie');
|
||||
$this->coookie('set', 'connect.sid', $overseerrToken, $this->config['rememberMeDays'], false);
|
||||
} else {
|
||||
$this->logger->debug('No Overseerr token received from backend');
|
||||
}
|
||||
}
|
||||
if ($this->config['ssoPetio']) {
|
||||
$this->logger->debug('Starting Petio SSO check function');
|
||||
$fallback = ($this->config['petioFallbackUser'] !== '' && $this->config['petioFallbackPassword'] !== '');
|
||||
$petioToken = $this->getPetioToken($this->getSSOUserFor('petio', $userobj), $password, $token, $fallback);
|
||||
if ($petioToken) {
|
||||
$this->logger->debug('Setting Petio SSO cookie');
|
||||
$this->coookie('set', 'petio_jwt', $petioToken, $this->config['rememberMeDays'], false);
|
||||
} else {
|
||||
$this->logger->debug('No Petio token received from backend');
|
||||
}
|
||||
}
|
||||
if ($this->config['ssoKomga'] && $this->qualifyRequest($this->config['ssoKomgaAuth'])) {
|
||||
$this->logger->debug('Starting Komga SSO check function');
|
||||
$fallback = ($this->config['komgaFallbackUser'] !== '' && $this->config['komgaFallbackPassword'] !== '');
|
||||
$komga = $this->getKomgaToken($this->getSSOUserFor('komga', $userobj), $password, $fallback);
|
||||
if ($komga) {
|
||||
$this->logger->debug('Setting Komga SSO cookie');
|
||||
$this->coookie('set', 'komga_token', $komga, $this->config['rememberMeDays'], false);
|
||||
} else {
|
||||
$this->logger->debug('No Komga token received from backend');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getKomgaToken($email, $password, $fallback = false)
|
||||
{
|
||||
$token = null;
|
||||
$useMaster = false;
|
||||
try {
|
||||
if ($password) {
|
||||
if ($password == '') {
|
||||
$useMaster = true;
|
||||
}
|
||||
} else {
|
||||
$useMaster = true;
|
||||
}
|
||||
if ($useMaster) {
|
||||
if ($this->config['komgaSSOMasterPassword'] !== '') {
|
||||
$password = $this->decrypt($this->config['komgaSSOMasterPassword']);
|
||||
}
|
||||
}
|
||||
$credentials = array('auth' => new Requests_Auth_Digest(array($email, $password)));
|
||||
$url = $this->qualifyURL($this->config['komgaURL']);
|
||||
$options = $this->requestOptions($url, $this->getSSOTimeout(), true, false, $credentials);
|
||||
$response = Requests::get($url . '/api/v1/users/me', ['X-Auth-Token' => 'organizrSSO'], $options);
|
||||
if ($response->success) {
|
||||
if ($response->headers['x-auth-token']) {
|
||||
$this->setLoggerChannel('Komga')->info('Grabbed token');
|
||||
$token = $response->headers['x-auth-token'];
|
||||
} else {
|
||||
$this->setLoggerChannel('Komga')->warning('Komga did not return Token');
|
||||
}
|
||||
} else {
|
||||
if ($fallback) {
|
||||
$this->setLoggerChannel('Komga')->warning('Komga did not return Token - Will retry using fallback credentials');
|
||||
} else {
|
||||
$this->setLoggerChannel('Komga')->warning('Komga did not return Token');
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Komga')->error($e);
|
||||
}
|
||||
if ($token) {
|
||||
return $token;
|
||||
} elseif ($fallback) {
|
||||
return $this->getKomgaToken($this->config['komgaFallbackUser'], $this->decrypt($this->config['komgaFallbackPassword']), false);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getJellyfinToken($username, $password)
|
||||
{
|
||||
$token = null;
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['jellyfinURL']);
|
||||
$ssoUrl = $this->qualifyURL($this->config['jellyfinSSOURL']);
|
||||
$headers = array(
|
||||
'X-Emby-Authorization' => 'MediaBrowser Client="Organizr Jellyfin Tab", Device="Organizr_PHP", DeviceId="Organizr_SSO", Version="1.0"',
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json",
|
||||
"X-Forwarded-For" => $this->userIP()
|
||||
);
|
||||
$data = array(
|
||||
"Username" => $username,
|
||||
"Pw" => $password
|
||||
);
|
||||
$endpoint = '/Users/authenticatebyname';
|
||||
$options = $this->requestOptions($url, $this->getSSOTimeout());
|
||||
$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
|
||||
if ($response->success) {
|
||||
$token = json_decode($response->body, true);
|
||||
$this->setLoggerChannel('JellyFin')->info('Grabbed token');
|
||||
$key = 'user-' . $token['User']['Id'] . '-' . $token['ServerId'];
|
||||
$jellyfin[$key] = json_encode($token['User']);
|
||||
$jellyfin['jellyfin_credentials'] = '{"Servers":[{"ManualAddress":"' . $ssoUrl . '","Id":"' . $token['ServerId'] . '","UserId":"' . $token['User']['Id'] . '","AccessToken":"' . $token['AccessToken'] . '"}]}';
|
||||
return $jellyfin;
|
||||
} else {
|
||||
$this->setLoggerChannel('JellyFin')->warning('JellyFin did not return Token');
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Jellyfin')->error($e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getOmbiToken($username, $password, $oAuthToken = null, $fallback = false)
|
||||
{
|
||||
$token = null;
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['ombiURL']);
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json",
|
||||
"X-Forwarded-For" => $this->userIP()
|
||||
);
|
||||
$data = array(
|
||||
"username" => ($oAuthToken ? "" : $username),
|
||||
"password" => ($oAuthToken ? "" : $password),
|
||||
"rememberMe" => "true",
|
||||
"plexToken" => $oAuthToken
|
||||
);
|
||||
$endpoint = ($oAuthToken) ? '/api/v1/Token/plextoken' : '/api/v1/Token';
|
||||
$options = $this->requestOptions($url, $this->getSSOTimeout());
|
||||
$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
|
||||
if ($response->success) {
|
||||
$token = json_decode($response->body, true)['access_token'];
|
||||
$this->setLoggerChannel('Ombi')->info('Grabbed token');
|
||||
} else {
|
||||
if ($fallback) {
|
||||
$this->setLoggerChannel('Ombi')->warning('Ombi did not return Token - Will retry using fallback credentials');
|
||||
} else {
|
||||
$this->setLoggerChannel('Ombi')->warning('Ombi did not return Token');
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Ombi')->error($e);
|
||||
}
|
||||
if ($token) {
|
||||
return $token;
|
||||
} elseif ($fallback) {
|
||||
return $this->getOmbiToken($this->config['ombiFallbackUser'], $this->decrypt($this->config['ombiFallbackPassword']), null, false);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getTautulliToken($username, $password, $plexToken = null)
|
||||
{
|
||||
$token = null;
|
||||
$tautulliURLList = explode(',', $this->config['tautulliURL']);
|
||||
if (count($tautulliURLList) !== 0) {
|
||||
foreach ($tautulliURLList as $key => $value) {
|
||||
try {
|
||||
$url = $this->qualifyURL($value);
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/x-www-form-urlencoded",
|
||||
"User-Agent" => $_SERVER ['HTTP_USER_AGENT'] ?? null,
|
||||
"X-Forwarded-For" => $this->userIP()
|
||||
);
|
||||
$data = array(
|
||||
"username" => ($plexToken ? "" : $username),
|
||||
"password" => ($plexToken ? "" : $password),
|
||||
"token" => $plexToken,
|
||||
"remember_me" => 1,
|
||||
);
|
||||
$options = $this->requestOptions($url, $this->getSSOTimeout());
|
||||
$response = Requests::post($url . '/auth/signin', $headers, $data, $options);
|
||||
if ($response->success) {
|
||||
$qualifiedURL = $this->qualifyURL($url, true);
|
||||
$path = ($qualifiedURL['path']) ? $qualifiedURL['path'] : '/';
|
||||
$token[$key]['token'] = json_decode($response->body, true)['token'];
|
||||
$token[$key]['uuid'] = json_decode($response->body, true)['uuid'];
|
||||
$token[$key]['path'] = $path;
|
||||
$this->setLoggerChannel('Tautulli')->info('Grabbed token from: ' . $url);
|
||||
} else {
|
||||
$this->setLoggerChannel('Tautulli')->warning('Error on URL: ' . $url);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Tautulli')->error($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ($token) ? $token : false;
|
||||
}
|
||||
|
||||
public function getOverseerrToken($email, $password, $oAuthToken = null, $fallback = false)
|
||||
{
|
||||
$token = null;
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['overseerrURL']);
|
||||
$headers = array(
|
||||
"Content-Type" => "application/json",
|
||||
"X-Forwarded-For" => $this->userIP()
|
||||
);
|
||||
$data = array(
|
||||
"email" => ($oAuthToken ? "" : $email), // not needed yet
|
||||
"password" => ($oAuthToken ? "" : $password), // not needed yet
|
||||
"authToken" => $oAuthToken
|
||||
);
|
||||
$endpoint = ($oAuthToken ? '/api/v1/auth/plex' : '/api/v1/auth/local');
|
||||
$options = $this->requestOptions($url, $this->getSSOTimeout());
|
||||
$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
|
||||
if ($response->success) {
|
||||
$user = json_decode($response->body, true); // not really needed yet
|
||||
$token = $response->cookies['connect.sid']->value;
|
||||
$this->setLoggerChannel('Overseerr')->info('Grabbed token');
|
||||
} else {
|
||||
if ($fallback) {
|
||||
$this->setLoggerChannel('Overseerr')->warning('Overseerr did not return Token - Will retry using fallback credentials');
|
||||
} else {
|
||||
$this->setLoggerChannel('Overseerr')->warning('Overseerr did not return Token');
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Overseerr')->error($e);
|
||||
}
|
||||
if ($token) {
|
||||
return urldecode($token);
|
||||
} elseif ($fallback) {
|
||||
return $this->getOverseerrToken($this->config['overseerrFallbackUser'], $this->decrypt($this->config['overseerrFallbackPassword']), null, false);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPetioToken($username, $password, $oAuthToken = null, $fallback = false)
|
||||
{
|
||||
$token = null;
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['petioURL']);
|
||||
$headers = array(
|
||||
"Content-Type" => "application/json",
|
||||
"X-Forwarded-For" => $this->userIP()
|
||||
);
|
||||
$data = array(
|
||||
'user' => [
|
||||
'username' => ($oAuthToken ? '' : $username),
|
||||
'password' => ($oAuthToken ? '' : $password),
|
||||
'type' => 1,
|
||||
],
|
||||
'authToken' => false,
|
||||
'token' => $oAuthToken
|
||||
);
|
||||
$endpoint = ($oAuthToken) ? '/api/login/plex_login' : '/api/login';
|
||||
$options = $this->requestOptions($url, $this->getSSOTimeout());
|
||||
$response = Requests::post($url . $endpoint, $headers, json_encode($data), $options);
|
||||
if ($response->success) {
|
||||
$user = json_decode($response->body, true)['user'];
|
||||
$token = json_decode($response->body, true)['token'];
|
||||
$this->setLoggerChannel('Petio')->info('Grabbed token');
|
||||
} else {
|
||||
if ($fallback) {
|
||||
$this->setLoggerChannel('Petio')->warning('Petio did not return Token - Will retry using fallback credentials');
|
||||
} else {
|
||||
$this->setLoggerChannel('Petio')->warning('Petio did not return Token');
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Petio')->error($e);
|
||||
}
|
||||
if ($token) {
|
||||
return $token;
|
||||
} elseif ($fallback) {
|
||||
return $this->getPetioToken($this->config['petioFallbackUser'], $this->decrypt($this->config['petioFallbackPassword']), null, false);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
trait StaticFunctions
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
/* Depreciated */
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
trait TokenFunctions
|
||||
{
|
||||
public function configToken()
|
||||
{
|
||||
return Lcobucci\JWT\Configuration::forSymmetricSigner(
|
||||
// You may use any HMAC variations (256, 384, and 512)
|
||||
new Lcobucci\JWT\Signer\Hmac\Sha256(),
|
||||
// replace the value below with a key of your own!
|
||||
Lcobucci\JWT\Signer\Key\InMemory::plainText($this->config['organizrHash'])
|
||||
// You may also override the JOSE encoder/decoder if needed by providing extra arguments here
|
||||
);
|
||||
}
|
||||
|
||||
public function validationConstraints()
|
||||
{
|
||||
return [
|
||||
new Lcobucci\JWT\Validation\Constraint\IssuedBy('Organizr'),
|
||||
new Lcobucci\JWT\Validation\Constraint\PermittedFor('Organizr'),
|
||||
new Lcobucci\JWT\Validation\Constraint\LooseValidAt(Lcobucci\Clock\SystemClock::fromUTC())
|
||||
];
|
||||
}
|
||||
|
||||
public function jwtParse($userToken)
|
||||
{
|
||||
try {
|
||||
$result = [];
|
||||
// Check Token with JWT
|
||||
// Set key
|
||||
if (!isset($this->config['organizrHash'])) {
|
||||
return null;
|
||||
}
|
||||
$config = $this->configToken();
|
||||
assert($config instanceof Lcobucci\JWT\Configuration);
|
||||
$token = $config->parser()->parse($userToken);
|
||||
assert($token instanceof Lcobucci\JWT\UnencryptedToken);
|
||||
$constraints = $this->validationConstraints();
|
||||
if (!$config->validator()->validate($token, ...$constraints)) {
|
||||
return false;
|
||||
}
|
||||
$result['username'] = ($token->claims()->has('name')) ? $token->claims()->get('name') : 'N/A';
|
||||
$result['group'] = ($token->claims()->has('group')) ? $token->claims()->get('group') : 'N/A';
|
||||
$result['groupID'] = $token->claims()->get('groupID');
|
||||
$result['userID'] = $token->claims()->get('userID');
|
||||
$result['email'] = $token->claims()->get('email');
|
||||
$result['image'] = $token->claims()->get('image');
|
||||
$result['tokenExpire'] = $token->claims()->get('exp');
|
||||
$result['tokenDate'] = $token->claims()->get('iat');
|
||||
return $result;
|
||||
} catch (\OutOfBoundsException | \RunTimeException | \InvalidArgumentException | \Lcobucci\JWT\Validation\RequiredConstraintsViolated $e) {
|
||||
$this->setLoggerChannel('Token Error')->error($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
trait UpdateFunctions
|
||||
{
|
||||
public function testScriptFilePermissions($script, $retest = false)
|
||||
{
|
||||
if (file_exists($script)) {
|
||||
if (is_executable($script)) {
|
||||
return true;
|
||||
} elseif (!$retest) {
|
||||
$this->setLoggerChannel('Update')->notice('Attempting to set correct permissions', ['file' => $script]);
|
||||
$permissions = shell_exec('chmod 777 ' . $script);
|
||||
return $this->testScriptFilePermissions($script, true);
|
||||
} else {
|
||||
$this->setLoggerChannel('Update')->warning('Update script doesn\'t have the correct permissions', ['file' => $script]);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('Update')->warning('Update script doesn\'t exist', ['file' => $script]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function updateOrganizr()
|
||||
{
|
||||
if ($this->docker) {
|
||||
return $this->dockerUpdate();
|
||||
} elseif ($this->getOS() == 'win') {
|
||||
return $this->windowsUpdate();
|
||||
} else {
|
||||
return $this->linuxUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public function createUpdateStatusFile()
|
||||
{
|
||||
$file = $this->config['dbLocation'] . 'updateInProgress.txt';
|
||||
touch($file);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeUpdateStatusFile()
|
||||
{
|
||||
$file = $this->config['dbLocation'] . 'updateInProgress.txt';
|
||||
if (file_exists($file)) {
|
||||
@unlink($file);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function hasUpdateStatusFile()
|
||||
{
|
||||
return file_exists($this->config['dbLocation'] . 'updateInProgress.txt');
|
||||
}
|
||||
|
||||
public function checkForGitLockFile()
|
||||
{
|
||||
return file_exists($this->root . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'index.lock');
|
||||
}
|
||||
|
||||
public function removeGitLockFile()
|
||||
{
|
||||
if ($this->checkForGitLockFile()) {
|
||||
$removed = false;
|
||||
if (@unlink($this->root . DIRECTORY_SEPARATOR . '.git' . DIRECTORY_SEPARATOR . 'index.lock')) {
|
||||
$removed = true;
|
||||
}
|
||||
return $removed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function dockerUpdate()
|
||||
{
|
||||
if (!$this->docker) {
|
||||
$this->setResponse(409, 'Your install type is not Docker');
|
||||
return false;
|
||||
}
|
||||
if ($this->hasUpdateStatusFile()) {
|
||||
$this->setResponse(500, 'Already Update in progress');
|
||||
return false;
|
||||
} else {
|
||||
$this->createUpdateStatusFile();
|
||||
}
|
||||
if (!$this->removeGitLockFile()) {
|
||||
$this->setResponse(500, 'Git Lock file was found and could not be removed. Please remove manually');
|
||||
return false;
|
||||
}
|
||||
$dockerUpdate = null;
|
||||
ini_set('max_execution_time', 0);
|
||||
set_time_limit(0);
|
||||
chdir('/etc/cont-init.d/');
|
||||
if (file_exists('./30-install')) {
|
||||
$this->setAPIResponse('error', 'Update failed - OrgTools is deprecated - please use organizr/organizr', 500);
|
||||
return false;
|
||||
} elseif (file_exists('./40-install')) {
|
||||
$dockerUpdate = shell_exec('./40-install');
|
||||
}
|
||||
$this->removeUpdateStatusFile();
|
||||
if ($dockerUpdate) {
|
||||
$this->setAPIResponse('success', $dockerUpdate, 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Update failed', 500);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function windowsUpdate()
|
||||
{
|
||||
if ($this->docker || $this->getOS() !== 'win') {
|
||||
$this->setResponse(409, 'Your install type is not Windows');
|
||||
return false;
|
||||
}
|
||||
if ($this->hasUpdateStatusFile()) {
|
||||
$this->setResponse(500, 'Already Update in progress');
|
||||
return false;
|
||||
} else {
|
||||
$this->createUpdateStatusFile();
|
||||
}
|
||||
$branch = ($this->config['branch'] == 'v2-master') ? '-m' : '-d';
|
||||
ini_set('max_execution_time', 0);
|
||||
set_time_limit(0);
|
||||
$logFile = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'log.txt';
|
||||
$windowsScript = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'windows-update.bat ' . $branch . ' > ' . $logFile . ' 2>&1';
|
||||
$windowsUpdate = shell_exec($windowsScript);
|
||||
$this->removeUpdateStatusFile();
|
||||
if ($windowsUpdate) {
|
||||
$this->setAPIResponse('success', $windowsUpdate, 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('success', 'Update Complete - check log.txt for output', 200);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function linuxUpdate()
|
||||
{
|
||||
if ($this->docker || $this->getOS() == 'win') {
|
||||
$this->setResponse(409, 'Your install type is not Linux');
|
||||
return false;
|
||||
}
|
||||
if ($this->hasUpdateStatusFile()) {
|
||||
$this->setResponse(500, 'Already Update in progress');
|
||||
return false;
|
||||
} else {
|
||||
$this->createUpdateStatusFile();
|
||||
}
|
||||
$branch = $this->config['branch'];
|
||||
ini_set('max_execution_time', 0);
|
||||
set_time_limit(0);
|
||||
$logFile = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'log.txt';
|
||||
$script = $this->root . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'linux-update.sh';
|
||||
$scriptExec = $script . ' ' . $branch . ' > ' . $logFile . ' 2>&1';
|
||||
$checkScript = $this->testScriptFilePermissions($script);
|
||||
if (!$checkScript) {
|
||||
$this->setResponse(500, 'Update script permissions error');
|
||||
$this->removeUpdateStatusFile();
|
||||
return false;
|
||||
}
|
||||
$update = shell_exec($scriptExec);
|
||||
$this->removeUpdateStatusFile();
|
||||
if ($update) {
|
||||
$this->setAPIResponse('success', $update, 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('success', 'Update Complete - check log.txt for output', 200);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function upgradeInstall($branch = 'v2-master', $stage = '1')
|
||||
{
|
||||
// may kill this function in place for php script to run elsewhere
|
||||
if ($this->docker) {
|
||||
$this->setAPIResponse('error', 'Cannot perform update action on docker install - use script', 500);
|
||||
return false;
|
||||
}
|
||||
if ($this->getOS() == 'win') {
|
||||
$this->setAPIResponse('error', 'Cannot perform update action on windows install - use script', 500);
|
||||
return false;
|
||||
}
|
||||
$notWritable = array_search(false, $this->pathsWritable($this->paths));
|
||||
if ($notWritable == false) {
|
||||
ini_set('max_execution_time', 0);
|
||||
set_time_limit(0);
|
||||
$url = 'https://github.com/causefx/Organizr/archive/' . $branch . '.zip';
|
||||
$file = "upgrade.zip";
|
||||
$source = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'upgrade' . DIRECTORY_SEPARATOR . 'Organizr-' . str_replace('v2', '2', $branch) . DIRECTORY_SEPARATOR;
|
||||
$cleanup = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . "upgrade" . DIRECTORY_SEPARATOR;
|
||||
$destination = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR;
|
||||
switch ($stage) {
|
||||
case '1':
|
||||
$this->setLoggerChannel('Update')->info('Started Upgrade Process');
|
||||
if ($this->downloadFile($url, $file)) {
|
||||
$this->setLoggerChannel('Update')->info('Downloaded Update File for Branch: ' . $branch);
|
||||
$this->setAPIResponse('success', 'Downloaded file successfully', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Update')->warning('Downloaded Update File Failed for Branch: ' . $branch);
|
||||
$this->setAPIResponse('error', 'Download failed', 500);
|
||||
return false;
|
||||
}
|
||||
case '2':
|
||||
if ($this->unzipFile($file)) {
|
||||
$this->setLoggerChannel('Update')->info('Unzipped Update File for Branch: ' . $branch);
|
||||
$this->setAPIResponse('success', 'Unzipped file successfully', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Update')->warning('Unzip Failed for Branch: ' . $branch);
|
||||
$this->setAPIResponse('error', 'Unzip failed', 500);
|
||||
return false;
|
||||
}
|
||||
case '3':
|
||||
if ($this->rcopy($source, $destination)) {
|
||||
$this->setLoggerChannel('Update')->info('Files overwritten using Updated Files from Branch: ' . $branch);
|
||||
$updateComplete = $this->config['dbLocation'] . 'completed.txt';
|
||||
if (!file_exists($updateComplete)) {
|
||||
touch($updateComplete);
|
||||
}
|
||||
$this->setAPIResponse('success', 'Files replaced successfully', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Update')->warning('Overwrite Failed for Branch: ' . $branch);
|
||||
$this->setAPIResponse('error', 'File replacement failed', 500);
|
||||
return false;
|
||||
}
|
||||
case '4':
|
||||
if ($this->rrmdir($cleanup)) {
|
||||
$this->setLoggerChannel('Update')->info('Deleted Update Files from Branch: ' . $branch);
|
||||
$this->setLoggerChannel('Update')->info('Update Completed');
|
||||
$this->setAPIResponse('success', 'Removed update files successfully', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Update')->warning('Removal of Update Files Failed for Branch: ' . $branch);
|
||||
$this->setAPIResponse('error', 'File removal failed', 500);
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
$this->setAPIResponse('error', 'Action not setup', 500);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'File permissions not set correctly', 500);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,768 @@
|
||||
<?php
|
||||
|
||||
trait UpgradeFunctions
|
||||
{
|
||||
public function upgradeCheck()
|
||||
{
|
||||
if (!$this->checkForUpdates) {
|
||||
return true;
|
||||
}
|
||||
if ($this->hasDB()) {
|
||||
$tempLock = $this->config['dbLocation'] . 'DBLOCK.txt';
|
||||
$updateComplete = $this->config['dbLocation'] . 'completed.txt';
|
||||
$cleanup = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'upgrade' . DIRECTORY_SEPARATOR;
|
||||
if (file_exists($updateComplete)) {
|
||||
@unlink($updateComplete);
|
||||
@$this->rrmdir($cleanup);
|
||||
}
|
||||
if (file_exists($tempLock)) {
|
||||
die($this->showHTML('Upgrading', 'Please wait...'));
|
||||
}
|
||||
$updateDB = false;
|
||||
$updateSuccess = true;
|
||||
$compare = new Composer\Semver\Comparator;
|
||||
$oldVer = $this->config['configVersion'];
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.0.0-beta-200';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = true;
|
||||
$oldVer = $versionCheck;
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.0.0-beta-500';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = true;
|
||||
$oldVer = $versionCheck;
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.0.0-beta-800';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = true;
|
||||
$oldVer = $versionCheck;
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.1.0';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = false;
|
||||
$oldVer = $versionCheck;
|
||||
$this->upgradeToVersion($versionCheck);
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.1.400';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = false;
|
||||
$oldVer = $versionCheck;
|
||||
$this->upgradeToVersion($versionCheck);
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.1.525';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = false;
|
||||
$oldVer = $versionCheck;
|
||||
$this->upgradeToVersion($versionCheck);
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.1.860';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = false;
|
||||
$oldVer = $versionCheck;
|
||||
$this->upgradeToVersion($versionCheck);
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.1.1500';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = false;
|
||||
$oldVer = $versionCheck;
|
||||
$this->upgradeToVersion($versionCheck);
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.1.1860';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = false;
|
||||
$oldVer = $versionCheck;
|
||||
$this->upgradeToVersion($versionCheck);
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
// Upgrade check start for version below
|
||||
$versionCheck = '2.1.2200';
|
||||
if ($compare->lessThan($oldVer, $versionCheck)) {
|
||||
$updateDB = false;
|
||||
$oldVer = $versionCheck;
|
||||
$this->upgradeToVersion($versionCheck);
|
||||
}
|
||||
// End Upgrade check start for version above
|
||||
if ($updateDB == true) {
|
||||
//return 'Upgraded Needed - Current Version '.$oldVer.' - New Version: '.$versionCheck;
|
||||
// Upgrade database to latest version
|
||||
$updateSuccess = $this->updateDB($oldVer);
|
||||
}
|
||||
// Update config.php version if different to the installed version
|
||||
if ($updateSuccess && $this->version !== $this->config['configVersion']) {
|
||||
$this->updateConfig(array('apply_CONFIG_VERSION' => $this->version));
|
||||
$this->setLoggerChannel('Update')->notice('Updated config version to ' . $this->version);
|
||||
}
|
||||
if ($updateSuccess == false) {
|
||||
die($this->showHTML('Database update failed', 'Please manually check logs and fix - Then reload this page'));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function dropColumnFromDatabase($table = '', $columnName = '')
|
||||
{
|
||||
if ($table == '' || $columnName == '') {
|
||||
return false;
|
||||
}
|
||||
if ($this->hasDB()) {
|
||||
$columnExists = $this->checkIfColumnExists($table, $columnName);
|
||||
if ($columnExists) {
|
||||
$columnAlter = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => ['ALTER TABLE %n DROP %n',
|
||||
(string)$table,
|
||||
(string)$columnName,
|
||||
]
|
||||
)
|
||||
];
|
||||
$AlterQuery = $this->processQueries($columnAlter);
|
||||
return (boolean)($AlterQuery);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkIfColumnExists($table = '', $columnName = '')
|
||||
{
|
||||
if ($table == '' || $columnName == '') {
|
||||
return false;
|
||||
}
|
||||
if ($this->hasDB()) {
|
||||
if ($this->config['driver'] === 'sqlite3') {
|
||||
$term = 'SELECT COUNT(*) AS has_column FROM pragma_table_info(?) WHERE name=?';
|
||||
} else {
|
||||
$term = 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . $this->config['dbName'] . '" AND TABLE_NAME=? AND COLUMN_NAME=?';
|
||||
}
|
||||
$tableInfo = [
|
||||
array(
|
||||
'function' => 'fetchSingle',
|
||||
'query' => array(
|
||||
$term,
|
||||
(string)$table,
|
||||
(string)$columnName
|
||||
)
|
||||
)
|
||||
];
|
||||
$query = $this->processQueries($tableInfo);
|
||||
return (boolean)($query);
|
||||
}
|
||||
}
|
||||
|
||||
public function addColumnToDatabase($table = '', $columnName = '', $definition = 'TEXT')
|
||||
{
|
||||
if ($table == '' || $columnName == '' || $definition == '') {
|
||||
return false;
|
||||
}
|
||||
if ($this->hasDB()) {
|
||||
if ($this->config['driver'] === 'sqlite3') {
|
||||
$term = 'SELECT COUNT(*) AS has_column FROM pragma_table_info(?) WHERE name=?';
|
||||
} else {
|
||||
$term = 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "' . $this->config['dbName'] . '" AND TABLE_NAME=? AND COLUMN_NAME=?';
|
||||
}
|
||||
$tableInfo = [
|
||||
array(
|
||||
'function' => 'fetchSingle',
|
||||
'query' => array(
|
||||
$term,
|
||||
(string)$table,
|
||||
(string)$columnName
|
||||
)
|
||||
)
|
||||
];
|
||||
$query = $this->processQueries($tableInfo);
|
||||
if (!$query) {
|
||||
$columnAlter = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => ['ALTER TABLE %n ADD %n ' . (string)$definition,
|
||||
(string)$table,
|
||||
(string)$columnName,
|
||||
]
|
||||
)
|
||||
];
|
||||
$AlterQuery = $this->processQueries($columnAlter);
|
||||
if ($AlterQuery) {
|
||||
$query = $this->processQueries($tableInfo);
|
||||
if ($query) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function createMysqliDatabase($database, $migration = false)
|
||||
{
|
||||
$query = [
|
||||
array(
|
||||
'function' => 'fetchAll',
|
||||
'query' => array(
|
||||
'DROP DATABASE IF EXISTS tempMigration'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'function' => 'fetchAll',
|
||||
'query' => array(
|
||||
'CREATE DATABASE IF NOT EXISTS %n',
|
||||
$database
|
||||
)
|
||||
),
|
||||
];
|
||||
//$query = ['CREATE DB %n', $database];
|
||||
return $this->processQueries($query, $migration);
|
||||
}
|
||||
|
||||
public function updateDB($oldVerNum = false)
|
||||
{
|
||||
$tempLock = $this->config['dbLocation'] . 'DBLOCK.txt';
|
||||
if (!file_exists($tempLock)) {
|
||||
touch($tempLock);
|
||||
$migrationDB = 'tempMigration.db';
|
||||
$pathDigest = pathinfo($this->config['dbLocation'] . $this->config['dbName']);
|
||||
// Delete old backup sqlite db if exists
|
||||
if (file_exists($this->config['dbLocation'] . $migrationDB)) {
|
||||
unlink($this->config['dbLocation'] . $migrationDB);
|
||||
}
|
||||
// Create Temp DB First
|
||||
$this->createNewDB('tempMigration', true);
|
||||
$this->connectOtherDB();
|
||||
if ($this->config['driver'] == 'sqlite3') {
|
||||
// Backup sqlite database
|
||||
$backupDB = $pathDigest['dirname'] . '/' . $pathDigest['filename'] . '[' . date('Y-m-d_H-i-s') . ']' . ($oldVerNum ? '[' . $oldVerNum . ']' : '') . '.bak.db';
|
||||
copy($this->config['dbLocation'] . $this->config['dbName'], $backupDB);
|
||||
}
|
||||
$success = $this->createDB($this->config['dbLocation'], true);
|
||||
if ($success) {
|
||||
switch ($this->config['driver']) {
|
||||
case 'sqlite3':
|
||||
$query = 'SELECT name FROM sqlite_master WHERE type="table"';
|
||||
break;
|
||||
case 'mysqli':
|
||||
$query = 'SELECT Table_name as name from information_schema.tables where table_schema = "tempMigration"';
|
||||
break;
|
||||
}
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'fetchAll',
|
||||
'query' => array(
|
||||
$query
|
||||
)
|
||||
),
|
||||
];
|
||||
$tables = $this->processQueries($response);
|
||||
$defaultTables = $this->getDefaultTablesFormatted();
|
||||
foreach ($tables as $table) {
|
||||
if (in_array($table['name'], $defaultTables)) {
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'fetchAll',
|
||||
'query' => array(
|
||||
'SELECT * FROM %n', $table['name']
|
||||
)
|
||||
),
|
||||
];
|
||||
$data = $this->processQueries($response);
|
||||
$this->setLoggerChannel('Migration')->info('Obtained Table data', ['table' => $table['name']]);
|
||||
foreach ($data as $row) {
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => array(
|
||||
'INSERT into %n', $table['name'],
|
||||
$row
|
||||
)
|
||||
),
|
||||
];
|
||||
$this->processQueries($response, true);
|
||||
}
|
||||
$this->setLoggerChannel('Migration')->info('Wrote Table data', ['table' => $table['name']]);
|
||||
}
|
||||
}
|
||||
if ($this->config['driver'] == 'mysqli') {
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => array(
|
||||
'DROP DATABASE IF EXISTS %n', $this->config['dbName']
|
||||
)
|
||||
),
|
||||
];
|
||||
$data = $this->processQueries($response);
|
||||
if ($data) {
|
||||
$create = $this->createNewDB($this->config['dbName']);
|
||||
if ($create) {
|
||||
$structure = $this->createDB($this->config['dbLocation']);
|
||||
if ($structure) {
|
||||
foreach ($tables as $table) {
|
||||
if (in_array($table['name'], $defaultTables)) {
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'fetchAll',
|
||||
'query' => array(
|
||||
'SELECT * FROM %n', $table['name']
|
||||
)
|
||||
),
|
||||
];
|
||||
$data = $this->processQueries($response, true);
|
||||
$this->setLoggerChannel('Migration')->info('Obtained Table data', ['table' => $table['name']]);
|
||||
foreach ($data as $row) {
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => array(
|
||||
'INSERT into %n', $table['name'],
|
||||
$row
|
||||
)
|
||||
),
|
||||
];
|
||||
$this->processQueries($response);
|
||||
}
|
||||
$this->setLoggerChannel('Migration')->info('Wrote Table data', ['table' => $table['name']]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('Migration')->warning('Could not recreate Database structure');
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('Migration')->warning('Could not recreate Database');
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('Migration')->warning('Could not drop old tempMigration Database');
|
||||
}
|
||||
$this->setLoggerChannel('Migration')->info('All Table data converted');
|
||||
@unlink($tempLock);
|
||||
return true;
|
||||
}
|
||||
//$this->db->disconnect();
|
||||
//$this->otherDb->disconnect();
|
||||
// Remove Current Database
|
||||
if ($this->config['driver'] == 'sqlite3') {
|
||||
$this->setLoggerChannel('Migration')->info('All Table data converted');
|
||||
$this->setLoggerChannel('Migration')->info('Starting Database movement for sqlite3');
|
||||
if (file_exists($this->config['dbLocation'] . $migrationDB)) {
|
||||
$oldFileSize = filesize($this->config['dbLocation'] . $this->config['dbName']);
|
||||
$newFileSize = filesize($this->config['dbLocation'] . $migrationDB);
|
||||
if ($newFileSize > 0) {
|
||||
$this->setLoggerChannel('Migration')->info('New Table size has been verified');
|
||||
@unlink($this->config['dbLocation'] . $this->config['dbName']);
|
||||
copy($this->config['dbLocation'] . $migrationDB, $this->config['dbLocation'] . $this->config['dbName']);
|
||||
@unlink($this->config['dbLocation'] . $migrationDB);
|
||||
$this->setLoggerChannel('Migration')->info('Migrated Old Info to new Database');
|
||||
@unlink($tempLock);
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Migration')->warning('Database filesize is zero');
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('Migration')->warning('Migration Database does not exist');
|
||||
}
|
||||
}
|
||||
@unlink($tempLock);
|
||||
return false;
|
||||
} else {
|
||||
$this->setLoggerChannel('Migration')->warning('Could not create migration Database');
|
||||
}
|
||||
@unlink($tempLock);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function resetUpdateFeature($feature = null)
|
||||
{
|
||||
if (!$feature) {
|
||||
$this->setResponse(409, 'Feature not supplied');
|
||||
return false;
|
||||
}
|
||||
$feature = strtolower($feature);
|
||||
switch ($feature) {
|
||||
case 'groupmax':
|
||||
case 'groupidmax':
|
||||
case 'group-max':
|
||||
case 'group-id-max':
|
||||
case 'group_id':
|
||||
case 'group_id_max':
|
||||
$columnExists = $this->checkIfColumnExists('tabs', 'group_id_max');
|
||||
if ($columnExists) {
|
||||
$query = [
|
||||
[
|
||||
'function' => 'query',
|
||||
'query' => [
|
||||
'UPDATE tabs SET group_id_max=0'
|
||||
]
|
||||
],
|
||||
];
|
||||
$tabs = $this->processQueries($query);
|
||||
if (!$tabs) {
|
||||
$this->setResponse(500, 'An error occurred');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setResponse(500, 'group_id_max column does not exist');
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
$this->setResponse(404, 'Feature not found in reset update');
|
||||
return false;
|
||||
}
|
||||
$this->setResponse(200, 'Ran reset update feature for ' . $feature);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function upgradeToVersion($version = '2.1.0')
|
||||
{
|
||||
$this->setLoggerChannel('Upgrade')->notice('Starting upgrade to version ' . $version);
|
||||
switch ($version) {
|
||||
case '2.1.0':
|
||||
$this->upgradeSettingsTabURL();
|
||||
$this->upgradeHomepageTabURL();
|
||||
break;
|
||||
case '2.1.400':
|
||||
$this->removeOldPluginDirectoriesAndFiles();
|
||||
break;
|
||||
case '2.1.525':
|
||||
$this->removeOldCustomHTML();
|
||||
break;
|
||||
case '2.1.860':
|
||||
$this->upgradeInstalledPluginsConfigItem();
|
||||
break;
|
||||
case '2.1.1500':
|
||||
$this->upgradeDataToFolder();
|
||||
break;
|
||||
case '2.1.1860':
|
||||
$this->upgradePluginsToDataFolder();
|
||||
break;
|
||||
case '2.1.2200':
|
||||
$this->backupOrganizr();
|
||||
$this->addGroupIdMaxToDatabase();
|
||||
$this->addAddToAdminToDatabase();
|
||||
break;
|
||||
}
|
||||
$this->setLoggerChannel('Upgrade')->notice('Finished upgrade to version ' . $version);
|
||||
$this->setAPIResponse('success', 'Ran update function for version: ' . $version, 200);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeOldCacheFolder()
|
||||
{
|
||||
$folder = $this->root . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$this->setLoggerChannel('Migration');
|
||||
$this->logger->info('Running Old Cache folder migration');
|
||||
if (file_exists($folder)) {
|
||||
$this->rrmdir($folder);
|
||||
$this->logger->info('Old Cache folder found');
|
||||
$this->logger->info('Removed Old Cache folder');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function upgradeDataToFolder()
|
||||
{
|
||||
if ($this->hasDB()) {
|
||||
// Make main data folder
|
||||
$rootFolderMade = $this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data');
|
||||
// Make config folder child
|
||||
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR);
|
||||
|
||||
if ($rootFolderMade) {
|
||||
// Migrate over userTabs folder
|
||||
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'userTabs');
|
||||
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'userTabs', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'userTabs')) {
|
||||
// Convert tabs over
|
||||
$query = [
|
||||
[
|
||||
'function' => 'fetchAll',
|
||||
'query' => [
|
||||
'SELECT * FROM tabs WHERE image like "%userTabs%"'
|
||||
]
|
||||
],
|
||||
];
|
||||
$tabs = $this->processQueries($query);
|
||||
if (count($tabs) > 0) {
|
||||
foreach ($tabs as $tab) {
|
||||
$newImage = str_replace('plugins/images/userTabs', 'data/userTabs', $tab['image']);
|
||||
$updateQuery = [
|
||||
[
|
||||
'function' => 'query',
|
||||
'query' => [
|
||||
'UPDATE tabs SET',
|
||||
['image' => $newImage],
|
||||
'WHERE id = ?',
|
||||
$tab['id']
|
||||
]
|
||||
],
|
||||
];
|
||||
$this->processQueries($updateQuery);
|
||||
}
|
||||
}
|
||||
$this->setLoggerChannel('Migration');
|
||||
$this->logger->info('The folder "userTabs" was migrated to new data folder');
|
||||
}
|
||||
// Migrate over custom cert
|
||||
if (file_exists($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem')) {
|
||||
// Make cert folder child
|
||||
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR);
|
||||
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'functions' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'custom.pem')) {
|
||||
$this->setLoggerChannel('Migration');
|
||||
$this->logger->info('Moved over custom cert file');
|
||||
}
|
||||
}
|
||||
// Migrate over favIcon
|
||||
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'favicon');
|
||||
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'faviconCustom', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'favicon')) {
|
||||
if ($this->config['favIcon'] !== '') {
|
||||
$this->config['favIcon'] = str_replace('plugins/images/faviconCustom', 'data/favicon', $this->config['favIcon']);
|
||||
$this->updateConfig(array('favIcon' => $this->config['favIcon']));
|
||||
}
|
||||
$this->setLoggerChannel('Migration');
|
||||
$this->logger->info('Favicon was migrated over');
|
||||
}
|
||||
// Migrate over custom pages
|
||||
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'pages');
|
||||
if (file_exists($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'custom')) {
|
||||
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'custom', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'pages')) {
|
||||
$this->rrmdir($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'custom');
|
||||
$this->setLoggerChannel('Migration');
|
||||
$this->logger->info('Custom pages was migrated over');
|
||||
}
|
||||
}
|
||||
// Migrate over custom routes
|
||||
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'routes');
|
||||
if (file_exists($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'v2' . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . 'custom')) {
|
||||
if ($this->rcopy($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'v2' . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . 'custom', $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'routes')) {
|
||||
$this->rrmdir($this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'v2' . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . 'custom');
|
||||
$this->setLoggerChannel('Migration');
|
||||
$this->logger->info('Custom routes was migrated over');
|
||||
}
|
||||
}
|
||||
// Migrate over cache folder
|
||||
$this->removeOldCacheFolder();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function upgradePluginsToDataFolder()
|
||||
{
|
||||
if ($this->hasDB()) {
|
||||
// Make main data folder
|
||||
$rootFolderMade = $this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data');
|
||||
if ($rootFolderMade) {
|
||||
// Migrate over plugins folder
|
||||
$this->makeDir($this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins');
|
||||
$plexLibraries = $this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'plexLibraries';
|
||||
if (file_exists($plexLibraries)) {
|
||||
if (rename($plexLibraries, $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'plexLibraries')) {
|
||||
$this->setLoggerChannel('Migration');
|
||||
$this->logger->info('The plugin folder "plexLibraries" was migrated to new data folder');
|
||||
}
|
||||
}
|
||||
$test = $this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'test';
|
||||
if (file_exists($test)) {
|
||||
if (rename($test, $this->root . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'test')) {
|
||||
$this->setLoggerChannel('Migration');
|
||||
$this->logger->info('The plugin folder "test" was migrated to new data folder');
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function upgradeSettingsTabURL()
|
||||
{
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => array(
|
||||
'UPDATE tabs SET',
|
||||
['url' => 'api/v2/page/settings'],
|
||||
'WHERE url = ?',
|
||||
'api/?v1/settings/page'
|
||||
)
|
||||
),
|
||||
];
|
||||
return $this->processQueries($response);
|
||||
}
|
||||
|
||||
public function upgradeHomepageTabURL()
|
||||
{
|
||||
$response = [
|
||||
array(
|
||||
'function' => 'query',
|
||||
'query' => array(
|
||||
'UPDATE tabs SET',
|
||||
['url' => 'api/v2/page/homepage'],
|
||||
'WHERE url = ?',
|
||||
'api/?v1/homepage/page'
|
||||
)
|
||||
),
|
||||
];
|
||||
return $this->processQueries($response);
|
||||
}
|
||||
|
||||
public function upgradeInstalledPluginsConfigItem()
|
||||
{
|
||||
$oldConfigItem = $this->config['installedPlugins'];
|
||||
if (gettype($oldConfigItem) == 'string') {
|
||||
if ((strpos($oldConfigItem, '|') !== false)) {
|
||||
$newPlugins = [];
|
||||
$plugins = explode('|', $oldConfigItem);
|
||||
foreach ($plugins as $plugin) {
|
||||
$info = explode(':', $plugin);
|
||||
$newPlugins[$info[0]] = [
|
||||
'name' => $info[0],
|
||||
'version' => $info[1],
|
||||
'repo' => 'organizr'
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$newPlugins = [];
|
||||
if ($oldConfigItem !== '') {
|
||||
$info = explode(':', $oldConfigItem);
|
||||
$newPlugins[$info[0]] = [
|
||||
'name' => $info[0],
|
||||
'version' => $info[1],
|
||||
'repo' => 'https://github.com/Organizr/Organizr-Plugins'
|
||||
];
|
||||
}
|
||||
}
|
||||
$this->updateConfig(['installedPlugins' => $newPlugins]);
|
||||
} elseif (gettype($oldConfigItem) == 'array') {
|
||||
$this->updateConfig(['installedPlugins' => $oldConfigItem]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeOldPluginDirectoriesAndFiles()
|
||||
{
|
||||
$folders = [
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'api',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'config',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'css',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'js',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'misc',
|
||||
];
|
||||
$files = [
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'bookmark.php',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'chat.php',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'healthChecks.php',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'invites.php',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'php-mailer.php',
|
||||
$this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'speedTest.php',
|
||||
];
|
||||
foreach ($files as $file) {
|
||||
if (file_exists($file)) {
|
||||
@unlink($file);
|
||||
}
|
||||
}
|
||||
foreach ($folders as $folder) {
|
||||
if (file_exists($folder)) {
|
||||
@$this->rrmdir($folder);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function checkForConfigKeyAddToArray($keys)
|
||||
{
|
||||
$updateItems = [];
|
||||
foreach ($keys as $new => $old) {
|
||||
if (isset($this->config[$old])) {
|
||||
if ($this->config[$old] !== '') {
|
||||
$updateItemsNew = [$new => $this->config[$old]];
|
||||
$updateItems = array_merge($updateItems, $updateItemsNew);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $updateItems;
|
||||
}
|
||||
|
||||
public function removeOldCustomHTML()
|
||||
{
|
||||
$backup = $this->backupOrganizr();
|
||||
if ($backup) {
|
||||
$keys = [
|
||||
'homepageCustomHTML01Enabled' => 'homepageCustomHTMLoneEnabled',
|
||||
'homepageCustomHTML01Auth' => 'homepageCustomHTMLoneAuth',
|
||||
'customHTML01' => 'customHTMLone',
|
||||
'homepageCustomHTML02Enabled' => 'homepageCustomHTMLtwoEnabled',
|
||||
'homepageCustomHTML02Auth' => 'homepageCustomHTMLtwoAuth',
|
||||
'customHTML02' => 'customHTMLtwo',
|
||||
];
|
||||
$updateItems = $this->checkForConfigKeyAddToArray($keys);
|
||||
$updateComplete = false;
|
||||
if (!empty($updateItems)) {
|
||||
$updateComplete = $this->updateConfig($updateItems);
|
||||
}
|
||||
if ($updateComplete) {
|
||||
$removeConfigItems = $this->removeConfigItem(['homepagCustomHTMLoneAuth', 'homepagCustomHTMLoneEnabled', 'homepagCustomHTMLtwoAuth', 'homepagCustomHTMLtwoEnabled', 'homepageOrdercustomhtml', 'homepageOrdercustomhtmlTwo', 'homepageCustomHTMLoneEnabled', 'homepageCustomHTMLoneAuth', 'customHTMLone', 'homepageCustomHTMLtwoEnabled', 'homepageCustomHTMLtwoAuth', 'customHTMLtwo']);
|
||||
if ($removeConfigItems) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function addGroupIdMaxToDatabase()
|
||||
{
|
||||
$this->setLoggerChannel('Database Migration')->info('Starting database update');
|
||||
$hasOldColumn = $this->checkIfColumnExists('tabs', 'group_id_min');
|
||||
if ($hasOldColumn) {
|
||||
$this->setLoggerChannel('Database Migration')->info('Cleaning up database by removing old group_id_min');
|
||||
$removeColumn = $this->dropColumnFromDatabase('tabs', 'group_id_min');
|
||||
if ($removeColumn) {
|
||||
$this->setLoggerChannel('Database Migration')->info('Removed group_id_min from database');
|
||||
} else {
|
||||
$this->setLoggerChannel('Database Migration')->warning('Error removing group_id_min from database');
|
||||
}
|
||||
}
|
||||
$addColumn = $this->addColumnToDatabase('tabs', 'group_id_max', 'INTEGER DEFAULT \'0\'');
|
||||
if ($addColumn) {
|
||||
$this->setLoggerChannel('Database Migration')->notice('Added group_id_max to database');
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Database Migration')->warning('Could not update database');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function addAddToAdminToDatabase()
|
||||
{
|
||||
$this->setLoggerChannel('Database Migration')->info('Starting database update');
|
||||
$addColumn = $this->addColumnToDatabase('tabs', 'add_to_admin', 'INTEGER DEFAULT \'0\'');
|
||||
if ($addColumn) {
|
||||
$this->setLoggerChannel('Database Migration')->notice('Added add_to_admin to database');
|
||||
return true;
|
||||
} else {
|
||||
$this->setLoggerChannel('Database Migration')->warning('Could not update database');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
trait AdGuardHomepageItem
|
||||
{
|
||||
public function adguardSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'AdGuardHome',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/AdGuardHome.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageAdGuardEnabled'),
|
||||
$this->settingsOption('auth', 'homepageAdGuardAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'adguardURL', ['help' => 'Please make sure to use local IP address and port. You can add multiple AdGuard Homes by comma separating the URLs.', 'placeholder' => 'http(s)://hostname:port']),
|
||||
$this->settingsOption('username', 'adGuardUsername'),
|
||||
$this->settingsOption('password', 'adGuardPassword'),
|
||||
],
|
||||
'Misc' => [
|
||||
$this->settingsOption('toggle-title', 'adguardHeaderToggle'),
|
||||
$this->settingsOption('switch', 'homepageAdGuardCombine', ['label' => 'Combine stat cards', 'help' => 'This controls whether to combine the stats for multiple adguard instances into 1 card.']),
|
||||
],
|
||||
'Stats' => [
|
||||
$this->settingsOption('switch', 'adguardQueriesToggle', ['label' => 'Total Queries']),
|
||||
$this->settingsOption('switch', 'adguardQueriesBlockedToggle', ['label' => 'Queries Blocked']),
|
||||
$this->settingsOption('switch', 'adguardPercentToggle', ['label' => 'Percent Blocked']),
|
||||
$this->settingsOption('switch', 'adguardProcessingToggle', ['label' => 'Processing Time']),
|
||||
$this->settingsOption('switch', 'adguardDomainListToggle', ['label' => 'Domains on Blocklist']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'adguard'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionAdGuard()
|
||||
{
|
||||
if (empty($this->config['adguardURL'])) {
|
||||
$this->setAPIResponse('error', 'AdGuard URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$api = array();
|
||||
$failed = false;
|
||||
$errors = '';
|
||||
$urls = explode(',', $this->config['adguardURL']);
|
||||
foreach ($urls as $url) {
|
||||
$url = $url . '/control/stats';
|
||||
try {
|
||||
$options = array(
|
||||
'auth' => array($this->config['adGuardUsername'], $this->decrypt($this->config['adGuardPassword']))
|
||||
);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
@$test = json_decode($response->body, true);
|
||||
if (!is_array($test)) {
|
||||
$ip = $this->qualifyURL($url, true)['host'];
|
||||
$errors .= $ip . ': Response was not JSON';
|
||||
$failed = true;
|
||||
}
|
||||
}
|
||||
if (!$response->success) {
|
||||
$ip = $this->qualifyURL($url, true)['host'];
|
||||
$errors .= $ip . ": Unknown Failure";
|
||||
$failed = true;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$failed = true;
|
||||
$ip = $this->qualifyURL($url, true)['host'];
|
||||
$errors .= $ip . ': ' . $e->getMessage();
|
||||
$this->setLoggerChannel('AdGuard')->error($e);
|
||||
};
|
||||
}
|
||||
if ($failed) {
|
||||
$this->setAPIResponse('error', $errors, 500);
|
||||
return false;
|
||||
} else {
|
||||
$this->setAPIResponse('success', null, 200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function adguardHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageAdGuardEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageAdGuardAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'adguardURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderAdGuard()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->adguardHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading AdGuard...</h2></div>
|
||||
<script>
|
||||
// Pi-hole Stats
|
||||
homepageAdGuard("' . $this->config['homepageAdGuardRefresh'] . '");
|
||||
// End Pi-hole Stats
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getAdGuardHomepageStats()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->adguardHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$stats = array();
|
||||
$urls = explode(',', $this->config['adguardURL']);
|
||||
foreach ($urls as $url) {
|
||||
$stats_url = $url . '/control/stats?';
|
||||
$filter_url = $url . '/control/filtering/status?';
|
||||
try {
|
||||
$options = array(
|
||||
'auth' => array($this->config['adGuardUsername'], $this->decrypt($this->config['adGuardPassword']))
|
||||
);
|
||||
$response = Requests::get($stats_url, [], $options);
|
||||
if ($response->success) {
|
||||
@$adguardResults = json_decode($response->body, true);
|
||||
if (is_array($adguardResults)) {
|
||||
$ip = $this->qualifyURL($stats_url, true)['host'];
|
||||
$stats['data'][$ip] = $adguardResults;
|
||||
}
|
||||
}
|
||||
$response = Requests::get($filter_url, [], $options);
|
||||
if ($response->success) {
|
||||
@$adguardFilterResults = json_decode($response->body, true);
|
||||
if (is_array($adguardFilterResults)) {
|
||||
$ip = $this->qualifyURL($filter_url, true)['host'];
|
||||
$stats['filters'][$ip] = $adguardFilterResults;
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
$this->setLoggerChannel('AdGuard')->error($e);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
$stats['options']['combine'] = $this->config['homepageAdGuardCombine'];
|
||||
$stats['options']['title'] = $this->config['adguardHeaderToggle'];
|
||||
$stats['options']['queries'] = $this->config['adguardQueriesToggle'];
|
||||
$stats['options']['blocked_count'] = $this->config['adguardQueriesBlockedToggle'];
|
||||
$stats['options']['blocked_percent'] = $this->config['adguardPercentToggle'];
|
||||
$stats['options']['processing_time'] = $this->config['adguardProcessingToggle'];
|
||||
$stats['options']['domain_count'] = $this->config['adguardDomainListToggle'];
|
||||
$stats = isset($stats) ? $stats : null;
|
||||
$this->setAPIResponse('success', null, 200, $stats);
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
trait BookmarksHomepageItem
|
||||
{
|
||||
public function bookmarksSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Bookmarks',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/bookmark.png',
|
||||
'category' => 'Links',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageBookmarksEnabled'),
|
||||
$this->settingsOption('auth', 'homepageBookmarksAuth'),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('title', 'homepageBookmarksHeader'),
|
||||
$this->settingsOption('toggle-title', 'homepageBookmarksHeaderToggle'),
|
||||
$this->settingsOption('enable', 'homepageBookmarksEnabled', ['label' => 'Enable Bookmarks', 'help' => 'Toggles the view module for Bookmarks']),
|
||||
$this->settingsOption('refresh', 'homepageBookmarksRefresh'),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function bookmarksHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageBookmarksEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageBookmarksAuth'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderBookmarks()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->bookmarksHomepagePermissions('main'))) {
|
||||
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Bookmarks...</h2></div>
|
||||
<script>
|
||||
// Bookmarks And Air
|
||||
homepageBookmarks("' . $this->config['homepageBookmarksRefresh'] . '");
|
||||
// End Bookmarks And Air
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
trait CalendarHomepageItem
|
||||
{
|
||||
public function calendarSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'iCal',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/calendar.png',
|
||||
'category' => 'HOMEPAGE',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageCalendarEnabled'),
|
||||
$this->settingsOption('auth', 'homepageCalendarAuth'),
|
||||
$this->settingsOption('multiple-url', 'calendariCal', ['label' => 'iCal URL\'s']),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('calendar-start', 'calendarStart'),
|
||||
$this->settingsOption('calendar-end', 'calendarEnd'),
|
||||
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
|
||||
$this->settingsOption('calendar-default-view', 'calendarDefault'),
|
||||
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
|
||||
$this->settingsOption('calendar-locale', 'calendarLocale'),
|
||||
$this->settingsOption('calendar-limit', 'calendarLimit'),
|
||||
$this->settingsOption('refresh', 'calendarRefresh'),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function calendarHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageCalendarEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageCalendarAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'calendariCal'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrdercalendar()
|
||||
{
|
||||
if (
|
||||
$this->homepageItemPermissions($this->sonarrHomepagePermissions('calendar')) ||
|
||||
$this->homepageItemPermissions($this->radarrHomepagePermissions('calendar')) ||
|
||||
$this->homepageItemPermissions($this->lidarrHomepagePermissions('calendar')) ||
|
||||
$this->homepageItemPermissions($this->sickrageHomepagePermissions('calendar')) ||
|
||||
$this->homepageItemPermissions($this->couchPotatoHomepagePermissions('calendar')) ||
|
||||
$this->homepageItemPermissions($this->traktHomepagePermissions('calendar')) ||
|
||||
$this->homepageItemPermissions($this->calendarHomepagePermissions('main'))
|
||||
) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div id="calendar" class="fc fc-ltr m-b-30"></div>
|
||||
<script>
|
||||
// Calendar
|
||||
homepageCalendar("' . $this->config['calendarRefresh'] . '");
|
||||
// End Calendar
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function loadCalendarJS()
|
||||
{
|
||||
$locale = ($this->config['calendarLocale'] !== 'en') ?? false;
|
||||
return ($locale) ? '<script src="plugins/bower_components/calendar/dist/lang-all.js"></script>' : '';
|
||||
}
|
||||
|
||||
public function getCalendar()
|
||||
{
|
||||
$startDate = date('Y-m-d', strtotime("-" . $this->config['calendarStart'] . " days"));
|
||||
$endDate = date('Y-m-d', strtotime("+" . $this->config['calendarEnd'] . " days"));
|
||||
$icalCalendarSources = array();
|
||||
$calendarItems = array();
|
||||
// SONARR CONNECT
|
||||
$items = $this->getSonarrCalendar($startDate, $endDate);
|
||||
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
|
||||
unset($items);
|
||||
// LIDARR CONNECT
|
||||
$items = $this->getLidarrCalendar($startDate, $endDate);
|
||||
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
|
||||
unset($items);
|
||||
// RADARR CONNECT
|
||||
$items = $this->getRadarrCalendar($startDate, $endDate);
|
||||
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
|
||||
unset($items);
|
||||
// SICKRAGE/BEARD/MEDUSA CONNECT
|
||||
$items = $this->getSickRageCalendar();
|
||||
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
|
||||
unset($items);
|
||||
// COUCHPOTATO CONNECT
|
||||
$items = $this->getCouchPotatoCalendar();
|
||||
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
|
||||
unset($items);
|
||||
// TRAKT CONNECT
|
||||
$items = $this->getTraktCalendar();
|
||||
$calendarItems = is_array($items) ? array_merge($calendarItems, $items) : $calendarItems;
|
||||
unset($items);
|
||||
// iCal URL
|
||||
$calendarSources['ical'] = $this->getICalendar();
|
||||
unset($items);
|
||||
// Finish
|
||||
$calendarSources['events'] = $calendarItems;
|
||||
$this->setAPIResponse('success', null, 200, $calendarSources);
|
||||
return $calendarSources;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
trait CouchPotatoHomepageItem
|
||||
{
|
||||
|
||||
public function couchPotatoSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'CouchPotato',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/couchpotato.png',
|
||||
'category' => 'PVR',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageCouchpotatoEnabled'),
|
||||
$this->settingsOption('auth', 'homepageCouchpotatoAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('multiple-url', 'couchpotatoURL'),
|
||||
$this->settingsOption('multiple-token', 'couchpotatoToken'),
|
||||
$this->settingsOption('disable-cert-check', 'couchpotatoDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'couchpotatoUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('calendar-start', 'calendarStart'),
|
||||
$this->settingsOption('calendar-end', 'calendarEnd'),
|
||||
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
|
||||
$this->settingsOption('calendar-default-view', 'calendarDefault'),
|
||||
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
|
||||
$this->settingsOption('calendar-locale', 'calendarLocale'),
|
||||
$this->settingsOption('calendar-limit', 'calendarLimit'),
|
||||
$this->settingsOption('refresh', 'calendarRefresh'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function couchPotatoHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'calendar' => [
|
||||
'enabled' => [
|
||||
'homepageCouchpotatoEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageCouchpotatoAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'couchpotatoURL',
|
||||
'couchpotatoToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function getCouchPotatoCalendar()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->couchPotatoHomepagePermissions('calendar'), true)) {
|
||||
return false;
|
||||
}
|
||||
$calendarItems = array();
|
||||
$list = $this->csvHomepageUrlToken($this->config['couchpotatoURL'], $this->config['couchpotatoToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], 60, $this->config['couchpotatoDisableCertCheck'], $this->config['couchpotatoUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\CouchPotato\CouchPotato($value['url'], $value['token'], null, null, $options);
|
||||
$calendar = $this->formatCouchCalendar($downloader->getMediaList(array('status' => 'active,done')), $key);
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('Radarr')->error($e);
|
||||
}
|
||||
if (!empty($calendar)) {
|
||||
$calendarItems = array_merge($calendarItems, $calendar);
|
||||
}
|
||||
}
|
||||
$this->setAPIResponse('success', null, 200, $calendarItems);
|
||||
return $calendarItems;
|
||||
}
|
||||
|
||||
public function formatCouchCalendar($array, $number)
|
||||
{
|
||||
$api = json_decode($array, true);
|
||||
$gotCalendar = array();
|
||||
$i = 0;
|
||||
foreach ($api['movies'] as $child) {
|
||||
$i++;
|
||||
$movieName = $child['info']['original_title'];
|
||||
$movieID = $child['info']['tmdb_id'];
|
||||
if (!isset($movieID)) {
|
||||
$movieID = "";
|
||||
}
|
||||
$physicalRelease = (isset($child['info']['released']) ? $child['info']['released'] : null);
|
||||
$backupRelease = (isset($child['info']['release_date']['theater']) ? $child['info']['release_date']['theater'] : null);
|
||||
$physicalRelease = (isset($physicalRelease) ? $physicalRelease : $backupRelease);
|
||||
$physicalRelease = strtotime($physicalRelease);
|
||||
$physicalRelease = date("Y-m-d", $physicalRelease);
|
||||
$oldestDay = new DateTime ($this->currentTime);
|
||||
$oldestDay->modify('-' . $this->config['calendarStart'] . ' days');
|
||||
$newestDay = new DateTime ($this->currentTime);
|
||||
$newestDay->modify('+' . $this->config['calendarEnd'] . ' days');
|
||||
$startDt = new DateTime ($physicalRelease);
|
||||
$calendarStartDiff = date_diff($startDt, $newestDay);
|
||||
$calendarEndDiff = date_diff($startDt, $oldestDay);
|
||||
if (!$this->calendarDaysCheck($calendarStartDiff->format('%R') . $calendarStartDiff->days, $calendarEndDiff->format('%R') . $calendarEndDiff->days)) {
|
||||
continue;
|
||||
}
|
||||
if (new DateTime() < $startDt) {
|
||||
$notReleased = "true";
|
||||
} else {
|
||||
$notReleased = "false";
|
||||
}
|
||||
$downloaded = ($child['status'] == "active") ? "0" : "1";
|
||||
if ($downloaded == "0" && $notReleased == "true") {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
if (!empty($child['info']['images']['backdrop_original'])) {
|
||||
$banner = $child['info']['images']['backdrop_original'][0];
|
||||
} elseif (!empty($child['info']['images']['backdrop'])) {
|
||||
$banner = $child['info']['images']['backdrop_original'][0];
|
||||
} else {
|
||||
$banner = "/plugins/images/homepage/no-np.png";
|
||||
}
|
||||
if ($banner !== "/plugins/images/homepage/no-np.png") {
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$imageURL = $banner;
|
||||
$cacheFile = $cacheDirectory . $movieID . '.jpg';
|
||||
$banner = 'data/cache/' . $movieID . '.jpg';
|
||||
if (!file_exists($cacheFile)) {
|
||||
$this->cacheImage($imageURL, $movieID);
|
||||
unset($imageURL);
|
||||
unset($cacheFile);
|
||||
}
|
||||
}
|
||||
$hasFile = (!empty($child['releases']) && !empty($child['releases'][0]['files']['movie']));
|
||||
$details = array(
|
||||
"topTitle" => $movieName,
|
||||
"bottomTitle" => $child['info']['tagline'],
|
||||
"status" => $child['status'],
|
||||
"overview" => $child['info']['plot'],
|
||||
"runtime" => $child['info']['runtime'],
|
||||
"image" => $banner,
|
||||
"ratings" => isset($child['info']['rating']['imdb'][0]) ? $child['info']['rating']['imdb'][0] : '',
|
||||
"videoQuality" => $hasFile ? $child['releases'][0]['quality'] : "unknown",
|
||||
"audioChannels" => "",
|
||||
"audioCodec" => "",
|
||||
"videoCodec" => "",
|
||||
"genres" => $child['info']['genres'],
|
||||
"year" => isset($child['info']['year']) ? $child['info']['year'] : '',
|
||||
"studio" => isset($child['info']['year']) ? $child['info']['year'] : '',
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "CouchPotato-" . $number . "-" . $i,
|
||||
"title" => $movieName,
|
||||
"start" => $physicalRelease,
|
||||
"className" => "inline-popups bg-calendar calendar-item movieID--" . $movieID,
|
||||
"imagetype" => "film " . $downloaded,
|
||||
"imagetypeFilter" => "film",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details
|
||||
));
|
||||
}
|
||||
if ($i != 0) {
|
||||
return $gotCalendar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
trait DelugeHomepageItem
|
||||
{
|
||||
public function delugeSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Deluge',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/deluge.png',
|
||||
'category' => 'Downloader',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'FYI' => [
|
||||
$this->settingsOption('html', null, ['override' => 12, 'html' => '
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<span lang="en">Notice</span>
|
||||
</div>
|
||||
<div class="panel-wrapper collapse in" aria-expanded="true">
|
||||
<div class="panel-body">
|
||||
<ul class="list-icons">
|
||||
<li><i class="fa fa-chevron-right text-danger"></i> <a href="https://github.com/idlesign/deluge-webapi/tree/master/dist" target="_blank">Download Plugin</a></li>
|
||||
<li><i class="fa fa-chevron-right text-danger"></i> Open Deluge Web UI, go to "Preferences -> Plugins -> Install plugin" and choose egg file.</li>
|
||||
<li><i class="fa fa-chevron-right text-danger"></i> Activate WebAPI plugin </li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>']
|
||||
)
|
||||
],
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageDelugeEnabled'),
|
||||
$this->settingsOption('auth', 'homepageDelugeAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'delugeURL'),
|
||||
$this->settingsOption('password', 'delugePassword', ['help' => 'Note that using a blank password might not work correctly.']),
|
||||
$this->settingsOption('disable-cert-check', 'delugeDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'delugeUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('hide-seeding', 'delugeHideSeeding'),
|
||||
$this->settingsOption('hide-completed', 'delugeHideCompleted'),
|
||||
$this->settingsOption('hide-status', 'delugeHideStatus'),
|
||||
$this->settingsOption('combine', 'delugeCombine'),
|
||||
$this->settingsOption('refresh', 'delugeRefresh'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing. Note that using a blank password might not work correctly.']),
|
||||
$this->settingsOption('test', 'deluge'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionDeluge()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->delugeHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$options = $this->requestOptions($this->config['delugeURL'], $this->config['delugeRefresh'], $this->config['delugeDisableCertCheck'], $this->config['delugeUseCustomCertificate'], ['organizr_cert' => $this->getCert(), 'custom_cert' => $this->getCustomCert()]);
|
||||
$deluge = new deluge($this->config['delugeURL'], $this->decrypt($this->config['delugePassword']), $options);
|
||||
$torrents = $deluge->getTorrents(null, 'comment, download_payload_rate, eta, hash, is_finished, is_seed, message, name, paused, progress, queue, state, total_size, upload_payload_rate');
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('Deluge')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function delugeHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageDelugeEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageDelugeAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'delugeURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderdeluge()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->delugeHomepagePermissions('main'))) {
|
||||
$loadingBox = ($this->config['delugeCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['delugeCombine']) ? 'buildDownloaderCombined(\'deluge\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("deluge"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrderdeluge
|
||||
' . $builder . '
|
||||
homepageDownloader("deluge", "' . $this->config['delugeRefresh'] . '");
|
||||
// End homepageOrderdeluge
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getDelugeHomepageQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->delugeHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$options = $this->requestOptions($this->config['delugeURL'], $this->config['delugeRefresh'], $this->config['delugeDisableCertCheck'], $this->config['delugeUseCustomCertificate'], ['organizr_cert' => $this->getCert(), 'custom_cert' => $this->getCustomCert()]);
|
||||
$deluge = new deluge($this->config['delugeURL'], $this->decrypt($this->config['delugePassword']), $options);
|
||||
$torrents = $deluge->getTorrents(null, 'comment, download_payload_rate, eta, hash, is_finished, is_seed, message, name, paused, progress, queue, state, total_size, upload_payload_rate, tracker_status');
|
||||
foreach ($torrents as $key => $value) {
|
||||
$tempStatus = $this->delugeStatus($value->queue, $value->state, $value->progress);
|
||||
if ($tempStatus == 'Seeding' && $this->config['delugeHideSeeding']) {
|
||||
//do nothing
|
||||
} elseif ($tempStatus == 'Finished' && $this->config['delugeHideCompleted']) {
|
||||
//do nothing
|
||||
} else {
|
||||
if ($this->config['delugeHideStatus']) {
|
||||
$value->tracker_status = "";
|
||||
}
|
||||
$api['content']['queueItems'][] = $value;
|
||||
}
|
||||
}
|
||||
$api['content']['queueItems'] = (empty($api['content']['queueItems'])) ? [] : $api['content']['queueItems'];
|
||||
$api['content']['historyItems'] = false;
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('Deluge')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function delugeStatus($queued, $status, $state)
|
||||
{
|
||||
if ($queued == '-1' && $state == '100' && ($status == 'Seeding' || $status == 'Queued' || $status == 'Paused')) {
|
||||
$state = 'Seeding';
|
||||
} elseif ($state !== '100') {
|
||||
$state = 'Downloading';
|
||||
} else {
|
||||
$state = 'Finished';
|
||||
}
|
||||
return ($state) ? $state : $status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
trait DonateHomepageItem
|
||||
{
|
||||
public function donateSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Donate',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/donate.png',
|
||||
'category' => 'Requests',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
|
||||
'settings' => [
|
||||
'About' => [
|
||||
$this->settingsOption('about', 'Donations', ['about' => 'This item allows you to use Stripe to accept donations on the homepage']),
|
||||
],
|
||||
'Setup' => [
|
||||
$this->settingsOption('html', null, ['label' => 'Instructions', 'override' => 12, 'html' => '
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<a href="https://dashboard.stripe.com//" target="_blank"><span class="label label-info m-l-5">Visit Stripe Site</span></a>
|
||||
</div>
|
||||
<div class="panel-wrapper collapse in">
|
||||
<div class="panel-body">
|
||||
<ul class="list-icons">
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Create or Login if you already have an account</li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Goto products and click [Add Product]</li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Name the product anything you like</li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Make sure the product has standard pricing</li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Also make sure that the product is set to <code>One Time</code></li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Set the pricing to the minimum price i.e. 1 USD</li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Click <code>Save Product</code></li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Click The Product you just created</li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Copy <code>ID</code> value</li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Click <code>Developers</code></li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Click <code>API Keys</code></li>
|
||||
<li lang="en"><i class="fa fa-caret-right text-info"></i> Copy both <code>Publishable key</code> and <code>Secret key</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
']
|
||||
),
|
||||
],
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageDonateEnabled'),
|
||||
$this->settingsOption('auth', 'homepageDonateAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('input', 'homepageDonatePublicToken', ['label' => 'Public Token']),
|
||||
$this->settingsOption('token', 'homepageDonateSecretToken', ['label' => 'Secret Token']),
|
||||
$this->settingsOption('input', 'homepageDonateProductID', ['label' => 'Product ID']),
|
||||
|
||||
],
|
||||
'Customize' => [
|
||||
$this->settingsOption('input', 'homepageDonateCustomizeHeading', ['label' => 'Heading']),
|
||||
$this->settingsOption('code-editor', 'homepageDonateCustomizeDescription', ['label' => 'Description', 'mode' => 'html']),
|
||||
$this->settingsOption('select', 'homepageDonateMinimum',
|
||||
['label' => 'Minimum',
|
||||
'options' => [
|
||||
['name' => '1 USD', 'value' => '100'],
|
||||
['name' => '2 USD', 'value' => '200'],
|
||||
['name' => '3 USD', 'value' => '300'],
|
||||
['name' => '4 USD', 'value' => '400'],
|
||||
['name' => '5 USD', 'value' => '500'],
|
||||
['name' => '10 USD', 'value' => '1000'],
|
||||
['name' => '20 USD', 'value' => '2000'],
|
||||
['name' => '25 USD', 'value' => '2500'],
|
||||
['name' => '50 USD', 'value' => '5000'],
|
||||
['name' => '75 USD', 'value' => '7500'],
|
||||
['name' => '100 USD', 'value' => '10000'],
|
||||
]
|
||||
]),
|
||||
$this->settingsOption('switch', 'homepageDonateShowUserHistory', ['label' => 'Show User Donate History']),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
|
||||
public function donateHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageDonateEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageDonateAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'homepageDonateMinimum',
|
||||
'homepageDonatePublicToken',
|
||||
'homepageDonateSecretToken',
|
||||
'homepageDonateProductID',
|
||||
]
|
||||
],
|
||||
'history' => [
|
||||
'enabled' => [
|
||||
'homepageDonateEnabled',
|
||||
'homepageDonateShowUserHistory'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageDonateAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'homepageDonateMinimum',
|
||||
'homepageDonatePublicToken',
|
||||
'homepageDonateSecretToken',
|
||||
'homepageDonateProductID',
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageDonateUserHistory()
|
||||
{
|
||||
$items = [];
|
||||
if ($this->homepageItemPermissions($this->donateHomepagePermissions('history'))) {
|
||||
try {
|
||||
$stripe = new \Stripe\StripeClient(
|
||||
trim($this->config['homepageDonateSecretToken'])
|
||||
);
|
||||
$history = $stripe->charges->all(['limit' => 100]);
|
||||
if (count($history) > 0) {
|
||||
if ($this->user['email']) {
|
||||
foreach ($history as $charge) {
|
||||
if (($this->qualifyRequest(0) || (strtolower($charge['billing_details']['email']) == strtolower($this->user['email']))) && $charge['amount_captured'] > 0) {
|
||||
$items[] = [
|
||||
'date' => date('Y-m-d\TH:i:s\Z', $charge['created']),
|
||||
'email' => $charge['billing_details']['email'],
|
||||
'amount' => $charge['amount_captured'] / 100
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||
die($this->showHTML('Error', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
$this->setResponse(200, null, $items);
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function homepageDonateCreateSession($amount = null)
|
||||
{
|
||||
$amount = $amount ? $amount * 100 : $this->config['homepageDonateMinimum'];
|
||||
if ($this->config['homepageDonatePublicToken'] == '' || $this->config['homepageDonateSecretToken'] == '' || $this->config['homepageDonateProductID'] == '') {
|
||||
$this->setResponse(409, 'Donation Tokens are not setup');
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$stripe = new \Stripe\StripeClient(
|
||||
trim($this->config['homepageDonateSecretToken'])
|
||||
);
|
||||
$sessionInfo = [
|
||||
'payment_method_types' => ['card'],
|
||||
'line_items' => [[
|
||||
'price_data' => [
|
||||
'product' => $this->config['homepageDonateProductID'],
|
||||
'unit_amount' => $amount,
|
||||
'currency' => 'usd',
|
||||
],
|
||||
'quantity' => 1,
|
||||
]],
|
||||
'mode' => 'payment',
|
||||
'success_url' => $this->getServerPath() . 'api/v2/homepage/donate/success',
|
||||
'cancel_url' => $this->getServerPath() . 'api/v2/homepage/donate/cancel',
|
||||
];
|
||||
if ($this->user['email'] && stripos($this->user['email'], 'placeholder') == false) {
|
||||
$sessionInfo = array_merge($sessionInfo, ['customer_email' => $this->user['email']]);
|
||||
}
|
||||
$session = $stripe->checkout->sessions->create($sessionInfo);
|
||||
header('HTTP/1.1 303 See Other');
|
||||
header('Location: ' . $session->url);
|
||||
} catch (\Stripe\Exception\ApiErrorException $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function homepageOrderDonate()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->donateHomepagePermissions('main'))) {
|
||||
$minimum = $this->config['homepageDonateMinimum'] / 100;
|
||||
$history = $this->config['homepageDonateShowUserHistory'] ? '<div class="pull-right"><a href="javascript:void(0)" class="toggle-donation-history" data-status="hidden"><i class="fa fa-clock-o"></i></a> </div>' : '';
|
||||
return '
|
||||
<script>
|
||||
$(document).on("keyup", "#custom-donation-amount", function () {
|
||||
$("#homepage-donation-form").attr("action", "api/v2/homepage/donate?amount=" + $(this).val());
|
||||
});
|
||||
</script>
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="panel panel-primary" style="position: static; zoom: 1;">
|
||||
<div class="panel-heading"> ' . $this->config['homepageDonateCustomizeHeading'] . $history . '</div>
|
||||
<div class="panel-wrapper collapse in" aria-expanded="true">
|
||||
<div class="panel-body">
|
||||
<p>' . $this->config['homepageDonateCustomizeDescription'] . '</p>
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?version=3.52.1&features=fetch"></script>
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<form id="homepage-donation-form" action="api/v2/homepage/donate?amount=' . $minimum . '" method="POST" target="_blank">
|
||||
<div class="input-group m-b-30">
|
||||
<span class="input-group-addon">$</span>
|
||||
<input type="number" class="form-control" name="amount" id="custom-donation-amount" placeholder="' . $minimum . '" min="' . $minimum . '"/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-info" type="submit" id="checkout-button" lang="en">Donate</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<div class="donation-history hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
}
|
||||
523
docker/test/tvtracker/config/www/organizr/api/homepage/emby.php
Normal file
523
docker/test/tvtracker/config/www/organizr/api/homepage/emby.php
Normal file
@@ -0,0 +1,523 @@
|
||||
<?php
|
||||
|
||||
trait EmbyHomepageItem
|
||||
{
|
||||
public function embySettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Emby',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/emby.png',
|
||||
'category' => 'Media Server',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageEmbyEnabled'),
|
||||
$this->settingsOption('auth', 'homepageEmbyAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'embyURL'),
|
||||
$this->settingsOption('token', 'embyToken'),
|
||||
$this->settingsOption('disable-cert-check', 'embyDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'embyUseCustomCertificate'),
|
||||
],
|
||||
'Active Streams' => [
|
||||
$this->settingsOption('enable', 'homepageEmbyStreams'),
|
||||
$this->settingsOption('auth', 'homepageEmbyStreamsAuth'),
|
||||
$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information']),
|
||||
$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
|
||||
$this->settingsOption('refresh', 'homepageStreamRefresh'),
|
||||
],
|
||||
'Recent Items' => [
|
||||
$this->settingsOption('enable', 'homepageEmbyRecent'),
|
||||
$this->settingsOption('auth', 'homepageEmbyRecentAuth'),
|
||||
$this->settingsOption('limit', 'homepageRecentLimit'),
|
||||
$this->settingsOption('refresh', 'homepageRecentRefresh'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('input', 'homepageEmbyLink', ['label' => 'Emby Homepage Link URL', 'help' => 'Available variables: {id} {serverId}']),
|
||||
$this->settingsOption('input', 'embyTabName', ['label' => 'Emby Tab Name', 'placeholder' => 'Only use if you have Emby in a reverse proxy']),
|
||||
$this->settingsOption('input', 'embyTabURL', ['label' => 'Emby Tab WAN URL', 'placeholder' => 'Only use if you have Emby in a reverse proxy']),
|
||||
$this->settingsOption('image-cache-quality', 'cacheImageSize'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'emby'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionEmby()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->embyHomepagePermissions('test'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['embyURL']);
|
||||
$url = $url . "/Users?api_key=" . $this->config['embyToken'];
|
||||
$options = $this->requestOptions($url, null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Emby Connection Error', 500);
|
||||
return true;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function embyHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'test' => [
|
||||
'enabled' => [
|
||||
'homepageEmbyEnabled',
|
||||
],
|
||||
'auth' => [
|
||||
'homepageEmbyAuth',
|
||||
],
|
||||
'not_empty' => [
|
||||
'embyURL',
|
||||
'embyToken'
|
||||
]
|
||||
],
|
||||
'streams' => [
|
||||
'enabled' => [
|
||||
'homepageEmbyEnabled',
|
||||
'homepageEmbyStreams'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageEmbyAuth',
|
||||
'homepageEmbyStreamsAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'embyURL',
|
||||
'embyToken'
|
||||
]
|
||||
],
|
||||
'recent' => [
|
||||
'enabled' => [
|
||||
'homepageEmbyEnabled',
|
||||
'homepageEmbyRecent'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageEmbyAuth',
|
||||
'homepageEmbyRecentAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'embyURL',
|
||||
'embyToken'
|
||||
]
|
||||
],
|
||||
'metadata' => [
|
||||
'enabled' => [
|
||||
'homepageEmbyEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageEmbyAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'embyURL',
|
||||
'embyToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderembynowplaying()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->embyHomepagePermissions('streams'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Now Playing...</h2></div>
|
||||
<script>
|
||||
// Emby Stream
|
||||
homepageStream("emby", "' . $this->config['homepageStreamRefresh'] . '");
|
||||
// End Emby Stream
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function homepageOrderembyrecent()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->embyHomepagePermissions('recent'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Recent...</h2></div>
|
||||
<script>
|
||||
// Emby Recent
|
||||
homepageRecent("emby", "' . $this->config['homepageRecentRefresh'] . '");
|
||||
// End Emby Recent
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getEmbyHomepageStreams()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->embyHomepagePermissions('streams'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['embyURL']);
|
||||
$url = $url . '/Sessions?api_key=' . $this->config['embyToken'] . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
|
||||
$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$emby = json_decode($response->body, true);
|
||||
foreach ($emby as $child) {
|
||||
if (isset($child['NowPlayingItem']) || isset($child['Name'])) {
|
||||
$items[] = $this->resolveEmbyItem($child);
|
||||
}
|
||||
}
|
||||
$api['content'] = array_filter($items);
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Emby')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getEmbyHomepageRecent()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->embyHomepagePermissions('recent'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['embyURL']);
|
||||
$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
|
||||
$username = false;
|
||||
$showPlayed = false;
|
||||
$userId = 0;
|
||||
try {
|
||||
|
||||
if (isset($this->user['username'])) {
|
||||
$username = strtolower($this->user['username']);
|
||||
}
|
||||
// Get A User
|
||||
$userIds = $url . "/Users?api_key=" . $this->config['embyToken'];
|
||||
$response = Requests::get($userIds, [], $options);
|
||||
if ($response->success) {
|
||||
$emby = json_decode($response->body, true);
|
||||
foreach ($emby as $value) { // Scan for admin user
|
||||
if (isset($value['Policy']) && isset($value['Policy']['IsAdministrator']) && $value['Policy']['IsAdministrator']) {
|
||||
$userId = $value['Id'];
|
||||
}
|
||||
if ($username && strtolower($value['Name']) == $username) {
|
||||
$userId = $value['Id'];
|
||||
$showPlayed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$url = $url . '/Users/' . $userId . '/Items/Latest?EnableImages=true&Limit=' . $this->config['homepageRecentLimit'] . '&api_key=' . $this->config['embyToken'] . ($showPlayed ? '' : '&IsPlayed=false') . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines&IncludeItemTypes=Series,Episode,MusicAlbum,Audio,Movie,Video';
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$emby = json_decode($response->body, true);
|
||||
foreach ($emby as $child) {
|
||||
if (isset($child['NowPlayingItem']) || isset($child['Name'])) {
|
||||
$items[] = $this->resolveEmbyItem($child);
|
||||
}
|
||||
}
|
||||
$api['content'] = array_filter($items);
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Emby')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getEmbyHomepageMetadata($array)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->embyHomepagePermissions('metadata'), true)) {
|
||||
return false;
|
||||
}
|
||||
$key = $array['key'] ?? null;
|
||||
if (!$key) {
|
||||
$this->setAPIResponse('error', 'Emby Metadata key is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['embyURL']);
|
||||
$options = $this->requestOptions($url, 60, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
|
||||
$username = false;
|
||||
$showPlayed = false;
|
||||
$userId = 0;
|
||||
try {
|
||||
if (isset($this->user['username'])) {
|
||||
$username = strtolower($this->user['username']);
|
||||
}
|
||||
// Get A User
|
||||
$userIds = $url . "/Users?api_key=" . $this->config['embyToken'];
|
||||
$response = Requests::get($userIds, [], $options);
|
||||
if ($response->success) {
|
||||
$emby = json_decode($response->body, true);
|
||||
foreach ($emby as $value) { // Scan for admin user
|
||||
if (isset($value['Policy']) && isset($value['Policy']['IsAdministrator']) && $value['Policy']['IsAdministrator']) {
|
||||
$userId = $value['Id'];
|
||||
}
|
||||
if ($username && strtolower($value['Name']) == $username) {
|
||||
$userId = $value['Id'];
|
||||
$showPlayed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$url = $url . '/Users/' . $userId . '/Items/' . $key . '?EnableImages=true&Limit=' . $this->config['homepageRecentLimit'] . '&api_key=' . $this->config['embyToken'] . ($showPlayed ? '' : '&IsPlayed=false') . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$emby = json_decode($response->body, true);
|
||||
if (isset($emby['NowPlayingItem']) || isset($emby['Name'])) {
|
||||
$items[] = $this->resolveEmbyItem($emby);
|
||||
}
|
||||
$api['content'] = array_filter($items);
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Emby Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Emby')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function resolveEmbyItem($itemDetails)
|
||||
{
|
||||
$item = isset($itemDetails['NowPlayingItem']['Id']) ? $itemDetails['NowPlayingItem'] : $itemDetails;
|
||||
// Static Height & Width
|
||||
$height = $this->getCacheImageSize('h');
|
||||
$width = $this->getCacheImageSize('w');
|
||||
$nowPlayingHeight = $this->getCacheImageSize('nph');
|
||||
$nowPlayingWidth = $this->getCacheImageSize('npw');
|
||||
$actorHeight = 450;
|
||||
$actorWidth = 300;
|
||||
// Cache Directories
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$cacheDirectoryWeb = 'data/cache/';
|
||||
// Types
|
||||
//$embyItem['array-item'] = $item;
|
||||
//$embyItem['array-itemdetails'] = $itemDetails;
|
||||
switch (@$item['Type']) {
|
||||
case 'Series':
|
||||
$embyItem['type'] = 'tv';
|
||||
$embyItem['title'] = $item['Name'];
|
||||
$embyItem['secondaryTitle'] = '';
|
||||
$embyItem['summary'] = '';
|
||||
$embyItem['ratingKey'] = $item['Id'];
|
||||
$embyItem['thumb'] = $item['Id'];
|
||||
$embyItem['key'] = $item['Id'] . "-list";
|
||||
$embyItem['nowPlayingThumb'] = $item['Id'];
|
||||
$embyItem['nowPlayingKey'] = $item['Id'] . "-np";
|
||||
$embyItem['metadataKey'] = $item['Id'];
|
||||
$embyItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? 'Thumb' : (isset($item['BackdropImageTags'][0]) ? 'Backdrop' : '');
|
||||
break;
|
||||
case 'Episode':
|
||||
$embyItem['type'] = 'tv';
|
||||
$embyItem['title'] = $item['SeriesName'];
|
||||
$embyItem['secondaryTitle'] = '';
|
||||
$embyItem['summary'] = '';
|
||||
$embyItem['ratingKey'] = $item['Id'];
|
||||
$embyItem['thumb'] = (isset($item['SeriesId']) ? $item['SeriesId'] : $item['Id']);
|
||||
$embyItem['key'] = (isset($item['SeriesId']) ? $item['SeriesId'] : $item['Id']) . "-list";
|
||||
$embyItem['nowPlayingThumb'] = isset($item['ParentThumbItemId']) ? $item['ParentThumbItemId'] : (isset($item['ParentBackdropItemId']) ? $item['ParentBackdropItemId'] : false);
|
||||
$embyItem['nowPlayingKey'] = isset($item['ParentThumbItemId']) ? $item['ParentThumbItemId'] . '-np' : (isset($item['ParentBackdropItemId']) ? $item['ParentBackdropItemId'] . '-np' : false);
|
||||
$embyItem['metadataKey'] = $item['Id'];
|
||||
$embyItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? 'Thumb' : (isset($item['ParentBackdropImageTags'][0]) ? 'Backdrop' : '');
|
||||
$embyItem['nowPlayingTitle'] = @$item['SeriesName'] . ' - ' . @$item['Name'];
|
||||
$embyItem['nowPlayingBottom'] = 'S' . @$item['ParentIndexNumber'] . ' · E' . @$item['IndexNumber'];
|
||||
break;
|
||||
case 'MusicAlbum':
|
||||
case 'Audio':
|
||||
$embyItem['type'] = 'music';
|
||||
$embyItem['title'] = $item['Name'];
|
||||
$embyItem['secondaryTitle'] = '';
|
||||
$embyItem['summary'] = '';
|
||||
$embyItem['ratingKey'] = $item['Id'];
|
||||
$embyItem['thumb'] = $item['Id'];
|
||||
$embyItem['key'] = $item['Id'] . "-list";
|
||||
$embyItem['nowPlayingThumb'] = (isset($item['AlbumId']) ? $item['AlbumId'] : @$item['ParentBackdropItemId']);
|
||||
$embyItem['nowPlayingKey'] = $item['Id'] . "-np";
|
||||
$embyItem['metadataKey'] = isset($item['AlbumId']) ? $item['AlbumId'] : $item['Id'];
|
||||
$embyItem['nowPlayingImageType'] = (isset($item['ParentBackdropItemId']) ? "Primary" : "Backdrop");
|
||||
$embyItem['nowPlayingTitle'] = @$item['AlbumArtist'] . ' - ' . @$item['Name'];
|
||||
$embyItem['nowPlayingBottom'] = @$item['Album'];
|
||||
break;
|
||||
case 'Movie':
|
||||
$embyItem['type'] = 'movie';
|
||||
$embyItem['title'] = $item['Name'];
|
||||
$embyItem['secondaryTitle'] = '';
|
||||
$embyItem['summary'] = '';
|
||||
$embyItem['ratingKey'] = $item['Id'];
|
||||
$embyItem['thumb'] = $item['Id'];
|
||||
$embyItem['key'] = $item['Id'] . "-list";
|
||||
$embyItem['nowPlayingThumb'] = $item['Id'];
|
||||
$embyItem['nowPlayingKey'] = $item['Id'] . "-np";
|
||||
$embyItem['metadataKey'] = $item['Id'];
|
||||
$embyItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? "Thumb" : (isset($item['BackdropImageTags']) ? "Backdrop" : false);
|
||||
$embyItem['nowPlayingTitle'] = @$item['Name'];
|
||||
$embyItem['nowPlayingBottom'] = @$item['ProductionYear'];
|
||||
break;
|
||||
case 'Video':
|
||||
$embyItem['type'] = 'video';
|
||||
$embyItem['title'] = $item['Name'];
|
||||
$embyItem['secondaryTitle'] = '';
|
||||
$embyItem['summary'] = '';
|
||||
$embyItem['ratingKey'] = $item['Id'];
|
||||
$embyItem['thumb'] = $item['Id'];
|
||||
$embyItem['key'] = $item['Id'] . "-list";
|
||||
$embyItem['nowPlayingThumb'] = $item['Id'];
|
||||
$embyItem['nowPlayingKey'] = $item['Id'] . "-np";
|
||||
$embyItem['metadataKey'] = $item['Id'];
|
||||
$embyItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? "Thumb" : (isset($item['BackdropImageTags']) ? "Backdrop" : false);
|
||||
$embyItem['nowPlayingTitle'] = @$item['Name'];
|
||||
$embyItem['nowPlayingBottom'] = @$item['ProductionYear'];
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
$embyItem['uid'] = $item['Id'];
|
||||
$embyItem['imageType'] = (isset($item['ImageTags']['Primary']) ? "Primary" : false);
|
||||
$embyItem['elapsed'] = isset($itemDetails['PlayState']['PositionTicks']) && $itemDetails['PlayState']['PositionTicks'] !== '0' ? (int)$itemDetails['PlayState']['PositionTicks'] : null;
|
||||
$embyItem['duration'] = isset($itemDetails['NowPlayingItem']['RunTimeTicks']) ? (int)$itemDetails['NowPlayingItem']['RunTimeTicks'] : (int)(isset($item['RunTimeTicks']) ? $item['RunTimeTicks'] : '');
|
||||
$embyItem['watched'] = ($embyItem['elapsed'] && $embyItem['duration'] ? floor(($embyItem['elapsed'] / $embyItem['duration']) * 100) : 0);
|
||||
$embyItem['transcoded'] = isset($itemDetails['TranscodingInfo']['CompletionPercentage']) ? floor((int)$itemDetails['TranscodingInfo']['CompletionPercentage']) : 100;
|
||||
$embyItem['stream'] = @$itemDetails['PlayState']['PlayMethod'];
|
||||
$embyItem['id'] = $item['ServerId'];
|
||||
$embyItem['session'] = @$itemDetails['DeviceId'];
|
||||
$embyItem['bandwidth'] = isset($itemDetails['TranscodingInfo']['Bitrate']) ? $itemDetails['TranscodingInfo']['Bitrate'] / 1000 : '';
|
||||
$embyItem['bandwidthType'] = 'wan';
|
||||
$embyItem['sessionType'] = (@$itemDetails['PlayState']['PlayMethod'] == 'Transcode') ? 'Transcoding' : 'Direct Playing';
|
||||
$embyItem['state'] = ((@(string)$itemDetails['PlayState']['IsPaused'] == '1') ? "pause" : "play");
|
||||
$embyItem['user'] = ($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) ? @(string)$itemDetails['UserName'] : "";
|
||||
$embyItem['userThumb'] = '';
|
||||
$embyItem['userAddress'] = (isset($itemDetails['RemoteEndPoint']) ? $itemDetails['RemoteEndPoint'] : "x.x.x.x");
|
||||
$embyVariablesForLink = [
|
||||
'{id}' => $embyItem['uid'],
|
||||
'{serverId}' => $embyItem['id']
|
||||
];
|
||||
$embyItem['address'] = $this->userDefinedIdReplacementLink($this->config['homepageEmbyLink'], $embyVariablesForLink);
|
||||
$embyItem['nowPlayingOriginalImage'] = 'api/v2/homepage/image?source=emby&type=' . $embyItem['nowPlayingImageType'] . '&img=' . $embyItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $embyItem['nowPlayingKey'] . '$' . $this->randString();
|
||||
$embyItem['originalImage'] = 'api/v2/homepage/image?source=emby&type=' . $embyItem['imageType'] . '&img=' . $embyItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $embyItem['key'] . '$' . $this->randString();
|
||||
$embyItem['openTab'] = $this->config['embyTabURL'] && $this->config['embyTabName'] ? true : false;
|
||||
$embyItem['tabName'] = $this->config['embyTabName'] ? $this->config['embyTabName'] : '';
|
||||
// Stream info
|
||||
$embyItem['userStream'] = array(
|
||||
'platform' => @(string)$itemDetails['Client'],
|
||||
'product' => @(string)$itemDetails['Client'],
|
||||
'device' => @(string)$itemDetails['DeviceName'],
|
||||
'stream' => @$itemDetails['PlayState']['PlayMethod'],
|
||||
'videoResolution' => isset($itemDetails['NowPlayingItem']['MediaStreams'][0]['Width']) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Width'] : '',
|
||||
'throttled' => false,
|
||||
'sourceVideoCodec' => isset($itemDetails['NowPlayingItem']['MediaStreams'][0]) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Codec'] : '',
|
||||
'videoCodec' => @$itemDetails['TranscodingInfo']['VideoCodec'],
|
||||
'audioCodec' => @$itemDetails['TranscodingInfo']['AudioCodec'],
|
||||
'sourceAudioCodec' => isset($itemDetails['NowPlayingItem']['MediaStreams'][1]) ? $itemDetails['NowPlayingItem']['MediaStreams'][1]['Codec'] : (isset($itemDetails['NowPlayingItem']['MediaStreams'][0]) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Codec'] : ''),
|
||||
'videoDecision' => $this->streamType(@$itemDetails['PlayState']['PlayMethod']),
|
||||
'audioDecision' => $this->streamType(@$itemDetails['PlayState']['PlayMethod']),
|
||||
'container' => isset($itemDetails['NowPlayingItem']['Container']) ? $itemDetails['NowPlayingItem']['Container'] : '',
|
||||
'audioChannels' => @$itemDetails['TranscodingInfo']['AudioChannels']
|
||||
);
|
||||
// Genre catch all
|
||||
if (isset($item['Genres'])) {
|
||||
$genres = array();
|
||||
foreach ($item['Genres'] as $genre) {
|
||||
$genres[] = $genre;
|
||||
}
|
||||
}
|
||||
// Actor catch all
|
||||
if (isset($item['People'])) {
|
||||
$actors = array();
|
||||
foreach ($item['People'] as $key => $value) {
|
||||
if (@$value['PrimaryImageTag'] && @$value['Role']) {
|
||||
if (file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg')) {
|
||||
$actorImage = $cacheDirectoryWeb . (string)$value['Id'] . '-cast.jpg';
|
||||
}
|
||||
if (file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg') && (time() - 604800) > filemtime($cacheDirectory . (string)$value['Id'] . '-cast.jpg') || !file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg')) {
|
||||
$actorImage = 'api/v2/homepage/image?source=emby&type=Primary&img=' . (string)$value['Id'] . '&height=' . $actorHeight . '&width=' . $actorWidth . '&key=' . (string)$value['Id'] . '-cast';
|
||||
}
|
||||
$actors[] = array(
|
||||
'name' => (string)$value['Name'],
|
||||
'role' => (string)$value['Role'],
|
||||
'thumb' => $actorImage
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Metadata information
|
||||
$embyItem['metadata'] = array(
|
||||
'guid' => $item['Id'],
|
||||
'summary' => @(string)$item['Overview'],
|
||||
'rating' => @(string)$item['CommunityRating'],
|
||||
'duration' => @(string)$item['RunTimeTicks'],
|
||||
'originallyAvailableAt' => @(string)$item['PremiereDate'],
|
||||
'year' => (string)isset($item['ProductionYear']) ? $item['ProductionYear'] : '',
|
||||
//'studio' => (string)$item['studio'],
|
||||
'tagline' => @(string)$item['Taglines'][0],
|
||||
'genres' => (isset($item['Genres'])) ? $genres : '',
|
||||
'actors' => (isset($item['People'])) ? $actors : ''
|
||||
);
|
||||
if (file_exists($cacheDirectory . $embyItem['nowPlayingKey'] . '.jpg')) {
|
||||
$embyItem['nowPlayingImageURL'] = $cacheDirectoryWeb . $embyItem['nowPlayingKey'] . '.jpg';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $embyItem['key'] . '.jpg')) {
|
||||
$embyItem['imageURL'] = $cacheDirectoryWeb . $embyItem['key'] . '.jpg';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $embyItem['nowPlayingKey'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $embyItem['nowPlayingKey'] . '.jpg') || !file_exists($cacheDirectory . $embyItem['nowPlayingKey'] . '.jpg')) {
|
||||
$embyItem['nowPlayingImageURL'] = 'api/v2/homepage/image?source=emby&type=' . $embyItem['nowPlayingImageType'] . '&img=' . $embyItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $embyItem['nowPlayingKey'] . '';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $embyItem['key'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $embyItem['key'] . '.jpg') || !file_exists($cacheDirectory . $embyItem['key'] . '.jpg')) {
|
||||
$embyItem['imageURL'] = 'api/v2/homepage/image?source=emby&type=' . $embyItem['imageType'] . '&img=' . $embyItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $embyItem['key'] . '';
|
||||
}
|
||||
if (!$embyItem['nowPlayingThumb']) {
|
||||
$embyItem['nowPlayingOriginalImage'] = $embyItem['nowPlayingImageURL'] = "plugins/images/homepage/no-np.png";
|
||||
$embyItem['nowPlayingKey'] = "no-np";
|
||||
}
|
||||
if (!$embyItem['thumb']) {
|
||||
$embyItem['originalImage'] = $embyItem['imageURL'] = "plugins/images/homepage/no-list.png";
|
||||
$embyItem['key'] = "no-list";
|
||||
}
|
||||
if (isset($useImage)) {
|
||||
$embyItem['useImage'] = $useImage;
|
||||
}
|
||||
return $embyItem;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,801 @@
|
||||
<?php
|
||||
|
||||
trait EmbyLiveTVTrackerHomepageItem
|
||||
{
|
||||
public function embyLiveTVTrackerSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'EmbyLiveTVTracker',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/homepage/embyLiveTVTracker.png',
|
||||
'category' => 'Media Server',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageEmbyLiveTVTrackerEnabled'),
|
||||
$this->settingsOption('auth', 'homepageEmbyLiveTVTrackerAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'embyURL'),
|
||||
$this->settingsOption('token', 'embyToken'),
|
||||
$this->settingsOption('disable-cert-check', 'embyDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'embyUseCustomCertificate'),
|
||||
],
|
||||
'Display Options' => [
|
||||
$this->settingsOption('number', 'homepageEmbyLiveTVTrackerRefresh', ['label' => 'Auto-refresh Interval (minutes)', 'min' => 1, 'max' => 60]),
|
||||
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerCompactView', ['label' => 'Use Compact View']),
|
||||
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowDuration', ['label' => 'Show Recording Duration']),
|
||||
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowSeriesInfo', ['label' => 'Show Series Information']),
|
||||
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowUserInfo', ['label' => 'Show User Information']),
|
||||
$this->settingsOption('number', 'homepageEmbyLiveTVTrackerMaxItems', ['label' => 'Maximum Scheduled Items', 'min' => 5, 'max' => 50]),
|
||||
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerShowCompleted', ['label' => 'Show Completed Recordings']),
|
||||
$this->settingsOption('number', 'homepageEmbyLiveTVTrackerDaysShown', ['label' => 'Days of Completed Recordings', 'min' => 1, 'max' => 30]),
|
||||
$this->settingsOption('number', 'homepageEmbyLiveTVTrackerMaxCompletedItems', ['label' => 'Maximum Completed Items', 'min' => 5, 'max' => 50]),
|
||||
$this->settingsOption('switch', 'homepageEmbyLiveTVTrackerDebug', ['label' => 'Enable Debug Logging']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'embyLiveTVTracker'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionEmbyLiveTVTracker()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('test'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['embyURL']);
|
||||
$url = $url . "/emby/System/Info?api_key=" . $this->config['embyToken'];
|
||||
$options = $this->requestOptions($url, null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$info = json_decode($response->body, true);
|
||||
if (isset($info['ServerName'])) {
|
||||
// Test LiveTV functionality
|
||||
$liveTvUrl = $this->qualifyURL($this->config['embyURL']) . '/emby/LiveTv/Info?api_key=' . $this->config['embyToken'];
|
||||
try {
|
||||
$liveTvResponse = Requests::get($liveTvUrl, [], $options);
|
||||
$liveTvInfo = json_decode($liveTvResponse->body, true);
|
||||
$hasLiveTV = isset($liveTvInfo['Services']) && count($liveTvInfo['Services']) > 0;
|
||||
$message = 'Successfully connected to ' . $info['ServerName'];
|
||||
if ($hasLiveTV) {
|
||||
$message .= ' with LiveTV support enabled';
|
||||
} else {
|
||||
$message .= ' (Warning: LiveTV may not be configured)';
|
||||
}
|
||||
$this->setAPIResponse('success', $message, 200);
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('success', 'Connected to ' . $info['ServerName'] . ' but LiveTV status unknown', 200);
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Invalid response from Emby server', 500);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Emby Connection Error', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function embyLiveTVTrackerHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'test' => [
|
||||
'enabled' => [
|
||||
'homepageEmbyLiveTVTrackerEnabled',
|
||||
],
|
||||
'auth' => [
|
||||
'homepageEmbyLiveTVTrackerAuth',
|
||||
],
|
||||
'not_empty' => [
|
||||
'embyURL',
|
||||
'embyToken'
|
||||
]
|
||||
],
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageEmbyLiveTVTrackerEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageEmbyLiveTVTrackerAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'embyURL',
|
||||
'embyToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderEmbyLiveTVTracker()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'))) {
|
||||
$refreshInterval = ($this->config['homepageEmbyLiveTVTrackerRefresh'] ?? 5) * 60000; // Convert minutes to milliseconds
|
||||
$compactView = ($this->config['homepageEmbyLiveTVTrackerCompactView'] ?? false) ? 'true' : 'false';
|
||||
$showDuration = ($this->config['homepageEmbyLiveTVTrackerShowDuration'] ?? true) ? 'true' : 'false';
|
||||
$showSeriesInfo = ($this->config['homepageEmbyLiveTVTrackerShowSeriesInfo'] ?? true) ? 'true' : 'false';
|
||||
$showUserInfo = ($this->config['homepageEmbyLiveTVTrackerShowUserInfo'] ?? false) ? 'true' : 'false';
|
||||
$maxItems = $this->config['homepageEmbyLiveTVTrackerMaxItems'] ?? 10;
|
||||
$showCompleted = ($this->config['homepageEmbyLiveTVTrackerShowCompleted'] ?? true) ? 'true' : 'false';
|
||||
$daysShown = $this->config['homepageEmbyLiveTVTrackerDaysShown'] ?? 7;
|
||||
$maxCompletedItems = $this->config['homepageEmbyLiveTVTrackerMaxCompletedItems'] ?? 5;
|
||||
|
||||
$panelClass = ($compactView === 'true') ? 'panel-compact' : '';
|
||||
$statsClass = ($compactView === 'true') ? 'col-sm-6' : 'col-sm-3';
|
||||
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box ' . $panelClass . '">
|
||||
<div class="white-box-header">
|
||||
<i class="fa fa-tv"></i> Emby LiveTV Tracker
|
||||
<span class="pull-right">
|
||||
<small id="embylivetv-last-update" class="text-muted"></small>
|
||||
<button class="btn btn-xs btn-primary" onclick="refreshEmbyLiveTVData()" title="Refresh Data">
|
||||
<i class="fa fa-refresh" id="embylivetv-refresh-icon"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="white-box-content">
|
||||
<div class="row" id="embylivetv-stats">
|
||||
<div class="' . $statsClass . '">
|
||||
<div class="text-center">
|
||||
<h3 id="embylivetv-active-timers" class="text-success">-</h3>
|
||||
<small>Active Timers</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="' . $statsClass . '">
|
||||
<div class="text-center">
|
||||
<h3 id="embylivetv-series-timers" class="text-info">-</h3>
|
||||
<small>Series Timers</small>
|
||||
</div>
|
||||
</div>
|
||||
' . (($compactView === 'false') ? '
|
||||
<div class="' . $statsClass . '">
|
||||
<div class="text-center">
|
||||
<h3 id="embylivetv-today-recordings" class="text-warning">-</h3>
|
||||
<small>Today\'s Recordings</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="' . $statsClass . '">
|
||||
<div class="text-center">
|
||||
<h3 id="embylivetv-total-recordings" class="text-primary">-</h3>
|
||||
<small>Total Recordings</small>
|
||||
</div>
|
||||
</div>
|
||||
' : '') . '
|
||||
</div>
|
||||
|
||||
<!-- Scheduled Recordings Table -->
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-lg-12">
|
||||
<h4>
|
||||
Scheduled Recordings
|
||||
<small class="text-muted">Upcoming and active timers</small>
|
||||
</h4>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="120">Date</th>
|
||||
<th>Series</th>
|
||||
' . (($showSeriesInfo === 'true') ? '<th>Episode Title</th>' : '') . '
|
||||
<th>Channel</th>
|
||||
' . (($showUserInfo === 'true') ? '<th>User</th>' : '') . '
|
||||
' . (($showDuration === 'true') ? '<th width="80">Duration</th>' : '') . '
|
||||
<th width="70">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="embylivetv-scheduled-table">
|
||||
<tr>
|
||||
<td colspan="' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '" class="text-center">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Completed Recordings Table (only if enabled) -->
|
||||
' . (($showCompleted === 'true') ? '
|
||||
<div class="row" style="margin-top: 20px;">
|
||||
<div class="col-lg-12">
|
||||
<h4>
|
||||
Completed Recordings
|
||||
<small class="text-muted">Recent recordings</small>
|
||||
</h4>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-striped table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="120">Date</th>
|
||||
<th>Series</th>
|
||||
' . (($showSeriesInfo === 'true') ? '<th>Series</th>' : '') . '
|
||||
' . (($showDuration === 'true') ? '<th width="80">Duration</th>' : '') . '
|
||||
<th width="70">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="embylivetv-completed-table">
|
||||
<tr>
|
||||
<td colspan="' . (3 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '" class="text-center">
|
||||
<i class="fa fa-spinner fa-spin"></i> Loading...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
' : '') . '
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.panel-compact .white-box-content { padding: 10px; }
|
||||
.panel-compact h3 { margin: 5px 0; font-size: 1.8em; }
|
||||
.panel-compact small { font-size: 0.85em; }
|
||||
#' . __FUNCTION__ . ' .table-condensed td { padding: 4px 8px; font-size: 0.9em; }
|
||||
#' . __FUNCTION__ . ' .status-success { color: #5cb85c; }
|
||||
#' . __FUNCTION__ . ' .status-recording { color: #d9534f; }
|
||||
#' . __FUNCTION__ . ' .status-scheduled { color: #f0ad4e; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var embyLiveTVRefreshTimer;
|
||||
var embyLiveTVLastRefresh = 0;
|
||||
|
||||
function refreshEmbyLiveTVData() {
|
||||
var refreshIcon = $("#embylivetv-refresh-icon");
|
||||
refreshIcon.addClass("fa-spin");
|
||||
|
||||
// Show loading state
|
||||
$("#embylivetv-stats h3").text("-");
|
||||
$("#embylivetv-scheduled-table").html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center\"><i class=\"fa fa-spinner fa-spin\"></i> Loading...</td></tr>");
|
||||
' . (($showCompleted === 'true') ? '$("#embylivetv-completed-table").html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center\"><i class=\"fa fa-spinner fa-spin\"></i> Loading...</td></tr>");' : '') . ';
|
||||
|
||||
// Load stats and activity
|
||||
homepageEmbyLiveTVTrackerStats()
|
||||
.always(function() {
|
||||
refreshIcon.removeClass("fa-spin");
|
||||
embyLiveTVLastRefresh = Date.now();
|
||||
updateEmbyLiveTVLastRefreshTime();
|
||||
});
|
||||
}
|
||||
|
||||
function updateEmbyLiveTVLastRefreshTime() {
|
||||
if (embyLiveTVLastRefresh > 0) {
|
||||
var ago = Math.floor((Date.now() - embyLiveTVLastRefresh) / 1000);
|
||||
var timeText = ago < 60 ? ago + "s ago" : Math.floor(ago / 60) + "m ago";
|
||||
$("#embylivetv-last-update").text("Updated " + timeText);
|
||||
}
|
||||
}
|
||||
|
||||
function homepageEmbyLiveTVTrackerStats() {
|
||||
return organizrAPI2("GET", "api/v2/homepage/embyLiveTVTracker/stats")
|
||||
.done(function(data) {
|
||||
console.log("Stats response received:", data);
|
||||
if (data && data.response && data.response.result === "success" && data.response.data) {
|
||||
console.log("Stats data is valid, loading activity...");
|
||||
$("#embylivetv-active-timers").text(data.response.data.activeTimers || "0");
|
||||
$("#embylivetv-series-timers").text(data.response.data.seriesTimers || "0");
|
||||
' . (($compactView === 'false') ? '
|
||||
$("#embylivetv-today-recordings").text(data.response.data.todaysRecordings || "0");
|
||||
$("#embylivetv-total-recordings").text(data.response.data.totalRecordings || "0");
|
||||
' : '') . '
|
||||
|
||||
// Load activity
|
||||
homepageEmbyLiveTVTrackerActivity();
|
||||
} else {
|
||||
console.error("Stats response structure issue:", {
|
||||
hasData: !!data,
|
||||
hasResponse: !!(data && data.response),
|
||||
result: data && data.response && data.response.result,
|
||||
hasResponseData: !!(data && data.response && data.response.data)
|
||||
});
|
||||
console.error("Failed to load Emby LiveTV stats:", data.response ? data.response.message : "Unknown error");
|
||||
$("#embylivetv-stats h3").text("?").attr("title", "Error loading data");
|
||||
}
|
||||
})
|
||||
.fail(function(xhr, status, error) {
|
||||
console.error("Error loading Emby LiveTV stats:", error);
|
||||
$("#embylivetv-stats h3").text("!").attr("title", "Connection failed");
|
||||
});
|
||||
}
|
||||
|
||||
function homepageEmbyLiveTVTrackerActivity() {
|
||||
console.log("Activity function called - making API request...");
|
||||
return organizrAPI2("GET", "api/v2/homepage/embyLiveTVTracker/activity?days=' . ($daysShown ?: 7) . '\u0026limit=' . ($maxItems ?: 10) . '")
|
||||
.done(function(data) {
|
||||
console.log("Activity response received:", data);
|
||||
console.log("Response structure check:", {
|
||||
hasData: !!data,
|
||||
hasResponse: !!(data && data.response),
|
||||
result: data && data.response && data.response.result,
|
||||
hasResponseData: !!(data && data.response && data.response.data),
|
||||
hasActivities: !!(data && data.response && data.response.data && data.response.data.activities),
|
||||
activitiesLength: data && data.response && data.response.data && data.response.data.activities ? data.response.data.activities.length : 0
|
||||
});
|
||||
if (data && data.response && data.response.result === "success" && data.response.data) {
|
||||
var scheduledRecordings = data.response.data.scheduledRecordings || [];
|
||||
var completedRecordings = data.response.data.completedRecordings || [];
|
||||
|
||||
console.log("Scheduled recordings:", scheduledRecordings.length);
|
||||
console.log("Completed recordings:", completedRecordings.length);
|
||||
|
||||
// Apply limits to each category separately to ensure we show both types
|
||||
var maxScheduled = Math.floor(' . $maxItems . ' * 0.7); // 70% for scheduled
|
||||
var maxCompleted = ' . $maxItems . ' - maxScheduled; // 30% for completed
|
||||
|
||||
// If we have fewer scheduled than the 70% allocation, give more space to completed
|
||||
if (scheduledRecordings.length < maxScheduled) {
|
||||
maxCompleted = Math.min(completedRecordings.length, ' . $maxItems . ' - scheduledRecordings.length);
|
||||
}
|
||||
// If we have fewer completed than the 30% allocation, give more space to scheduled
|
||||
if (completedRecordings.length < maxCompleted) {
|
||||
maxScheduled = Math.min(scheduledRecordings.length, ' . $maxItems . ' - completedRecordings.length);
|
||||
}
|
||||
|
||||
var scheduledActivities = scheduledRecordings.slice(0, maxScheduled);
|
||||
var completedActivities = completedRecordings.slice(0, maxCompleted);
|
||||
|
||||
console.log("Split - Scheduled activities:", scheduledActivities.length);
|
||||
console.log("Split - Completed activities:", completedActivities.length);
|
||||
|
||||
// Helper function to format scheduled activity rows
|
||||
function formatScheduledRow(activity) {
|
||||
var date = new Date(activity.date);
|
||||
var formattedDate = date.toLocaleDateString() + " " + date.toLocaleTimeString([], {hour: "2-digit", minute:"2-digit"});
|
||||
var status = activity.status || "Scheduled";
|
||||
var statusClass = status.toLowerCase() === "completed" ? "status-success" :
|
||||
status.toLowerCase() === "recording" ? "status-recording" : "status-scheduled";
|
||||
|
||||
return "<tr>" +
|
||||
"<td><small>" + formattedDate + "</small></td>" +
|
||||
"<td>" + (activity.seriesName || activity.name || "-") + "</td>" +
|
||||
' . (($showSeriesInfo === 'true') ? '"<td><small>" + (activity.episodeTitle || activity.name || "-") + "</small></td>" +' : '') . '
|
||||
"<td><small>" + (activity.channelName || "-") + "</small></td>" +
|
||||
' . (($showUserInfo === 'true') ? '"<td><small>" + (activity.userName || "-") + "</small></td>" +' : '') . '
|
||||
' . (($showDuration === 'true') ? '"<td><small>" + (activity.duration || "-") + "</small></td>" +' : '') . '
|
||||
"<td><span class=\"" + statusClass + "\"><i class=\"fa fa-circle\"></i></span></td>" +
|
||||
"</tr>";
|
||||
}
|
||||
|
||||
// Helper function to format completed activity rows (no channel or user columns)
|
||||
function formatCompletedRow(activity) {
|
||||
var date = new Date(activity.date);
|
||||
var formattedDate = date.toLocaleDateString() + " " + date.toLocaleTimeString([], {hour: "2-digit", minute:"2-digit"});
|
||||
var status = activity.status || "Completed";
|
||||
var statusClass = "status-success";
|
||||
|
||||
return "<tr>" +
|
||||
"<td><small>" + formattedDate + "</small></td>" +
|
||||
"<td>" + (activity.seriesName || activity.name || "-") + "</td>" +
|
||||
' . (($showSeriesInfo === 'true') ? '"<td><small>" + (activity.seriesName || "-") + "</small></td>" +' : '') . '
|
||||
' . (($showDuration === 'true') ? '"<td><small>" + (activity.duration || "-") + "</small></td>" +' : '') . '
|
||||
"<td><span class=\"" + statusClass + "\"><i class=\"fa fa-circle\"></i></span></td>" +
|
||||
"</tr>";
|
||||
}
|
||||
|
||||
// Populate scheduled recordings table
|
||||
var scheduledTable = $("#embylivetv-scheduled-table");
|
||||
if (scheduledActivities.length === 0) {
|
||||
scheduledTable.html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0)) . '\" class=\"text-center text-muted\">No scheduled recordings</td></tr>");
|
||||
} else {
|
||||
var scheduledRows = scheduledActivities.map(formatScheduledRow).join("");
|
||||
scheduledTable.html(scheduledRows);
|
||||
}
|
||||
|
||||
// Populate completed recordings table (only if enabled and table exists)
|
||||
' . (($showCompleted === 'true') ? '
|
||||
var completedTable = $("#embylivetv-completed-table");
|
||||
if (completedActivities.length === 0) {
|
||||
completedTable.html("<tr><td colspan=\"' . (3 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center text-muted\">No completed recordings</td></tr>");
|
||||
} else {
|
||||
var completedRows = completedActivities.map(formatCompletedRow).join("");
|
||||
completedTable.html(completedRows);
|
||||
}
|
||||
' : '') . '
|
||||
} else {
|
||||
$("#embylivetv-scheduled-table").html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0)) . '\" class=\"text-center text-muted\">No activity data available</td></tr>");
|
||||
' . (($showCompleted === 'true') ? '$("#embylivetv-completed-table").html("<tr><td colspan=\"' . (3 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center text-muted\">No activity data available</td></tr>");' : '') . '
|
||||
}
|
||||
})
|
||||
.fail(function(xhr, status, error) {
|
||||
console.error("Error loading Emby LiveTV activity:", error);
|
||||
$("#embylivetv-scheduled-table").html("<tr><td colspan=\"' . (4 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showUserInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center text-danger\">Error loading activity data</td></tr>");
|
||||
' . (($showCompleted === 'true') ? '$("#embylivetv-completed-table").html("<tr><td colspan=\"' . (3 + ($showSeriesInfo === 'true' ? 1 : 0) + ($showDuration === 'true' ? 1 : 0)) . '\" class=\"text-center text-danger\">Error loading activity data</td></tr>");' : '') . '
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-refresh setup
|
||||
var refreshInterval = ' . $refreshInterval . ';
|
||||
if (refreshInterval > 0) {
|
||||
embyLiveTVRefreshTimer = setInterval(function() {
|
||||
refreshEmbyLiveTVData();
|
||||
}, refreshInterval);
|
||||
}
|
||||
|
||||
// Update time display every 30 seconds
|
||||
setInterval(updateEmbyLiveTVLastRefreshTime, 30000);
|
||||
|
||||
// Initial load
|
||||
$(document).ready(function() {
|
||||
refreshEmbyLiveTVData();
|
||||
});
|
||||
|
||||
// Cleanup timer when page unloads
|
||||
$(window).on("beforeunload", function() {
|
||||
if (embyLiveTVRefreshTimer) {
|
||||
clearInterval(embyLiveTVRefreshTimer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getHomepageEmbyLiveTVStats()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->config['embyURL'] || !$this->config['embyToken']) {
|
||||
$this->setAPIResponse('error', 'Emby URL or Token not configured', 500);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$options = $this->requestOptions($this->config['embyURL'], null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
|
||||
$baseUrl = $this->qualifyURL($this->config['embyURL']);
|
||||
|
||||
$stats = [
|
||||
'activeTimers' => 0,
|
||||
'seriesTimers' => 0,
|
||||
'todaysRecordings' => 0,
|
||||
'totalRecordings' => 0,
|
||||
'recentRecordings' => []
|
||||
];
|
||||
|
||||
// Get active timers
|
||||
$timersUrl = $baseUrl . '/emby/LiveTv/Timers?api_key=' . $this->config['embyToken'];
|
||||
try {
|
||||
$timersResponse = Requests::get($timersUrl, [], $options);
|
||||
if ($timersResponse->success) {
|
||||
$timers = json_decode($timersResponse->body, true);
|
||||
$stats['activeTimers'] = count($timers['Items'] ?? []);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get timers: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Get series timers
|
||||
$seriesTimersUrl = $baseUrl . '/emby/LiveTv/SeriesTimers?api_key=' . $this->config['embyToken'];
|
||||
try {
|
||||
$seriesTimersResponse = Requests::get($seriesTimersUrl, [], $options);
|
||||
if ($seriesTimersResponse->success) {
|
||||
$seriesTimers = json_decode($seriesTimersResponse->body, true);
|
||||
$stats['seriesTimers'] = count($seriesTimers['Items'] ?? []);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get series timers: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Get recordings from the last 90 days
|
||||
$recordingsUrl = $baseUrl . '/emby/LiveTv/Recordings?api_key=' . $this->config['embyToken'] . '&StartIndex=0&Limit=50&Fields=Overview,DateCreated&SortBy=DateCreated&SortOrder=Descending';
|
||||
try {
|
||||
$recordingsResponse = Requests::get($recordingsUrl, [], $options);
|
||||
if ($recordingsResponse->success) {
|
||||
$recordings = json_decode($recordingsResponse->body, true);
|
||||
$allRecordings = $recordings['Items'] ?? [];
|
||||
|
||||
// Count today's recordings
|
||||
$today = date('Y-m-d');
|
||||
$todaysCount = 0;
|
||||
$recentRecordings = [];
|
||||
|
||||
foreach ($allRecordings as $recording) {
|
||||
if (isset($recording['DateCreated'])) {
|
||||
$recordDate = date('Y-m-d', strtotime($recording['DateCreated']));
|
||||
if ($recordDate === $today) {
|
||||
$todaysCount++;
|
||||
}
|
||||
|
||||
// Add to recent recordings list
|
||||
$recentRecordings[] = [
|
||||
'date' => $recordDate,
|
||||
'program' => $recording['Name'] ?? 'Unknown',
|
||||
'series' => $recording['SeriesName'] ?? '',
|
||||
'channel' => $recording['ChannelName'] ?? 'Unknown Channel',
|
||||
'status' => 'Completed'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$stats['todaysRecordings'] = $todaysCount;
|
||||
$stats['totalRecordings'] = $recordings['TotalRecordCount'] ?? count($allRecordings);
|
||||
$stats['recentRecordings'] = array_slice($recentRecordings, 0, 10); // Limit to 10 recent recordings
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get recordings: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->setAPIResponse('success', 'LiveTV stats retrieved successfully', 200, $stats);
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', 'Failed to retrieve LiveTV stats: ' . $e->getMessage(), 500);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getHomepageEmbyLiveTVActivity()
|
||||
{
|
||||
$debugEnabled = $this->config['homepageEmbyLiveTVTrackerDebug'] ?? false;
|
||||
|
||||
if (!$this->homepageItemPermissions($this->embyLiveTVTrackerHomepagePermissions('main'), true)) {
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Permission denied for user');
|
||||
}
|
||||
$this->setAPIResponse('error', 'Permission denied', 403);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->config['embyURL'] || !$this->config['embyToken']) {
|
||||
$this->setAPIResponse('error', 'Emby URL or Token not configured', 500);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Activity method called - starting execution');
|
||||
}
|
||||
$options = $this->requestOptions($this->config['embyURL'], null, $this->config['embyDisableCertCheck'], $this->config['embyUseCustomCertificate']);
|
||||
$baseUrl = $this->qualifyURL($this->config['embyURL']);
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Base URL configured: ' . $baseUrl);
|
||||
}
|
||||
|
||||
$scheduledRecordings = [];
|
||||
$completedRecordings = [];
|
||||
$maxItems = intval($this->config['homepageEmbyLiveTVTrackerMaxItems'] ?? 10);
|
||||
$showCompleted = $this->config['homepageEmbyLiveTVTrackerShowCompleted'] ?? true;
|
||||
$maxCompletedItems = intval($this->config['homepageEmbyLiveTVTrackerMaxCompletedItems'] ?? 5);
|
||||
|
||||
// Get user info if user info is enabled
|
||||
$userMap = [];
|
||||
$showUserInfo = $this->config['homepageEmbyLiveTVTrackerShowUserInfo'] ?? false;
|
||||
if ($showUserInfo) {
|
||||
$usersUrl = $baseUrl . '/emby/Users?api_key=' . $this->config['embyToken'];
|
||||
try {
|
||||
$usersResponse = Requests::get($usersUrl, [], $options);
|
||||
if ($usersResponse->success) {
|
||||
$users = json_decode($usersResponse->body, true);
|
||||
foreach ($users as $user) {
|
||||
$userMap[$user['Id']] = $user['Name'];
|
||||
}
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($userMap) . ' users for mapping');
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get users: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Get scheduled recordings (active timers)
|
||||
$timersUrl = $baseUrl . '/emby/LiveTv/Timers?api_key=' . $this->config['embyToken'] . '&Fields=ChannelName,ChannelId,SeriesName,ProgramInfo,StartDate,EndDate,UserId';
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Fetching timers from URL: ' . $timersUrl);
|
||||
}
|
||||
try {
|
||||
$timersResponse = Requests::get($timersUrl, [], $options);
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API response status: ' . ($timersResponse->success ? 'success' : 'failed'));
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API response code: ' . $timersResponse->status_code);
|
||||
}
|
||||
if ($timersResponse->success) {
|
||||
$timers = json_decode($timersResponse->body, true);
|
||||
$allTimers = $timers['Items'] ?? [];
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($allTimers) . ' timers from Emby API');
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Timers API raw response length: ' . strlen($timersResponse->body));
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('First timer sample: ' . json_encode(array_slice($allTimers, 0, 1)));
|
||||
}
|
||||
|
||||
// Sort timers by start date
|
||||
usort($allTimers, function($a, $b) {
|
||||
$aDate = $a['StartDate'] ?? '';
|
||||
$bDate = $b['StartDate'] ?? '';
|
||||
return strcmp($aDate, $bDate);
|
||||
});
|
||||
|
||||
$timersToProcess = array_slice($allTimers, 0, intval($maxItems));
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Processing ' . count($timersToProcess) . ' timers (maxItems: ' . $maxItems . ')');
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('All timers count before slice: ' . count($allTimers));
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('MaxItems value: ' . $maxItems . ' (type: ' . gettype($maxItems) . ')');
|
||||
}
|
||||
|
||||
foreach ($timersToProcess as $index => $timer) {
|
||||
if ($debugEnabled) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Processing timer ' . ($index + 1) . ': ' . json_encode([
|
||||
'Name' => $timer['Name'] ?? 'no name',
|
||||
'StartDate' => $timer['StartDate'] ?? 'no start date',
|
||||
'EndDate' => $timer['EndDate'] ?? 'no end date',
|
||||
'ChannelName' => $timer['ChannelName'] ?? 'no channel name',
|
||||
'Status' => $timer['Status'] ?? 'no status',
|
||||
'UserId' => $timer['UserId'] ?? 'no user'
|
||||
]));
|
||||
}
|
||||
|
||||
// Calculate duration
|
||||
$duration = '-';
|
||||
if (isset($timer['StartDate']) && isset($timer['EndDate'])) {
|
||||
$start = strtotime($timer['StartDate']);
|
||||
$end = strtotime($timer['EndDate']);
|
||||
if ($start && $end) {
|
||||
$minutes = round(($end - $start) / 60);
|
||||
$hours = floor($minutes / 60);
|
||||
$remainingMinutes = $minutes % 60;
|
||||
if ($hours > 0) {
|
||||
$duration = sprintf('%dh %dm', $hours, $remainingMinutes);
|
||||
} else {
|
||||
$duration = sprintf('%dm', $minutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get channel name - timers should have this information
|
||||
$channelName = $timer['ChannelName'] ?? null;
|
||||
if (empty($channelName) && !empty($timer['ChannelId'])) {
|
||||
$channelName = 'Channel ' . $timer['ChannelId'];
|
||||
} elseif (empty($channelName)) {
|
||||
$channelName = 'Unknown Channel';
|
||||
}
|
||||
|
||||
// Get user name
|
||||
$userName = null;
|
||||
if ($showUserInfo && !empty($timer['UserId']) && isset($userMap[$timer['UserId']])) {
|
||||
$userName = $userMap[$timer['UserId']];
|
||||
}
|
||||
|
||||
// Determine status based on timing
|
||||
$status = 'Scheduled';
|
||||
$startTime = strtotime($timer['StartDate'] ?? '');
|
||||
$endTime = strtotime($timer['EndDate'] ?? '');
|
||||
$now = time();
|
||||
|
||||
if ($startTime && $endTime) {
|
||||
if ($now >= $startTime && $now <= $endTime) {
|
||||
$status = 'Recording';
|
||||
} elseif ($now > $endTime) {
|
||||
$status = 'Completed';
|
||||
}
|
||||
}
|
||||
|
||||
// Get series name and episode title - try multiple approaches
|
||||
$seriesName = '';
|
||||
$episodeTitle = '';
|
||||
|
||||
// Check for episode title first
|
||||
if (!empty($timer['ProgramInfo']['EpisodeTitle'])) {
|
||||
$episodeTitle = $timer['ProgramInfo']['EpisodeTitle'];
|
||||
}
|
||||
|
||||
// Get series name
|
||||
if (!empty($timer['SeriesName'])) {
|
||||
$seriesName = $timer['SeriesName'];
|
||||
} elseif (!empty($timer['ProgramInfo']['SeriesName'])) {
|
||||
$seriesName = $timer['ProgramInfo']['SeriesName'];
|
||||
} elseif (!empty($timer['ProgramInfo']['Name'])) {
|
||||
$seriesName = $timer['ProgramInfo']['Name'];
|
||||
} elseif (!empty($timer['Name'])) {
|
||||
$seriesName = $timer['Name'];
|
||||
}
|
||||
|
||||
// Use episode title if available, otherwise use program/series name
|
||||
$displayName = $episodeTitle ? $episodeTitle : ($timer['Name'] ?? ($timer['ProgramInfo']['Name'] ?? 'Unknown Program'));
|
||||
|
||||
$activity = [
|
||||
'date' => $timer['StartDate'] ?? '',
|
||||
'name' => $displayName,
|
||||
'seriesName' => $seriesName,
|
||||
'episodeTitle' => $episodeTitle,
|
||||
'channelName' => $channelName,
|
||||
'userName' => $userName,
|
||||
'duration' => $duration,
|
||||
'status' => $status,
|
||||
'type' => 'timer'
|
||||
];
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Created activity for timer ' . ($index + 1) . ': ' . json_encode($activity));
|
||||
|
||||
// Debug timer date parsing
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Timer date debug - StartDate: "' . ($timer['StartDate'] ?? 'null') . '", parsed startTime: ' . ($startTime ? date('Y-m-d H:i:s', $startTime) : 'failed to parse') . ', EndDate: "' . ($timer['EndDate'] ?? 'null') . '", parsed endTime: ' . ($endTime ? date('Y-m-d H:i:s', $endTime) : 'failed to parse') . ', current time: ' . date('Y-m-d H:i:s', $now) . ', calculated status: ' . $status);
|
||||
|
||||
$scheduledRecordings[] = $activity;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get timers for activity: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// Get completed recordings if enabled
|
||||
if ($showCompleted) {
|
||||
$recordingsUrl = $baseUrl . '/emby/LiveTv/Recordings?api_key=' . $this->config['embyToken'] . '&StartIndex=0&Limit=' . $maxCompletedItems . '&Fields=DateCreated,SeriesName,RunTimeTicks&SortBy=DateCreated&SortOrder=Descending';
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Fetching completed recordings from URL: ' . $recordingsUrl);
|
||||
try {
|
||||
$recordingsResponse = Requests::get($recordingsUrl, [], $options);
|
||||
if ($recordingsResponse->success) {
|
||||
$recordings = json_decode($recordingsResponse->body, true);
|
||||
$allRecordings = $recordings['Items'] ?? [];
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Retrieved ' . count($allRecordings) . ' completed recordings');
|
||||
|
||||
foreach ($allRecordings as $recording) {
|
||||
if (isset($recording['DateCreated'])) {
|
||||
// Format duration
|
||||
$duration = '-';
|
||||
if (isset($recording['RunTimeTicks']) && $recording['RunTimeTicks'] > 0) {
|
||||
$minutes = floor($recording['RunTimeTicks'] / 600000000);
|
||||
$hours = floor($minutes / 60);
|
||||
$remainingMinutes = $minutes % 60;
|
||||
if ($hours > 0) {
|
||||
$duration = sprintf('%dh %dm', $hours, $remainingMinutes);
|
||||
} else {
|
||||
$duration = sprintf('%dm', $minutes);
|
||||
}
|
||||
}
|
||||
|
||||
$completedRecordings[] = [
|
||||
'date' => $recording['DateCreated'],
|
||||
'name' => $recording['Name'] ?? 'Unknown Program',
|
||||
'seriesName' => $recording['SeriesName'] ?? '',
|
||||
'channelName' => 'Unknown Channel', // Completed recordings don't have reliable channel info
|
||||
'userName' => null, // No user info available for completed recordings
|
||||
'duration' => $duration,
|
||||
'status' => 'Completed',
|
||||
'type' => 'recording'
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->warning('Failed to get completed recordings: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Final scheduled recordings count: ' . count($scheduledRecordings));
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Final completed recordings count: ' . count($completedRecordings));
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Sample scheduled: ' . json_encode(array_slice($scheduledRecordings, 0, 1)));
|
||||
$this->setLoggerChannel('EmbyLiveTVTracker')->info('Sample completed: ' . json_encode(array_slice($completedRecordings, 0, 1)));
|
||||
|
||||
$this->setAPIResponse('success', 'LiveTV activity retrieved successfully', 200, [
|
||||
'scheduledRecordings' => $scheduledRecordings,
|
||||
'completedRecordings' => $completedRecordings
|
||||
]);
|
||||
return true;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', 'Failed to retrieve LiveTV activity: ' . $e->getMessage(), 500);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
trait HealthChecksHomepageItem
|
||||
{
|
||||
public function healthChecksSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'HealthChecks',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/healthchecks.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'docs' => $this->docs('features/homepage/healthchecks-homepage-item'),
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageHealthChecksEnabled'),
|
||||
$this->settingsOption('auth', 'homepageHealthChecksAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'healthChecksURL'),
|
||||
$this->settingsOption('multiple-token', 'healthChecksToken'),
|
||||
$this->settingsOption('disable-cert-check', 'healthChecksDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'healthChecksUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('multiple', 'healthChecksTags', ['label' => 'Tags', 'help' => 'Pull only checks with this tag - Blank for all', 'placeholder' => 'Multiple tags using CSV - tag1,tag2']),
|
||||
$this->settingsOption('refresh', 'homepageHealthChecksRefresh'),
|
||||
$this->settingsOption('switch', 'homepageHealthChecksShowDesc', ['label' => 'Show Description']),
|
||||
$this->settingsOption('switch', 'homepageHealthChecksShowTags', ['label' => 'Show Tags']),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function healthChecksHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageHealthChecksEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageHealthChecksAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'healthChecksURL',
|
||||
'healthChecksToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderhealthchecks()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->healthChecksHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Health Checks...</h2></div>
|
||||
<script>
|
||||
// Health Checks
|
||||
homepageHealthChecks("' . $this->config['healthChecksTags'] . '","' . $this->config['homepageHealthChecksRefresh'] . '");
|
||||
// End Health Checks
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getHealthChecks($tags = null)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->healthChecksHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api['content']['checks'] = array();
|
||||
$tags = ($tags) ? $this->healthChecksTags($tags) : '';
|
||||
$healthChecks = explode(',', $this->config['healthChecksToken']);
|
||||
foreach ($healthChecks as $token) {
|
||||
$url = $this->qualifyURL($this->config['healthChecksURL']) . '/' . $tags;
|
||||
try {
|
||||
$headers = array('X-Api-Key' => $token);
|
||||
$options = $this->requestOptions($url, $this->config['homepageHealthChecksRefresh'], $this->config['healthChecksDisableCertCheck'], $this->config['healthChecksUseCustomCertificate']);
|
||||
$response = Requests::get($url, $headers, $options);
|
||||
if ($response->success) {
|
||||
$healthResults = json_decode($response->body, true);
|
||||
$api['content']['checks'] = array_merge($api['content']['checks'], $healthResults['checks']);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('HealthChecks')->error($e);
|
||||
};
|
||||
}
|
||||
usort($api['content']['checks'], function ($a, $b) {
|
||||
return $a['status'] <=> $b['status'];
|
||||
});
|
||||
$api['options'] = [
|
||||
'desc' => $this->config['homepageHealthChecksShowDesc'],
|
||||
'tags' => $this->config['homepageHealthChecksShowTags'],
|
||||
];
|
||||
$api['content']['checks'] = isset($api['content']['checks']) ? $api['content']['checks'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function healthChecksTags($tags)
|
||||
{
|
||||
$return = '?tag=';
|
||||
if (!$tags) {
|
||||
return '';
|
||||
} elseif ($tags == '*') {
|
||||
return '';
|
||||
} else {
|
||||
if (strpos($tags, ',') !== false) {
|
||||
$list = explode(',', $tags);
|
||||
return $return . implode("&tag=", $list);
|
||||
} else {
|
||||
return $return . $tags;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
trait HTMLHomepageItem
|
||||
{
|
||||
public function customHtmlNumber()
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
public function customHtmlSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'CustomHTML',
|
||||
'enabled' => strpos('personal,business', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/HTML5.png',
|
||||
'category' => 'Custom',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => []
|
||||
];
|
||||
for ($i = 1; $i <= $this->customHtmlNumber(); $i++) {
|
||||
$i = sprintf('%02d', $i);
|
||||
$homepageSettings['settings']['Custom HTML ' . $i] = array(
|
||||
$this->settingsOption('enable', 'homepageCustomHTML' . $i . 'Enabled'),
|
||||
$this->settingsOption('auth', 'homepageCustomHTML' . $i . 'Auth'),
|
||||
//$this->settingsOption('pre-code-editor', 'customHTML' . $i), // possibly can remove this as we consolidated the type into one
|
||||
$this->settingsOption('code-editor', 'customHTML' . $i, ['label' => 'Custom HTML Code', 'mode' => 'html']),
|
||||
);
|
||||
}
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function htmlHomepagePermissions($key = null)
|
||||
{
|
||||
for ($i = 1; $i <= $this->customHtmlNumber(); $i++) {
|
||||
$i = sprintf('%02d', $i);
|
||||
$permissions[$i] = [
|
||||
'enabled' => [
|
||||
'homepageCustomHTML' . $i . 'Enabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageCustomHTML' . $i . 'Auth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'customHTML' . $i
|
||||
]
|
||||
];
|
||||
}
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrdercustomhtml($key = '01')
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->htmlHomepagePermissions($key))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $this->config['customHTML' . $key] . '
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
}
|
||||
328
docker/test/tvtracker/config/www/organizr/api/homepage/ical.php
Normal file
328
docker/test/tvtracker/config/www/organizr/api/homepage/ical.php
Normal file
@@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
trait ICalHomepageItem
|
||||
{
|
||||
public function calendarDaysCheck($entryStart, $entryEnd)
|
||||
{
|
||||
$success = false;
|
||||
$entryStart = intval($entryStart);
|
||||
$entryEnd = intval($entryEnd);
|
||||
if ($entryStart >= 0 && $entryEnd <= 0) {
|
||||
$success = true;
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function calendarStandardizeTimezone($timezone)
|
||||
{
|
||||
switch ($timezone) {
|
||||
case('CST'):
|
||||
case('Central Time'):
|
||||
case('Central Standard Time'):
|
||||
$timezone = 'America/Chicago';
|
||||
break;
|
||||
case('CET'):
|
||||
case('Central European Time'):
|
||||
$timezone = 'Europe/Berlin';
|
||||
break;
|
||||
case('EST'):
|
||||
case('Eastern Time'):
|
||||
case('Eastern Standard Time'):
|
||||
$timezone = 'America/New_York';
|
||||
break;
|
||||
case('MST'):
|
||||
case('Mountain Time'):
|
||||
case('Mountain Standard Time'):
|
||||
$timezone = 'America/Denver';
|
||||
break;
|
||||
case('PST'):
|
||||
case('Pacific Time'):
|
||||
case('Pacific Standard Time'):
|
||||
$timezone = 'America/Los_Angeles';
|
||||
break;
|
||||
case('AKST'):
|
||||
case('Alaska Time'):
|
||||
case('Alaska Standard Time'):
|
||||
$timezone = 'America/Anchorage';
|
||||
break;
|
||||
case('HST'):
|
||||
case('Hawaii Time'):
|
||||
case('Hawaii Standard Time'):
|
||||
$timezone = 'Pacific/Honolulu';
|
||||
break;
|
||||
case('China Time'):
|
||||
case('China Standard Time'):
|
||||
$timezone = 'Asia/Beijing';
|
||||
break;
|
||||
case('IST'):
|
||||
case('India Time'):
|
||||
case('India Standard Time'):
|
||||
$timezone = 'Asia/New_Delhi';
|
||||
break;
|
||||
case('JST'):
|
||||
case('Japan Time'):
|
||||
case('Japan Standard Time'):
|
||||
$timezone = 'Asia/Tokyo';
|
||||
break;
|
||||
case('WET'):
|
||||
case('WEST'):
|
||||
case('Western European Time'):
|
||||
case('Western European Standard Time'):
|
||||
case('Western European Summer Time'):
|
||||
case('W. Europe Time'):
|
||||
case('W. Europe Standard Time'):
|
||||
case('W. Europe Summer Time'):
|
||||
$timezone = 'Europe/Lisbon';
|
||||
break;
|
||||
}
|
||||
return in_array($timezone, timezone_identifiers_list()) ? $timezone : 'UTC';
|
||||
}
|
||||
|
||||
public function getCalendarExtraDates($start, $rule, $timezone)
|
||||
{
|
||||
$extraDates = [];
|
||||
try {
|
||||
if (stripos($rule, 'FREQ') !== false) {
|
||||
$until = $this->getCalenderRepeatUntil($rule);
|
||||
$start = new DateTime ($start);
|
||||
$startDate = new DateTime ($this->currentTime);
|
||||
$startDate->setTime($start->format('H'), $start->format('i'));
|
||||
$startDate->modify('-' . $this->config['calendarStart'] . ' days');
|
||||
$endDate = new DateTime ($this->currentTime);
|
||||
$endDate->modify('+' . $this->config['calendarEnd'] . ' days');
|
||||
$start = (stripos($rule, 'BYDAY') !== false || stripos($rule, 'BYMONTHDAY') !== false || stripos($rule, 'DAILY') !== false) ? $startDate : $start;
|
||||
$until = $until ? new DateTime($until) : $endDate;
|
||||
$dates = new \Recurr\Rule(trim($rule));
|
||||
$dates->setStartDate($start)->setUntil($until);
|
||||
$transformer = new \Recurr\Transformer\ArrayTransformer();
|
||||
$transformerConfig = new \Recurr\Transformer\ArrayTransformerConfig();
|
||||
$transformerConfig->enableLastDayOfMonthFix();
|
||||
$transformer->setConfig($transformerConfig);
|
||||
foreach (@$transformer->transform($dates) as $key => $date) {
|
||||
if ($date->getStart() >= $startDate) {
|
||||
$extraDates[$key]['start'] = $date->getStart();
|
||||
$extraDates[$key]['end'] = $date->getEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Recurr\Exception\InvalidRRule|\Recurr\Exception\InvalidWeekday|Exception $e) {
|
||||
return $extraDates;
|
||||
}
|
||||
return $extraDates;
|
||||
}
|
||||
|
||||
public function getCalenderRepeat($value)
|
||||
{
|
||||
//FREQ=DAILY
|
||||
//RRULE:FREQ=WEEKLY;BYDAY=TH
|
||||
$first = explode('=', $value);
|
||||
if (count($first) > 1) {
|
||||
$second = explode(';', $first[1]);
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
if ($second) {
|
||||
return $second[0];
|
||||
} else {
|
||||
return $first[1];
|
||||
}
|
||||
}
|
||||
|
||||
public function getCalenderRepeatUntil($value)
|
||||
{
|
||||
$first = explode('UNTIL=', $value);
|
||||
if (count($first) > 1) {
|
||||
if (strpos($first[1], ';') !== false) {
|
||||
$check = explode(';', $first[1]);
|
||||
return $check[0];
|
||||
} else {
|
||||
return $first[1];
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getCalenderRepeatCount($value)
|
||||
{
|
||||
$first = explode('COUNT=', $value);
|
||||
if (count($first) > 1) {
|
||||
return $first[1];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function file_get_contents_curl($url)
|
||||
{
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_AUTOREFERER, true);
|
||||
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
$data = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getIcsEventsAsArray($file)
|
||||
{
|
||||
$icalString = $this->file_get_contents_curl($file);
|
||||
$icsDates = array();
|
||||
/* Explode the ICs Data to get datas as array according to string ‘BEGIN:’ */
|
||||
$icsData = explode('BEGIN:', $icalString);
|
||||
/* Iterating the icsData value to make all the start end dates as sub array */
|
||||
foreach ($icsData as $key => $value) {
|
||||
$icsDatesMeta [$key] = explode("\n", $value);
|
||||
}
|
||||
/* Itearting the Ics Meta Value */
|
||||
foreach ($icsDatesMeta as $key => $value) {
|
||||
foreach ($value as $subKey => $subValue) {
|
||||
/* to get ics events in proper order */
|
||||
$icsDates = $this->getICSDates($key, $subKey, $subValue, $icsDates);
|
||||
}
|
||||
}
|
||||
return $icsDates;
|
||||
}
|
||||
|
||||
/* function is to avoid the elements which is not having the proper start, end and summary information */
|
||||
public function getICSDates($key, $subKey, $subValue, $icsDates)
|
||||
{
|
||||
if ($key != 0 && $subKey == 0) {
|
||||
$icsDates [$key] ['BEGIN'] = $subValue;
|
||||
} else {
|
||||
$subValueArr = explode(':', $subValue, 2);
|
||||
if (isset ($subValueArr [1])) {
|
||||
$icsDates [$key] [$subValueArr [0]] = $subValueArr [1];
|
||||
}
|
||||
}
|
||||
return $icsDates;
|
||||
}
|
||||
|
||||
public function retrieveCalenderByURL($url)
|
||||
{
|
||||
$events = [];
|
||||
$icsEvents = $this->getIcsEventsAsArray($url);
|
||||
if (isset($icsEvents) && !empty($icsEvents)) {
|
||||
$timeZone = isset($icsEvents [1] ['X-WR-TIMEZONE']) ? trim($icsEvents[1]['X-WR-TIMEZONE']) : date_default_timezone_get();
|
||||
$originalTimeZone = isset($icsEvents [1] ['X-WR-TIMEZONE']) ? str_replace('"', '', trim($icsEvents[1]['X-WR-TIMEZONE'])) : false;
|
||||
unset($icsEvents [1]);
|
||||
foreach ($icsEvents as $icsEvent) {
|
||||
$startKeys = $this->array_filter_key($icsEvent, function ($key) {
|
||||
return strpos($key, 'DTSTART') === 0;
|
||||
});
|
||||
$endKeys = $this->array_filter_key($icsEvent, function ($key) {
|
||||
return strpos($key, 'DTEND') === 0;
|
||||
});
|
||||
if (!empty($startKeys) && !empty($endKeys) && isset($icsEvent['SUMMARY'])) {
|
||||
/* Getting start date and time */
|
||||
$dates = [];
|
||||
$repeat = $icsEvent ['RRULE'] ?? false;
|
||||
if (!$originalTimeZone) {
|
||||
$tzKey = array_keys($startKeys);
|
||||
if (strpos($tzKey[0], 'TZID=') !== false) {
|
||||
$originalTimeZone = explode('TZID=', (string)$tzKey[0]);
|
||||
$originalTimeZone = (count($originalTimeZone) >= 2) ? str_replace('"', '', $originalTimeZone[1]) : false;
|
||||
$originalTimeZone = stripos($originalTimeZone, ';') !== false ? explode(';', $originalTimeZone)[0] : $originalTimeZone;
|
||||
}
|
||||
}
|
||||
$start = reset($startKeys);
|
||||
$end = reset($endKeys);
|
||||
$oldestDay = new DateTime ($this->currentTime);
|
||||
$oldestDay->modify('-' . $this->config['calendarStart'] . ' days');
|
||||
$newestDay = new DateTime ($this->currentTime);
|
||||
$newestDay->modify('+' . $this->config['calendarEnd'] . ' days');
|
||||
|
||||
if ($repeat) {
|
||||
$dates = $this->getCalendarExtraDates($start, $icsEvent['RRULE'], $originalTimeZone);
|
||||
} else {
|
||||
$dates[] = [
|
||||
'start' => new DateTime ($start),
|
||||
'end' => new DateTime ($end)
|
||||
];
|
||||
if ($oldestDay > new DateTime ($end)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach ($dates as $eventDate) {
|
||||
/* Converting to datetime and apply the timezone to get proper date time */
|
||||
$startDt = $eventDate['start'];
|
||||
/* Getting end date with time */
|
||||
$endDt = $eventDate['end'];
|
||||
$calendarStartDiff = date_diff($startDt, $newestDay);
|
||||
$calendarEndDiff = date_diff($startDt, $oldestDay);
|
||||
if ($originalTimeZone && $originalTimeZone !== 'UTC' && (strpos($start, 'Z') == false)) {
|
||||
$originalTimeZone = $this->calendarStandardizeTimezone($originalTimeZone);
|
||||
$dateTimeOriginalTZ = new DateTimeZone($originalTimeZone);
|
||||
$dateTimeOriginal = new DateTime('now', $dateTimeOriginalTZ);
|
||||
$dateTimeUTCTZ = new DateTimeZone(date_default_timezone_get());
|
||||
$dateTimeUTC = new DateTime('now', $dateTimeUTCTZ);
|
||||
$dateTimeOriginalOffset = $dateTimeOriginal->getOffset() / 3600;
|
||||
$dateTimeUTCOffset = $dateTimeUTC->getOffset() / 3600;
|
||||
$diff = $dateTimeUTCOffset - $dateTimeOriginalOffset;
|
||||
if ((int)$diff >= 0) {
|
||||
$startDt->modify('+ ' . $diff . ' hour');
|
||||
$endDt->modify('+ ' . $diff . ' hour');
|
||||
}
|
||||
}
|
||||
$startDt->setTimeZone(new DateTimezone ($timeZone));
|
||||
$endDt->setTimeZone(new DateTimezone ($timeZone));
|
||||
$startDate = $startDt->format(DateTime::ATOM);
|
||||
$endDate = $endDt->format(DateTime::ATOM);
|
||||
$dates = isset($icsEvent['RRULE']) ? $dates : null;
|
||||
if (new DateTime() < $endDt) {
|
||||
$extraClass = 'text-info';
|
||||
} else {
|
||||
$extraClass = 'text-success';
|
||||
}
|
||||
/* Getting the name of event */
|
||||
$eventName = $icsEvent['SUMMARY'];
|
||||
if (!$this->calendarDaysCheck($calendarStartDiff->format('%R') . $calendarStartDiff->days, $calendarEndDiff->format('%R') . $calendarEndDiff->days)) {
|
||||
break;
|
||||
}
|
||||
if ($startDt->format('H') == 0 && $startDt->format('i') == 0) {
|
||||
$startDate = $startDt->format('Y-m-d');
|
||||
}
|
||||
$events[] = array(
|
||||
'title' => $eventName,
|
||||
'imagetype' => 'calendar-o text-warning text-custom-calendar ' . $extraClass,
|
||||
'imagetypeFilter' => 'ical',
|
||||
'className' => 'bg-calendar calendar-item bg-custom-calendar',
|
||||
'start' => (strlen(trim($start)) == 8) ? $eventDate['start']->format('Y-m-d') : $startDate,
|
||||
'end' => (strlen(trim($end)) == 8) ? $eventDate['end']->format('Y-m-d') : $endDate,
|
||||
'bgColor' => str_replace('text', 'bg', $extraClass),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
|
||||
public function getICalendar()
|
||||
{
|
||||
if (!$this->config['homepageCalendarEnabled']) {
|
||||
$this->setAPIResponse('error', 'iCal homepage item is not enabled', 409);
|
||||
return false;
|
||||
}
|
||||
if (!$this->qualifyRequest($this->config['homepageCalendarAuth'])) {
|
||||
$this->setAPIResponse('error', 'User not approved to view this homepage item', 401);
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['calendariCal'])) {
|
||||
$this->setAPIResponse('error', 'iCal URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$iCalEvents = [];
|
||||
$calendarURLList = explode(',', $this->config['calendariCal']);
|
||||
foreach ($calendarURLList as $key => $value) {
|
||||
$iCalEvents = array_merge($iCalEvents, $this->retrieveCalenderByURL($value));
|
||||
}
|
||||
$calendarSources = $iCalEvents;
|
||||
$this->setAPIResponse('success', null, 200, $calendarSources);
|
||||
return $calendarSources;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
trait JackettHomepageItem
|
||||
{
|
||||
public function jackettSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Jackett',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/jackett.png',
|
||||
'category' => 'Utility',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageJackettEnabled'),
|
||||
$this->settingsOption('auth', 'homepageJackettAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'jackettURL'),
|
||||
$this->settingsOption('token', 'jackettToken'),
|
||||
$this->settingsOption('disable-cert-check', 'jackettDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'jackettUseCustomCertificate'),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('switch', 'homepageJackettBackholeDownload', ['label' => 'Prefer black hole download', 'help' => 'Prefer black hole download link instead of direct/magnet download']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'jackett'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function jackettHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageJackettEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageJackettAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'jackettURL',
|
||||
'jackettToken'
|
||||
]
|
||||
],
|
||||
'test' => [
|
||||
'auth' => [
|
||||
'homepageJackettAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'jackettURL',
|
||||
'jackettToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderJackett()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->jackettHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Jackett...</h2></div>
|
||||
<script>
|
||||
// Jackett
|
||||
homepageJackett();
|
||||
// End Jackett
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnectionJackett()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->jackettHomepagePermissions('test'), true)) {
|
||||
return false;
|
||||
}
|
||||
$apiURL = $this->qualifyURL($this->config['jackettURL']);
|
||||
$endpoint = $apiURL . '/api/v2.0/indexers/all/results?apikey=' . $this->config['jackettToken'] . '&Query=this-is-just-a-test-for-organizr';
|
||||
try {
|
||||
$headers = [];
|
||||
$options = $this->requestOptions($apiURL, 120, $this->config['jackettDisableCertCheck'], $this->config['jackettUseCustomCertificate']);
|
||||
$response = Requests::get($endpoint, $headers, $options);
|
||||
if ($response->success) {
|
||||
$apiData = json_decode($response->body, true);
|
||||
$api['content'] = $apiData;
|
||||
unset($apiData);
|
||||
} else {
|
||||
$this->setResponse(403, 'Error connecting to Jackett');
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setResponse(200, null, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function searchJackettIndexers($query = null)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->jackettHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
if (!$query) {
|
||||
$this->setAPIResponse('error', 'Query was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
$apiURL = $this->qualifyURL($this->config['jackettURL']);
|
||||
$endpoint = $apiURL . '/api/v2.0/indexers/all/results?apikey=' . $this->config['jackettToken'] . '&Query=' . urlencode($query);
|
||||
try {
|
||||
$headers = [];
|
||||
$options = $this->requestOptions($apiURL, 120, $this->config['jackettDisableCertCheck'], $this->config['jackettUseCustomCertificate']);
|
||||
$response = Requests::get($endpoint, $headers, $options);
|
||||
if ($response->success) {
|
||||
$apiData = json_decode($response->body, true);
|
||||
$api['content'] = $apiData;
|
||||
unset($apiData);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function performJackettBackHoleDownload($url = null)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->jackettHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
if (!$url) {
|
||||
$this->setAPIResponse('error', 'URL was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
$apiURL = $this->qualifyURL($this->config['jackettURL']);
|
||||
$endpoint = $apiURL . $url;
|
||||
error_log($endpoint);
|
||||
try {
|
||||
$headers = [];
|
||||
$options = $this->requestOptions($apiURL, 120, $this->config['jackettDisableCertCheck'], $this->config['jackettUseCustomCertificate']);
|
||||
$response = Requests::get($endpoint, $headers, $options);
|
||||
if ($response->success) {
|
||||
$apiData = json_decode($response->body, true);
|
||||
$api['content'] = $apiData;
|
||||
unset($apiData);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Jackett')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
if ($api['content'] && $api['content']['result'] == 'success') {
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
} else if ($api['content']) {
|
||||
$this->setAPIResponse('error', $api['content']['error'], 400, $api);
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Unknown error', 400, $api);
|
||||
}
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<?php
|
||||
|
||||
trait JDownloaderHomepageItem
|
||||
{
|
||||
public function jDownloaderSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'JDownloader',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/jdownloader.png',
|
||||
'category' => 'Downloader',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'FYI' => [
|
||||
$this->settingsOption('html', null, ['override' => 12, 'html' => '
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<span lang="en">Notice</span>
|
||||
</div>
|
||||
<div class="panel-wrapper collapse in" aria-expanded="true">
|
||||
<div class="panel-body">
|
||||
<ul class="list-icons">
|
||||
<li><i class="fa fa-chevron-right text-danger"></i> <a href="https://pypi.org/project/myjd-api/" target="_blank">Download [myjd-api] Module</a></li>
|
||||
<li><i class="fa fa-chevron-right text-danger"></i> Add <b>/api/myjd</b> to the URL if you are using <a href="https://pypi.org/project/FeedCrawler/" target="_blank">FeedCrawler</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>']
|
||||
),
|
||||
],
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageJdownloaderEnabled'),
|
||||
$this->settingsOption('auth', 'homepageJdownloaderAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'jdownloaderURL'),
|
||||
$this->settingsOption('username', 'jdownloaderUsername'),
|
||||
$this->settingsOption('password', 'jdownloaderPassword'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('disable-cert-check', 'jdownloaderDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'jdownloaderUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('refresh', 'jdownloaderRefresh'),
|
||||
$this->settingsOption('combine', 'jdownloaderCombine'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'jdownloader'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionJDownloader()
|
||||
{
|
||||
if (empty($this->config['jdownloaderURL'])) {
|
||||
$this->setAPIResponse('error', 'JDownloader URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (!empty($this->config['jdownloaderUsername']) || !empty($this->config['jdownloaderPassword'])) {
|
||||
$auth = array('auth' => array($this->config['jdownloaderUsername'], $this->decrypt($this->config['jdownloaderPassword'])));
|
||||
} else {
|
||||
$auth = [];
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['jdownloaderURL']);
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['jdownloaderRefresh'], $this->config['jdownloaderDisableCertCheck'], $this->config['jdownloaderUseCustomCertificate'], $auth);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
echo($response->body);
|
||||
$this->setAPIResponse('success', 'JDownloader Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('JDownloader')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function jDownloaderHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageJdownloaderEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageJdownloaderAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'jdownloaderURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderjdownloader()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->jDownloaderHomepagePermissions('main'))) {
|
||||
$loadingBox = ($this->config['jdownloaderCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['jdownloaderCombine']) ? 'buildDownloaderCombined(\'jdownloader\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("jdownloader"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrderjdownloader
|
||||
' . $builder . '
|
||||
homepageDownloader("jdownloader", "' . $this->config['jdownloaderRefresh'] . '");
|
||||
// End homepageOrderjdownloader
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getJdownloaderHomepageQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->jDownloaderHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['jdownloaderURL']);
|
||||
if (!empty($this->config['jdownloaderUsername']) || !empty($this->config['jdownloaderPassword'])) {
|
||||
$auth = array('auth' => array($this->config['jdownloaderUsername'], $this->decrypt($this->config['jdownloaderPassword'])));
|
||||
} else {
|
||||
$auth = [];
|
||||
}
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['jdownloaderRefresh'], $this->config['jdownloaderDisableCertCheck'], $this->config['jdownloaderUseCustomCertificate'], $auth);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$temp = json_decode($response->body, true);
|
||||
$packages = $temp['packages'];
|
||||
if ($packages['downloader']) {
|
||||
$api['content']['queueItems'] = $packages['downloader'];
|
||||
} else {
|
||||
$api['content']['queueItems'] = [];
|
||||
}
|
||||
if ($packages['linkgrabber_decrypted']) {
|
||||
$api['content']['grabberItems'] = $packages['linkgrabber_decrypted'];
|
||||
} else {
|
||||
$api['content']['grabberItems'] = [];
|
||||
}
|
||||
if ($packages['linkgrabber_failed']) {
|
||||
$api['content']['encryptedItems'] = $packages['linkgrabber_failed'];
|
||||
} else {
|
||||
$api['content']['encryptedItems'] = [];
|
||||
}
|
||||
if ($packages['linkgrabber_offline']) {
|
||||
$api['content']['offlineItems'] = $packages['linkgrabber_offline'];
|
||||
} else {
|
||||
$api['content']['offlineItems'] = [];
|
||||
}
|
||||
$api['content']['$status'] = array($temp['downloader_state'], $temp['grabber_collecting'], $temp['update_ready']);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('JDownloader')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
<?php
|
||||
|
||||
trait JellyfinHomepageItem
|
||||
{
|
||||
|
||||
public function jellyfinSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Jellyfin',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/jellyfin.png',
|
||||
'category' => 'Media Server',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageJellyfinEnabled'),
|
||||
$this->settingsOption('auth', 'homepageJellyfinAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'jellyfinURL'),
|
||||
$this->settingsOption('token', 'jellyfinToken'),
|
||||
$this->settingsOption('disable-cert-check', 'jellyfinDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'jellyfinUseCustomCertificate'),
|
||||
],
|
||||
'Active Streams' => [
|
||||
$this->settingsOption('enable', 'homepageJellyfinStreams'),
|
||||
$this->settingsOption('auth', 'homepageJellyStreamsAuth'),
|
||||
$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information']),
|
||||
$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
|
||||
$this->settingsOption('refresh', 'homepageStreamRefresh'),
|
||||
],
|
||||
'Recent Items' => [
|
||||
$this->settingsOption('enable', 'homepageJellyfinRecent'),
|
||||
$this->settingsOption('auth', 'homepageJellyfinRecentAuth'),
|
||||
$this->settingsOption('limit', 'homepageRecentLimit'),
|
||||
$this->settingsOption('refresh', 'homepageRecentRefresh'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('input', 'homepageJellyfinLink', ['label' => 'Jellyfin Homepage Link URL', 'help' => 'Available variables: {id} {serverId}']),
|
||||
$this->settingsOption('input', 'jellyfinTabName', ['label' => 'Jellyfin Tab Name', 'placeholder' => 'Only use if you have Jellyfin in a reverse proxy']),
|
||||
$this->settingsOption('image-cache-quality', 'cacheImageSize'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'jellyfin'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionJellyfin()
|
||||
{
|
||||
if (empty($this->config['jellyfinURL'])) {
|
||||
$this->setAPIResponse('error', 'Jellyfin URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['jellyfinToken'])) {
|
||||
$this->setAPIResponse('error', 'Jellyfin Token is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['jellyfinURL']);
|
||||
$url = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
|
||||
$options = $this->requestOptions($url, null, $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body);
|
||||
if (is_array($json) || is_object($json)) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'URL or token incorrect', 409);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Jellyfin Connection Error', 500);
|
||||
return true;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function jellyfinHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'streams' => [
|
||||
'enabled' => [
|
||||
'homepageJellyfinEnabled',
|
||||
'homepageJellyfinStreams'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageJellyfinAuth',
|
||||
'homepageJellyStreamsAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'jellyfinURL',
|
||||
'jellyfinToken'
|
||||
]
|
||||
],
|
||||
'recent' => [
|
||||
'enabled' => [
|
||||
'homepageJellyfinEnabled',
|
||||
'homepageJellyfinRecent'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageJellyfinAuth',
|
||||
'homepageJellyfinRecentAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'jellyfinURL',
|
||||
'jellyfinToken'
|
||||
]
|
||||
],
|
||||
'metadata' => [
|
||||
'enabled' => [
|
||||
'homepageJellyfinEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageJellyfinAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'jellyfinURL',
|
||||
'jellyfinToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderjellyfinnowplaying()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->jellyfinHomepagePermissions('streams'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Now Playing...</h2></div>
|
||||
<script>
|
||||
// Jellyfin Stream
|
||||
homepageStream("jellyfin", "' . $this->config['homepageStreamRefresh'] . '");
|
||||
// End Jellyfin Stream
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function homepageOrderjellyfinrecent()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->jellyfinHomepagePermissions('recent'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Recent...</h2></div>
|
||||
<script>
|
||||
// Jellyfin Recent
|
||||
homepageRecent("jellyfin", "' . $this->config['homepageRecentRefresh'] . '");
|
||||
// End Jellyfin Recent
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getJellyfinHomepageStreams()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->jellyfinHomepagePermissions('streams'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['jellyfinURL']);
|
||||
$url = $url . '/Sessions?api_key=' . $this->config['jellyfinToken'] . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
|
||||
$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$jellyfin = json_decode($response->body, true);
|
||||
foreach ($jellyfin as $child) {
|
||||
if (isset($child['NowPlayingItem']) || isset($child['Name'])) {
|
||||
$items[] = $this->resolveJellyfinItem($child);
|
||||
}
|
||||
}
|
||||
$api['content'] = array_filter($items);
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Jellyfin')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getJellyfinHomepageRecent()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->jellyfinHomepagePermissions('recent'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['jellyfinURL']);
|
||||
$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
|
||||
$username = false;
|
||||
$showPlayed = false;
|
||||
$userId = 0;
|
||||
try {
|
||||
if (isset($this->user['username'])) {
|
||||
$username = strtolower($this->user['username']);
|
||||
}
|
||||
// Get A User
|
||||
$userIds = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
|
||||
$response = Requests::get($userIds, [], $options);
|
||||
if ($response->success) {
|
||||
$jellyfin = json_decode($response->body, true);
|
||||
foreach ($jellyfin as $value) { // Scan for admin user
|
||||
if (isset($value['Policy']) && isset($value['Policy']['IsAdministrator']) && $value['Policy']['IsAdministrator']) {
|
||||
$userId = $value['Id'];
|
||||
}
|
||||
if ($username && strtolower($value['Name']) == $username) {
|
||||
$userId = $value['Id'];
|
||||
$showPlayed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$url = $url . '/Users/' . $userId . '/Items/Latest?EnableImages=true&Limit=' . $this->config['homepageRecentLimit'] . '&api_key=' . $this->config['jellyfinToken'] . ($showPlayed ? '' : '&IsPlayed=false') . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$jellyfin = json_decode($response->body, true);
|
||||
foreach ($jellyfin as $child) {
|
||||
if (isset($child['NowPlayingItem']) || isset($child['Name'])) {
|
||||
$items[] = $this->resolveJellyfinItem($child);
|
||||
}
|
||||
}
|
||||
$api['content'] = array_filter($items);
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('JellyFin')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getJellyfinHomepageMetadata($array)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->jellyfinHomepagePermissions('metadata'), true)) {
|
||||
return false;
|
||||
}
|
||||
$key = $array['key'] ?? null;
|
||||
if (!$key) {
|
||||
$this->setAPIResponse('error', 'Jellyfin Metadata key is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['jellyfinURL']);
|
||||
$options = $this->requestOptions($url, 60, $this->config['jellyfinDisableCertCheck'], $this->config['jellyfinUseCustomCertificate']);
|
||||
$username = false;
|
||||
$showPlayed = false;
|
||||
$userId = 0;
|
||||
try {
|
||||
if (isset($this->user['username'])) {
|
||||
$username = strtolower($this->user['username']);
|
||||
}
|
||||
// Get A User
|
||||
$userIds = $url . "/Users?api_key=" . $this->config['jellyfinToken'];
|
||||
$response = Requests::get($userIds, [], $options);
|
||||
if ($response->success) {
|
||||
$jellyfin = json_decode($response->body, true);
|
||||
foreach ($jellyfin as $value) { // Scan for admin user
|
||||
if (isset($value['Policy']) && isset($value['Policy']['IsAdministrator']) && $value['Policy']['IsAdministrator']) {
|
||||
$userId = $value['Id'];
|
||||
}
|
||||
if ($username && strtolower($value['Name']) == $username) {
|
||||
$userId = $value['Id'];
|
||||
$showPlayed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$url = $url . '/Users/' . $userId . '/Items/' . $key . '?EnableImages=true&Limit=' . $this->config['homepageRecentLimit'] . '&api_key=' . $this->config['jellyfinToken'] . ($showPlayed ? '' : '&IsPlayed=false') . '&Fields=Overview,People,Genres,CriticRating,Studios,Taglines';
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$jellyfin = json_decode($response->body, true);
|
||||
if (isset($jellyfin['NowPlayingItem']) || isset($jellyfin['Name'])) {
|
||||
$items[] = $this->resolveJellyfinItem($jellyfin);
|
||||
}
|
||||
$api['content'] = array_filter($items);
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Jellyfin Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('JellyFin')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function resolveJellyfinItem($itemDetails)
|
||||
{
|
||||
$item = isset($itemDetails['NowPlayingItem']['Id']) ? $itemDetails['NowPlayingItem'] : $itemDetails;
|
||||
// Static Height & Width
|
||||
$height = $this->getCacheImageSize('h');
|
||||
$width = $this->getCacheImageSize('w');
|
||||
$nowPlayingHeight = $this->getCacheImageSize('nph');
|
||||
$nowPlayingWidth = $this->getCacheImageSize('npw');
|
||||
$actorHeight = 450;
|
||||
$actorWidth = 300;
|
||||
// Cache Directories
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$cacheDirectoryWeb = 'data/cache/';
|
||||
// Types
|
||||
switch (@$item['Type']) {
|
||||
case 'Series':
|
||||
$jellyfinItem['type'] = 'tv';
|
||||
$jellyfinItem['title'] = $item['Name'];
|
||||
$jellyfinItem['secondaryTitle'] = '';
|
||||
$jellyfinItem['summary'] = '';
|
||||
$jellyfinItem['ratingKey'] = $item['Id'];
|
||||
$jellyfinItem['thumb'] = $item['Id'];
|
||||
$jellyfinItem['key'] = $item['Id'] . "-list";
|
||||
$jellyfinItem['nowPlayingThumb'] = $item['Id'];
|
||||
$jellyfinItem['nowPlayingKey'] = $item['Id'] . "-np";
|
||||
$jellyfinItem['metadataKey'] = $item['Id'];
|
||||
$jellyfinItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? 'Thumb' : (isset($item['BackdropImageTags'][0]) ? 'Backdrop' : '');
|
||||
break;
|
||||
case 'Episode':
|
||||
$jellyfinItem['type'] = 'tv';
|
||||
$jellyfinItem['title'] = $item['SeriesName'];
|
||||
$jellyfinItem['secondaryTitle'] = '';
|
||||
$jellyfinItem['summary'] = '';
|
||||
$jellyfinItem['ratingKey'] = $item['Id'];
|
||||
$jellyfinItem['thumb'] = (isset($item['SeriesId']) ? $item['SeriesId'] : $item['Id']);
|
||||
$jellyfinItem['key'] = (isset($item['SeriesId']) ? $item['SeriesId'] : $item['Id']) . "-list";
|
||||
$jellyfinItem['nowPlayingThumb'] = isset($item['ParentThumbItemId']) ? $item['ParentThumbItemId'] : (isset($item['ParentBackdropItemId']) ? $item['ParentBackdropItemId'] : false);
|
||||
$jellyfinItem['nowPlayingKey'] = isset($item['ParentThumbItemId']) ? $item['ParentThumbItemId'] . '-np' : (isset($item['ParentBackdropItemId']) ? $item['ParentBackdropItemId'] . '-np' : false);
|
||||
$jellyfinItem['metadataKey'] = $item['Id'];
|
||||
$jellyfinItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? 'Thumb' : (isset($item['ParentBackdropImageTags'][0]) ? 'Backdrop' : '');
|
||||
$jellyfinItem['nowPlayingTitle'] = @$item['SeriesName'] . ' - ' . @$item['Name'];
|
||||
$jellyfinItem['nowPlayingBottom'] = 'S' . @$item['ParentIndexNumber'] . ' · E' . @$item['IndexNumber'];
|
||||
break;
|
||||
case 'MusicAlbum':
|
||||
case 'Audio':
|
||||
$jellyfinItem['type'] = 'music';
|
||||
$jellyfinItem['title'] = $item['Name'];
|
||||
$jellyfinItem['secondaryTitle'] = '';
|
||||
$jellyfinItem['summary'] = '';
|
||||
$jellyfinItem['ratingKey'] = $item['Id'];
|
||||
$jellyfinItem['thumb'] = $item['Id'];
|
||||
$jellyfinItem['key'] = $item['Id'] . "-list";
|
||||
$jellyfinItem['nowPlayingThumb'] = (isset($item['AlbumId']) ? $item['AlbumId'] : @$item['ParentBackdropItemId']);
|
||||
$jellyfinItem['nowPlayingKey'] = $item['Id'] . "-np";
|
||||
$jellyfinItem['metadataKey'] = isset($item['AlbumId']) ? $item['AlbumId'] : $item['Id'];
|
||||
$jellyfinItem['nowPlayingImageType'] = (isset($item['ParentBackdropItemId']) ? "Primary" : "Backdrop");
|
||||
$jellyfinItem['nowPlayingTitle'] = @$item['AlbumArtist'] . ' - ' . @$item['Name'];
|
||||
$jellyfinItem['nowPlayingBottom'] = @$item['Album'];
|
||||
break;
|
||||
case 'Movie':
|
||||
$jellyfinItem['type'] = 'movie';
|
||||
$jellyfinItem['title'] = $item['Name'];
|
||||
$jellyfinItem['secondaryTitle'] = '';
|
||||
$jellyfinItem['summary'] = '';
|
||||
$jellyfinItem['ratingKey'] = $item['Id'];
|
||||
$jellyfinItem['thumb'] = $item['Id'];
|
||||
$jellyfinItem['key'] = $item['Id'] . "-list";
|
||||
$jellyfinItem['nowPlayingThumb'] = $item['Id'];
|
||||
$jellyfinItem['nowPlayingKey'] = $item['Id'] . "-np";
|
||||
$jellyfinItem['metadataKey'] = $item['Id'];
|
||||
$jellyfinItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? "Thumb" : (isset($item['BackdropImageTags']) ? "Backdrop" : false);
|
||||
$jellyfinItem['nowPlayingTitle'] = @$item['Name'];
|
||||
$jellyfinItem['nowPlayingBottom'] = @$item['ProductionYear'];
|
||||
break;
|
||||
case 'Video':
|
||||
$jellyfinItem['type'] = 'video';
|
||||
$jellyfinItem['title'] = $item['Name'];
|
||||
$jellyfinItem['secondaryTitle'] = '';
|
||||
$jellyfinItem['summary'] = '';
|
||||
$jellyfinItem['ratingKey'] = $item['Id'];
|
||||
$jellyfinItem['thumb'] = $item['Id'];
|
||||
$jellyfinItem['key'] = $item['Id'] . "-list";
|
||||
$jellyfinItem['nowPlayingThumb'] = $item['Id'];
|
||||
$jellyfinItem['nowPlayingKey'] = $item['Id'] . "-np";
|
||||
$jellyfinItem['metadataKey'] = $item['Id'];
|
||||
$jellyfinItem['nowPlayingImageType'] = isset($item['ImageTags']['Thumb']) ? "Thumb" : (isset($item['BackdropImageTags']) ? "Backdrop" : false);
|
||||
$jellyfinItem['nowPlayingTitle'] = @$item['Name'];
|
||||
$jellyfinItem['nowPlayingBottom'] = @$item['ProductionYear'];
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
$jellyfinItem['uid'] = $item['Id'];
|
||||
$jellyfinItem['imageType'] = (isset($item['ImageTags']['Primary']) ? "Primary" : false);
|
||||
$jellyfinItem['elapsed'] = isset($itemDetails['PlayState']['PositionTicks']) && $itemDetails['PlayState']['PositionTicks'] !== '0' ? (int)$itemDetails['PlayState']['PositionTicks'] : null;
|
||||
$jellyfinItem['duration'] = isset($itemDetails['NowPlayingItem']['RunTimeTicks']) ? (int)$itemDetails['NowPlayingItem']['RunTimeTicks'] : (int)(isset($item['RunTimeTicks']) ? $item['RunTimeTicks'] : '');
|
||||
$jellyfinItem['watched'] = ($jellyfinItem['elapsed'] && $jellyfinItem['duration'] ? floor(($jellyfinItem['elapsed'] / $jellyfinItem['duration']) * 100) : 0);
|
||||
$jellyfinItem['transcoded'] = isset($itemDetails['TranscodingInfo']['CompletionPercentage']) ? floor((int)$itemDetails['TranscodingInfo']['CompletionPercentage']) : 100;
|
||||
$jellyfinItem['stream'] = @$itemDetails['PlayState']['PlayMethod'];
|
||||
$jellyfinItem['id'] = $item['ServerId'];
|
||||
$jellyfinItem['session'] = @$itemDetails['DeviceId'];
|
||||
$jellyfinItem['bandwidth'] = isset($itemDetails['TranscodingInfo']['Bitrate']) ? $itemDetails['TranscodingInfo']['Bitrate'] / 1000 : '';
|
||||
$jellyfinItem['bandwidthType'] = 'wan';
|
||||
$jellyfinItem['sessionType'] = (@$itemDetails['PlayState']['PlayMethod'] == 'Transcode') ? 'Transcoding' : 'Direct Playing';
|
||||
$jellyfinItem['state'] = ((@(string)$itemDetails['PlayState']['IsPaused'] == '1') ? "pause" : "play");
|
||||
$jellyfinItem['user'] = ($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) ? @(string)$itemDetails['UserName'] : "";
|
||||
$jellyfinItem['userThumb'] = '';
|
||||
$jellyfinItem['userAddress'] = (isset($itemDetails['RemoteEndPoint']) ? $itemDetails['RemoteEndPoint'] : "x.x.x.x");
|
||||
$jellfinVariablesForLink = [
|
||||
'{id}' => $jellyfinItem['uid'],
|
||||
'{serverId}' => $jellyfinItem['id']
|
||||
];
|
||||
$jellyfinItem['address'] = $this->userDefinedIdReplacementLink($this->config['homepageJellyfinLink'], $jellfinVariablesForLink);
|
||||
$jellyfinItem['nowPlayingOriginalImage'] = 'api/v2/homepage/image?source=jellyfin&type=' . $jellyfinItem['nowPlayingImageType'] . '&img=' . $jellyfinItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $jellyfinItem['nowPlayingKey'] . '$' . $this->randString();
|
||||
$jellyfinItem['originalImage'] = 'api/v2/homepage/image?source=jellyfin&type=' . $jellyfinItem['imageType'] . '&img=' . $jellyfinItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $jellyfinItem['key'] . '$' . $this->randString();
|
||||
$jellyfinItem['openTab'] = (bool)$this->config['jellyfinTabName'];
|
||||
$jellyfinItem['tabName'] = $this->config['jellyfinTabName'] ?: '';
|
||||
// Stream info
|
||||
$jellyfinItem['userStream'] = array(
|
||||
'platform' => @(string)$itemDetails['Client'],
|
||||
'product' => @(string)$itemDetails['Client'],
|
||||
'device' => @(string)$itemDetails['DeviceName'],
|
||||
'stream' => @$itemDetails['PlayState']['PlayMethod'],
|
||||
'videoResolution' => isset($itemDetails['NowPlayingItem']['MediaStreams'][0]['Width']) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Width'] : '',
|
||||
'throttled' => false,
|
||||
'sourceVideoCodec' => isset($itemDetails['NowPlayingItem']['MediaStreams'][0]) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Codec'] : '',
|
||||
'videoCodec' => @$itemDetails['TranscodingInfo']['VideoCodec'],
|
||||
'audioCodec' => @$itemDetails['TranscodingInfo']['AudioCodec'],
|
||||
'sourceAudioCodec' => isset($itemDetails['NowPlayingItem']['MediaStreams'][1]) ? $itemDetails['NowPlayingItem']['MediaStreams'][1]['Codec'] : (isset($itemDetails['NowPlayingItem']['MediaStreams'][0]) ? $itemDetails['NowPlayingItem']['MediaStreams'][0]['Codec'] : ''),
|
||||
'videoDecision' => $this->streamType(@$itemDetails['PlayState']['PlayMethod']),
|
||||
'audioDecision' => $this->streamType(@$itemDetails['PlayState']['PlayMethod']),
|
||||
'container' => isset($itemDetails['NowPlayingItem']['Container']) ? $itemDetails['NowPlayingItem']['Container'] : '',
|
||||
'audioChannels' => @$itemDetails['TranscodingInfo']['AudioChannels']
|
||||
);
|
||||
// Genre catch all
|
||||
if (isset($item['Genres'])) {
|
||||
$genres = array();
|
||||
foreach ($item['Genres'] as $genre) {
|
||||
$genres[] = $genre;
|
||||
}
|
||||
}
|
||||
// Actor catch all
|
||||
if (isset($item['People'])) {
|
||||
$actors = array();
|
||||
foreach ($item['People'] as $key => $value) {
|
||||
if (@$value['PrimaryImageTag'] && @$value['Role']) {
|
||||
if (file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg')) {
|
||||
$actorImage = $cacheDirectoryWeb . (string)$value['Id'] . '-cast.jpg';
|
||||
}
|
||||
if (file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg') && (time() - 604800) > filemtime($cacheDirectory . (string)$value['Id'] . '-cast.jpg') || !file_exists($cacheDirectory . (string)$value['Id'] . '-cast.jpg')) {
|
||||
$actorImage = 'api/v2/homepage/image?source=jellyfin&type=Primary&img=' . (string)$value['Id'] . '&height=' . $actorHeight . '&width=' . $actorWidth . '&key=' . (string)$value['Id'] . '-cast';
|
||||
}
|
||||
$actors[] = array(
|
||||
'name' => (string)$value['Name'],
|
||||
'role' => (string)$value['Role'],
|
||||
'thumb' => $actorImage
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Metadata information
|
||||
$jellyfinItem['metadata'] = array(
|
||||
'guid' => $item['Id'],
|
||||
'summary' => @(string)$item['Overview'],
|
||||
'rating' => @(string)$item['CommunityRating'],
|
||||
'duration' => @(string)$item['RunTimeTicks'],
|
||||
'originallyAvailableAt' => @(string)$item['PremiereDate'],
|
||||
'year' => (string)isset($item['ProductionYear']) ? $item['ProductionYear'] : '',
|
||||
//'studio' => (string)$item['studio'],
|
||||
'tagline' => @(string)$item['Taglines'][0],
|
||||
'genres' => (isset($item['Genres'])) ? $genres : '',
|
||||
'actors' => (isset($item['People'])) ? $actors : ''
|
||||
);
|
||||
if (file_exists($cacheDirectory . $jellyfinItem['nowPlayingKey'] . '.jpg')) {
|
||||
$jellyfinItem['nowPlayingImageURL'] = $cacheDirectoryWeb . $jellyfinItem['nowPlayingKey'] . '.jpg';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $jellyfinItem['key'] . '.jpg')) {
|
||||
$jellyfinItem['imageURL'] = $cacheDirectoryWeb . $jellyfinItem['key'] . '.jpg';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $jellyfinItem['nowPlayingKey'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $jellyfinItem['nowPlayingKey'] . '.jpg') || !file_exists($cacheDirectory . $jellyfinItem['nowPlayingKey'] . '.jpg')) {
|
||||
$jellyfinItem['nowPlayingImageURL'] = 'api/v2/homepage/image?source=jellyfin&type=' . $jellyfinItem['nowPlayingImageType'] . '&img=' . $jellyfinItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $jellyfinItem['nowPlayingKey'] . '';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $jellyfinItem['key'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $jellyfinItem['key'] . '.jpg') || !file_exists($cacheDirectory . $jellyfinItem['key'] . '.jpg')) {
|
||||
$jellyfinItem['imageURL'] = 'api/v2/homepage/image?source=jellyfin&type=' . $jellyfinItem['imageType'] . '&img=' . $jellyfinItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $jellyfinItem['key'] . '';
|
||||
}
|
||||
if (!$jellyfinItem['nowPlayingThumb']) {
|
||||
$jellyfinItem['nowPlayingOriginalImage'] = $jellyfinItem['nowPlayingImageURL'] = "plugins/images/homepage/no-np.png";
|
||||
$jellyfinItem['nowPlayingKey'] = "no-np";
|
||||
}
|
||||
if (!$jellyfinItem['thumb']) {
|
||||
$jellyfinItem['originalImage'] = $jellyfinItem['imageURL'] = "plugins/images/homepage/no-list.png";
|
||||
$jellyfinItem['key'] = "no-list";
|
||||
}
|
||||
if (isset($useImage)) {
|
||||
$jellyfinItem['useImage'] = $useImage;
|
||||
}
|
||||
return $jellyfinItem;
|
||||
}
|
||||
|
||||
}
|
||||
1881
docker/test/tvtracker/config/www/organizr/api/homepage/jellystat.php
Normal file
1881
docker/test/tvtracker/config/www/organizr/api/homepage/jellystat.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
trait LidarrHomepageItem
|
||||
{
|
||||
public function lidarrSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Lidarr',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/lidarr.png',
|
||||
'category' => 'PMR',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageLidarrEnabled'),
|
||||
$this->settingsOption('auth', 'homepageLidarrAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('multiple-url', 'lidarrURL'),
|
||||
$this->settingsOption('multiple-token', 'lidarrToken'),
|
||||
$this->settingsOption('disable-cert-check', 'lidarrDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'lidarrUseCustomCertificate'),
|
||||
],
|
||||
'API SOCKS' => [
|
||||
$this->settingsOption('socks', 'lidarr'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('enable', 'lidarrSocksEnabled'),
|
||||
$this->settingsOption('auth', 'lidarrSocksAuth'),
|
||||
],
|
||||
'Calendar' => [
|
||||
$this->settingsOption('calendar-start', 'calendarStart'),
|
||||
$this->settingsOption('calendar-end', 'calendarEnd'),
|
||||
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
|
||||
$this->settingsOption('calendar-default-view', 'calendarDefault'),
|
||||
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
|
||||
$this->settingsOption('calendar-locale', 'calendarLocale'),
|
||||
$this->settingsOption('calendar-limit', 'calendarLimit'),
|
||||
$this->settingsOption('refresh', 'calendarRefresh'),
|
||||
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
|
||||
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
|
||||
$this->settingsOption('enable', 'lidarrIcon', ['label' => 'Show Lidarr Icon']),
|
||||
$this->settingsOption('calendar-link-url', 'lidarrCalendarLink'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('calendar-frame-target', 'lidarrFrameTarget')
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'lidarr'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionLidarr()
|
||||
{
|
||||
if (empty($this->config['lidarrURL'])) {
|
||||
$this->setAPIResponse('error', 'Lidarr URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['lidarrToken'])) {
|
||||
$this->setAPIResponse('error', 'Lidarr Token is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$failed = false;
|
||||
$errors = '';
|
||||
$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null . null, $options);
|
||||
$results = $downloader->getRootFolder();
|
||||
$downloadList = json_decode($results, true);
|
||||
if (is_array($downloadList) || is_object($downloadList)) {
|
||||
$queue = (array_key_exists('error', $downloadList)) ? $downloadList['error']['msg'] : $downloadList;
|
||||
if (!is_array($queue)) {
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': ' . $queue;
|
||||
$failed = true;
|
||||
}
|
||||
} else {
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': Response was not JSON';
|
||||
$failed = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$failed = true;
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': ' . $e->getMessage();
|
||||
$this->setLoggerChannel('Lidarr')->error($e);
|
||||
}
|
||||
}
|
||||
if ($failed) {
|
||||
$this->setAPIResponse('error', $errors, 500);
|
||||
return false;
|
||||
} else {
|
||||
$this->setAPIResponse('success', null, 200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function lidarrHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'calendar' => [
|
||||
'enabled' => [
|
||||
'homepageLidarrEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageLidarrAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'lidarrURL',
|
||||
'lidarrToken'
|
||||
]
|
||||
],
|
||||
'queue' => [
|
||||
'enabled' => [
|
||||
'homepageLidarrEnabled',
|
||||
'homepageLidarrQueueEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageLidarrAuth',
|
||||
'homepageLidarrQueueAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'lidarrURL',
|
||||
'lidarrToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function getLidarrQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->lidarrHomepagePermissions('queue'), true)) {
|
||||
return false;
|
||||
}
|
||||
$queueItems = array();
|
||||
$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null, null, $options);
|
||||
$results = $downloader->getQueue();
|
||||
$downloadList = json_decode($results, true);
|
||||
if (is_array($downloadList) || is_object($downloadList)) {
|
||||
$queue = (array_key_exists('error', $downloadList)) ? '' : $downloadList;
|
||||
} else {
|
||||
$queue = '';
|
||||
}
|
||||
if (!empty($queue)) {
|
||||
$queueItems = array_merge($queueItems, $queue);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('Lidarr')->error($e);
|
||||
}
|
||||
}
|
||||
$api['content']['queueItems'] = $queueItems;
|
||||
$api['content']['historyItems'] = false;
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;;
|
||||
}
|
||||
|
||||
public function getLidarrCalendar($startDate = null, $endDate = null)
|
||||
{
|
||||
$startDate = ($startDate) ?? $_GET['start'] ?? date('Y-m-d', strtotime('-' . $this->config['calendarStart'] . ' days'));
|
||||
$endDate = ($endDate) ?? $_GET['end'] ?? date('Y-m-d', strtotime('+' . $this->config['calendarEnd'] . ' days'));
|
||||
if (!$this->homepageItemPermissions($this->lidarrHomepagePermissions('calendar'), true)) {
|
||||
return false;
|
||||
}
|
||||
if ($this->demo) {
|
||||
return $this->demoData('lidarr/calendar.json');
|
||||
}
|
||||
$calendarItems = array();
|
||||
$list = $this->csvHomepageUrlToken($this->config['lidarrURL'], $this->config['lidarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], null, $this->config['lidarrDisableCertCheck'], $this->config['lidarrUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'lidarr', null, null, $options);
|
||||
$results = $downloader->getCalendar($startDate, $endDate);
|
||||
$result = json_decode($results, true);
|
||||
if (is_array($result) || is_object($result)) {
|
||||
$calendar = (array_key_exists('error', $result)) ? '' : $this->formatLidarrCalendar($results, $key);
|
||||
} else {
|
||||
$calendar = '';
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('Lidarr')->error($e);
|
||||
}
|
||||
if (!empty($calendar)) {
|
||||
$calendarItems = array_merge($calendarItems, $calendar);
|
||||
}
|
||||
}
|
||||
$this->setAPIResponse('success', null, 200, $calendarItems);
|
||||
return $calendarItems;
|
||||
}
|
||||
|
||||
public function formatLidarrCalendar($array, $number)
|
||||
{
|
||||
$array = json_decode($array, true);
|
||||
$gotCalendar = array();
|
||||
$i = 0;
|
||||
foreach ($array as $child) {
|
||||
$i++;
|
||||
$albumName = $child['title'];
|
||||
$artistName = $child['artist']['artistName'];
|
||||
$albumID = '';
|
||||
$releaseDate = $child['releaseDate'];
|
||||
$releaseDate = strtotime($releaseDate);
|
||||
$releaseDate = date("Y-m-d H:i:s", $releaseDate);
|
||||
if (new DateTime() < new DateTime($releaseDate)) {
|
||||
$unaired = true;
|
||||
}
|
||||
if (isset($child['statistics']['percentOfTracks'])) {
|
||||
if ($child['statistics']['percentOfTracks'] == '100.0') {
|
||||
$downloaded = '1';
|
||||
} else {
|
||||
$downloaded = '0';
|
||||
}
|
||||
} else {
|
||||
$downloaded = '0';
|
||||
}
|
||||
if ($downloaded == "0" && isset($unaired)) {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
$fanart = "/plugins/images/homepage/no-np.png";
|
||||
foreach ($child['artist']['images'] as $image) {
|
||||
if ($image['coverType'] == "fanart") {
|
||||
$fanart = str_replace('http://', 'https://', $image['url']);
|
||||
}
|
||||
}
|
||||
$href = $this->config['lidarrCalendarLink'] ?? '';
|
||||
if (empty($href) && !empty($this->config['lidarrURL'])) {
|
||||
$href_arr = explode(',', $this->config['lidarrURL']);
|
||||
$href = reset($href_arr);
|
||||
}
|
||||
if (!empty($href)) {
|
||||
$href = $href . '/artist/' . $child['artist']['foreignArtistId'];
|
||||
$href = str_replace("//artist/", "/artist/", $href);
|
||||
}
|
||||
$details = array(
|
||||
"seasonCount" => '',
|
||||
"status" => '',
|
||||
"topTitle" => $albumName,
|
||||
"bottomTitle" => $artistName,
|
||||
"overview" => isset($child['artist']['overview']) ? $child['artist']['overview'] : '',
|
||||
"runtime" => '',
|
||||
"image" => $fanart,
|
||||
"ratings" => $child['artist']['ratings']['value'],
|
||||
"videoQuality" => "unknown",
|
||||
"audioChannels" => "unknown",
|
||||
"audioCodec" => "unknown",
|
||||
"videoCodec" => "unknown",
|
||||
"size" => "unknown",
|
||||
"genres" => $child['genres'],
|
||||
"href" => strtolower($href),
|
||||
"icon" => "/plugins/images/tabs/lidarr.png",
|
||||
"frame" => $this->config['lidarrFrameTarget'],
|
||||
"showLink" => $this->config['lidarrIcon']
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "Lidarr-" . $number . "-" . $i,
|
||||
"title" => $artistName,
|
||||
"start" => $child['releaseDate'],
|
||||
"className" => "inline-popups bg-calendar calendar-item musicID--",
|
||||
"imagetype" => "music " . $downloaded,
|
||||
"imagetypeFilter" => "music",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details,
|
||||
"data" => $child
|
||||
));
|
||||
}
|
||||
if ($i != 0) {
|
||||
return $gotCalendar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
trait MiscHomepageItem
|
||||
{
|
||||
public function miscSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Misc',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/organizr/logo-no-border.png',
|
||||
'category' => 'Custom',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'YouTube' => [
|
||||
$this->settingsOption('token', 'youtubeAPI', ['label' => 'Youtube API Key', 'help' => 'Please make sure to input this API key as the organizr one gets limited']),
|
||||
$this->settingsOption('html', null, ['override' => 6, 'label' => 'Instructions', 'html' => '<a href="https://www.slickremix.com/docs/get-api-key-for-youtube/" target="_blank">Click here for instructions</a>']),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
trait MonitorrHomepageItem
|
||||
{
|
||||
public function monitorrSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Monitorr',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/monitorr.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageMonitorrEnabled'),
|
||||
$this->settingsOption('auth', 'homepageMonitorrAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'monitorrURL', ['help' => 'URL for Monitorr. Please use the reverse proxy URL i.e. https://domain.com/monitorr/.', 'placeholder' => 'http://domain.com/monitorr/']),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('disable-cert-check', 'monitorrDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'monitorrUseCustomCertificate'),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('refresh', 'homepageMonitorrRefresh'),
|
||||
$this->settingsOption('switch', 'monitorrCompact', ['label' => 'Compact view', 'help' => 'Toggles the compact view of this homepage module']),
|
||||
$this->settingsOption('title', 'monitorrHeader'),
|
||||
$this->settingsOption('toggle-title', 'monitorrHeaderToggle'),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function monitorrHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageMonitorrEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageMonitorrAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'monitorrURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderMonitorr()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->monitorrHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Monitorr...</h2></div>
|
||||
<script>
|
||||
// Monitorr
|
||||
homepageMonitorr("' . $this->config['homepageMonitorrRefresh'] . '");
|
||||
// End Monitorr
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getMonitorrHomepageData()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->monitorrHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api = [];
|
||||
$url = $this->qualifyURL($this->config['monitorrURL']);
|
||||
$dataUrl = $url . '/assets/php/loop.php';
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['homepageMonitorrRefresh'], $this->config['monitorrDisableCertCheck'], $this->config['monitorrUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, ['Token' => $this->config['organizrAPI']], $options);
|
||||
if ($response->success) {
|
||||
$html = html_entity_decode($response->body);
|
||||
// This section grabs the names of all services by regex
|
||||
$services = [];
|
||||
$servicesMatch = [];
|
||||
$servicePattern = '/<div id="servicetitle(?:offline|nolink)?".*><div>(.*)<\/div><\/div><div class="(?:btnonline|btnoffline|btnunknown)".*>(Online|Offline|Unresponsive)<\/div>(:?<\/a>)?<\/div><\/div>/';
|
||||
preg_match_all($servicePattern, $html, $servicesMatch);
|
||||
$services = array_filter($servicesMatch[1]);
|
||||
$status = array_filter($servicesMatch[2]);
|
||||
$statuses = [];
|
||||
foreach ($services as $key => $service) {
|
||||
$match = $status[$key];
|
||||
$statuses[$service] = $match;
|
||||
if ($match == 'Online') {
|
||||
$statuses[$service] = [
|
||||
'status' => true
|
||||
];
|
||||
} else if ($match == 'Offline') {
|
||||
$statuses[$service] = [
|
||||
'status' => false
|
||||
];
|
||||
} else if ($match == 'Unresponsive') {
|
||||
$statuses[$service] = [
|
||||
'status' => 'unresponsive'
|
||||
];
|
||||
}
|
||||
$statuses[$service]['sort'] = $key;
|
||||
$imageMatch = [];
|
||||
$imgPattern = '/assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitle"><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg imgoffline" alt=.*><\/div><\/div><div id="servicetitleoffline".*><div>' . $service . '|assets\/img\/\.\.(.*)" class="serviceimg" alt=.*><\/div><\/div><div id="servicetitlenolink".*><div>' . $service . '/';
|
||||
preg_match($imgPattern, $html, $imageMatch);
|
||||
unset($imageMatch[0]);
|
||||
$imageMatch = array_values($imageMatch);
|
||||
// array_push($api['imagematches'][$service], $imageMatch);
|
||||
foreach ($imageMatch as $match) {
|
||||
if ($match !== '') {
|
||||
$image = $match;
|
||||
}
|
||||
}
|
||||
$ext = explode('.', $image);
|
||||
$ext = $ext[key(array_slice($ext, -1, 1, true))];
|
||||
$imageUrl = $url . '/assets' . $image;
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$img = Requests::get($imageUrl, ['Token' => $this->config['organizrAPI']], $options);
|
||||
if ($img->success) {
|
||||
$base64 = 'data:image/' . $ext . ';base64,' . base64_encode($img->body);
|
||||
$statuses[$service]['image'] = $base64;
|
||||
} else {
|
||||
$statuses[$service]['image'] = 'plugins/images/homepage/no-list.png';
|
||||
}
|
||||
$linkMatch = [];
|
||||
$linkPattern = '/<a class="servicetile" href="(.*)" target="_blank" style="display: block"><div id="serviceimg"><div><img id="' . strtolower($service) . '-service-img/';
|
||||
preg_match($linkPattern, $html, $linkMatch);
|
||||
$linkMatch = array_values($linkMatch);
|
||||
unset($linkMatch[0]);
|
||||
foreach ($linkMatch as $link) {
|
||||
if ($link !== '') {
|
||||
$statuses[$service]['link'] = $link;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($statuses as $status) {
|
||||
foreach ($status as $key => $value) {
|
||||
if (!isset($sortArray[$key])) {
|
||||
$sortArray[$key] = array();
|
||||
}
|
||||
$sortArray[$key][] = $value;
|
||||
}
|
||||
}
|
||||
array_multisort($sortArray['status'], SORT_ASC, $sortArray['sort'], SORT_ASC, $statuses);
|
||||
$api['services'] = $statuses;
|
||||
$api['options'] = [
|
||||
'title' => $this->config['monitorrHeader'],
|
||||
'titleToggle' => $this->config['monitorrHeaderToggle'],
|
||||
'compact' => $this->config['monitorrCompact'],
|
||||
];
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Monitorr')->error($e);
|
||||
$this->setAPIResponse('error', $e->getMessage(), 401);
|
||||
return false;
|
||||
};
|
||||
$api = isset($api) ? $api : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
<?php
|
||||
|
||||
trait NetDataHomepageItem
|
||||
{
|
||||
public function getNetdataHomepageData()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->netdataHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api = [];
|
||||
$api['data'] = [];
|
||||
$api['url'] = $this->config['netdataURL'];
|
||||
$url = $this->qualifyURL($this->config['netdataURL']);
|
||||
for ($i = 1; $i < 8; $i++) {
|
||||
if ($this->config['netdata' . ($i) . 'Enabled']) {
|
||||
switch ($this->config['netdata' . $i . 'Data']) {
|
||||
case 'disk-read':
|
||||
$data = $this->disk('in', $url);
|
||||
break;
|
||||
case 'disk-write':
|
||||
$data = $this->disk('out', $url);
|
||||
$data['value'] = (isset($data['value'])) ? abs($data['value']) : 0;
|
||||
$data['percent'] = (isset($data['percent'])) ? abs($data['percent']) : 0;
|
||||
break;
|
||||
case 'cpu':
|
||||
$data = $this->cpu($url);
|
||||
break;
|
||||
case 'net-in':
|
||||
$data = $this->net('received', $url);
|
||||
break;
|
||||
case 'net-out':
|
||||
$data = $this->net('sent', $url);
|
||||
$data['value'] = (isset($data['value'])) ? abs($data['value']) : 0;
|
||||
$data['percent'] = (isset($data['percent'])) ? abs($data['percent']) : 0;
|
||||
break;
|
||||
case 'ram-used':
|
||||
$data = $this->ram($url);
|
||||
break;
|
||||
case 'swap-used':
|
||||
$data = $this->swap($url);
|
||||
break;
|
||||
case 'disk-avail':
|
||||
$data = $this->diskSpace('avail', $url);
|
||||
break;
|
||||
case 'disk-used':
|
||||
$data = $this->diskSpace('used', $url);
|
||||
break;
|
||||
case 'ipmi-temp-c':
|
||||
$data = $this->ipmiTemp($url, 'c');
|
||||
break;
|
||||
case 'ipmi-temp-f':
|
||||
$data = $this->ipmiTemp($url, 'f');
|
||||
break;
|
||||
case 'cpu-temp-c':
|
||||
$data = $this->cpuTemp($url, 'c');
|
||||
break;
|
||||
case 'cpu-temp-f':
|
||||
$data = $this->cpuTemp($url, 'f');
|
||||
break;
|
||||
case 'custom':
|
||||
$data = $this->customNetdata($url, $i);
|
||||
break;
|
||||
default:
|
||||
$data = [
|
||||
'title' => 'DNC',
|
||||
'value' => 0,
|
||||
'units' => 'N/A',
|
||||
'max' => 100,
|
||||
];
|
||||
break;
|
||||
}
|
||||
$data['title'] = $this->config['netdata' . $i . 'Title'];
|
||||
$data['colour'] = $this->config['netdata' . $i . 'Colour'];
|
||||
$data['chart'] = $this->config['netdata' . $i . 'Chart'];
|
||||
$data['size'] = $this->config['netdata' . $i . 'Size'];
|
||||
$data['lg'] = $this->config['netdata' . ($i) . 'lg'];
|
||||
$data['md'] = $this->config['netdata' . ($i) . 'md'];
|
||||
$data['sm'] = $this->config['netdata' . ($i) . 'sm'];
|
||||
array_push($api['data'], $data);
|
||||
}
|
||||
}
|
||||
$api = $api ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function netdataSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Netdata',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/netdata.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageNetdataEnabled'),
|
||||
$this->settingsOption('auth', 'homepageNetdataAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'netdataURL'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('disable-cert-check', 'netdataDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'netdataUseCustomCertificate'),
|
||||
],
|
||||
]
|
||||
];
|
||||
for ($i = 1; $i <= 7; $i++) {
|
||||
$homepageSettings['settings']['Chart ' . $i] = array(
|
||||
$this->settingsOption('enable', 'netdata' . $i . 'Enabled'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('input', 'netdata' . $i . 'Title', ['label' => 'Title', 'help' => 'Title for the netdata graph']),
|
||||
$this->settingsOption('select', 'netdata' . $i . 'Data', ['label' => 'Data', 'options' => $this->netdataOptions()]),
|
||||
$this->settingsOption('select', 'netdata' . $i . 'Chart', ['label' => 'Chart', 'options' => $this->netdataChartOptions()]),
|
||||
$this->settingsOption('select', 'netdata' . $i . 'Colour', ['label' => 'Colour', 'options' => $this->netdataColourOptions()]),
|
||||
$this->settingsOption('select', 'netdata' . $i . 'Size', ['label' => 'Size', 'options' => $this->netdataSizeOptions()]),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('switch', 'netdata' . $i . 'lg', ['label' => 'Show on large screens']),
|
||||
$this->settingsOption('switch', 'netdata' . $i . 'md', ['label' => 'Show on medium screens']),
|
||||
$this->settingsOption('switch', 'netdata' . $i . 'sm', ['label' => 'Show on small screens']),
|
||||
);
|
||||
}
|
||||
$homepageSettings['settings']['Custom data'] = array(
|
||||
$this->settingsOption('html', null, ['label' => '', 'override' => 12, 'html' => '
|
||||
<div>
|
||||
<p>This is where you can define custom data sources for your netdata charts. To use a custom source, you need to select "Custom" in the data field for the chart.</p>
|
||||
<p>To define a custom data source, you need to add an entry to the JSON below, where the key is the chart number you want the custom data to be used for. Here is an example to set chart 1 custom data source to RAM percentage:</p>
|
||||
<pre>
|
||||
{
|
||||
"1": {
|
||||
"url": "/api/v1/data?chart=system.ram&format=array&points=540&group=average>ime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired",
|
||||
"value": "result,0",
|
||||
"units": "%",
|
||||
"max": 100
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
<p>The URL is appended to your netdata URL and returns JSON formatted data. The value field tells Organizr how to return the value you want from the netdata API. This should be formatted as comma-separated keys to access the desired value.</p>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Parameter</th>
|
||||
<th>Description</th>
|
||||
<th>Required</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>url</td>
|
||||
<td>Specifies the netdata API endpoint</td>
|
||||
<td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>value</td>
|
||||
<td>Specifies the selector used to get the data form the netdata response</td>
|
||||
<td><i class="fa fa-check text-success" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>units</td>
|
||||
<td>Specifies the units shown in the graph/chart. Defaults to %</td>
|
||||
<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>max</td>
|
||||
<td>Specifies the maximum possible value for the data. Defaults to 100</td>
|
||||
<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mutator</td>
|
||||
<td>Used to perform simple mathematical operations on the result (+, -, /, *). For example: dividing the result by 1000 would be "/1000". These operations can be chained together by putting them in a comma-seprated format.</td>
|
||||
<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>netdata</td>
|
||||
<td>Can be used to override the netdata instance data is retrieved from (in the format: http://IP:PORT)</td>
|
||||
<td><i class="fa fa-times text-danger" aria-hidden="true"></i></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>']),
|
||||
$this->settingsOption('html', 'netdataCustomTextAce', ['class' => 'jsonTextarea hidden', 'label' => 'Custom definitions', 'override' => 12, 'html' => '<div id="netdataCustomTextAce" style="height: 300px;">' . htmlentities($this->config['netdataCustom']) . '</div>']),
|
||||
$this->settingsOption('textbox', 'netdataCustom', ['class' => 'jsonTextarea hidden', 'id' => 'netdataCustomText', 'label' => '']),
|
||||
);
|
||||
$homepageSettings['settings']['Options'] = array(
|
||||
$this->settingsOption('refresh', 'homepageNetdataRefresh'),
|
||||
);
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function netdataHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageNetdataEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageNetdataAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'netdataURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderNetdata()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->netdataHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Netdata...</h2></div>
|
||||
<script>
|
||||
// Netdata
|
||||
homepageNetdata("' . $this->config['homepageNetdataRefresh'] . '");
|
||||
// End Netdata
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function disk($dimension, $url)
|
||||
{
|
||||
$data = [];
|
||||
// Get Data
|
||||
$dataUrl = $url . '/api/v1/data?chart=system.io&dimensions=' . $dimension . '&format=array&points=540&group=average>ime=0&options=absolute|jsonwrap|nonzero&after=-540';
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
$data['value'] = $json['latest_values'][0] / 1000;
|
||||
$data['percent'] = $this->getPercent($json['latest_values'][0], $json['max']);
|
||||
$data['units'] = 'MiB/s';
|
||||
$data['max'] = $json['max'];
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Netdata')->error($e);
|
||||
};
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function diskSpace($dimension, $url)
|
||||
{
|
||||
$data = [];
|
||||
// Get Data
|
||||
$dataUrl = $url . '/api/v1/data?chart=disk_space._&format=json&points=509&group=average>ime=0&options=ms|jsonwrap|nonzero&after=-540&dimension=' . $dimension;
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
$data['value'] = $json['result']['data'][0][1];
|
||||
$data['percent'] = $data['value'];
|
||||
$data['units'] = '%';
|
||||
$data['max'] = 100;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Netdata')->error($e);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function net($dimension, $url)
|
||||
{
|
||||
$data = [];
|
||||
// Get Data
|
||||
$dataUrl = $url . '/api/v1/data?chart=system.net&dimensions=' . $dimension . '&format=array&points=540&group=average>ime=0&options=absolute|jsonwrap|nonzero&after=-540';
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
$data['value'] = $json['latest_values'][0] / 1000;
|
||||
$data['percent'] = $this->getPercent($json['latest_values'][0], $json['max']);
|
||||
$data['units'] = 'Mbit/s';
|
||||
$data['max'] = $json['max'];
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Netdata')->error($e);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function cpu($url)
|
||||
{
|
||||
$data = [];
|
||||
$dataUrl = $url . '/api/v1/data?chart=system.cpu&format=array';
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
$data['value'] = $json[0];
|
||||
$data['percent'] = $data['value'];
|
||||
$data['max'] = 100;
|
||||
$data['units'] = '%';
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Netdata')->error($e);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function ram($url)
|
||||
{
|
||||
$data = [];
|
||||
$dataUrl = $url . '/api/v1/data?chart=system.ram&format=array&points=540&group=average>ime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used|buffers|active|wired';
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
$data['value'] = $json['result'][0];
|
||||
$data['percent'] = $data['value'];
|
||||
$data['max'] = 100;
|
||||
$data['units'] = '%';
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Netdata')->error($e);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function swap($url)
|
||||
{
|
||||
$data = [];
|
||||
$dataUrl = $url . '/api/v1/data?chart=system.swap&format=array&points=540&group=average>ime=0&options=absolute|percentage|jsonwrap|nonzero&after=-540&dimensions=used';
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
$data['value'] = $json['result'][0];
|
||||
$data['percent'] = $data['value'];
|
||||
$data['max'] = 100;
|
||||
$data['units'] = '%';
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Netdata')->error($e);
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getPercent($val, $max)
|
||||
{
|
||||
if ($max == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return ($val / $max) * 100;
|
||||
}
|
||||
}
|
||||
|
||||
public function customNetdata($url, $id)
|
||||
{
|
||||
try {
|
||||
$customs = json_decode($this->config['netdataCustom'], true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (Exception $e) {
|
||||
$customs = false;
|
||||
}
|
||||
if ($customs == false) {
|
||||
return [
|
||||
'error' => 'unable to parse custom JSON'
|
||||
];
|
||||
} else if (!isset($customs[$id])) {
|
||||
return [
|
||||
'error' => 'custom definition not found'
|
||||
];
|
||||
} else {
|
||||
$data = [];
|
||||
$custom = $customs[$id];
|
||||
if (isset($custom['url']) && isset($custom['value'])) {
|
||||
if (isset($custom['netdata']) && $custom['netdata'] != '') {
|
||||
$url = $this->qualifyURL($custom['netdata']);
|
||||
}
|
||||
$dataUrl = $url . '/' . $custom['url'];
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['homepageNetdataRefresh'], $this->config['netdataDisableCertCheck'], $this->config['netdataUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
if (!isset($custom['max']) || $custom['max'] == '') {
|
||||
$custom['max'] = 100;
|
||||
}
|
||||
$data['max'] = $custom['max'];
|
||||
if (!isset($custom['units']) || $custom['units'] == '') {
|
||||
$custom['units'] = '%';
|
||||
}
|
||||
$data['units'] = $custom['units'];
|
||||
$selectors = explode(',', $custom['value']);
|
||||
foreach ($selectors as $selector) {
|
||||
if (is_numeric($selector)) {
|
||||
$selector = (int)$selector;
|
||||
}
|
||||
if (!isset($data['value'])) {
|
||||
$data['value'] = $json[$selector];
|
||||
} else {
|
||||
$data['value'] = $data['value'][$selector];
|
||||
}
|
||||
}
|
||||
if (isset($custom['mutator'])) {
|
||||
$data['value'] = $this->parseMutators($data['value'], $custom['mutator']);
|
||||
}
|
||||
if ($data['max'] == 0) {
|
||||
$data['percent'] = 0;
|
||||
} else {
|
||||
$data['percent'] = ($data['value'] / $data['max']) * 100;
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Netdata')->error($e);
|
||||
}
|
||||
} else {
|
||||
$data['error'] = 'custom definition incomplete';
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
public function parseMutators($val, $mutators)
|
||||
{
|
||||
$mutators = explode(',', $mutators);
|
||||
foreach ($mutators as $m) {
|
||||
$op = $m[0];
|
||||
try {
|
||||
$m = (float)substr($m, 1);
|
||||
switch ($op) {
|
||||
case '+':
|
||||
$val = $val + $m;
|
||||
break;
|
||||
case '-':
|
||||
$val = $val - $m;
|
||||
break;
|
||||
case '/':
|
||||
$val = $val / $m;
|
||||
break;
|
||||
case '*':
|
||||
$val = $val * $m;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
trait NZBGetHomepageItem
|
||||
{
|
||||
public function nzbgetSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'NZBGet',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/nzbget.png',
|
||||
'category' => 'Downloader',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageNzbgetEnabled'),
|
||||
$this->settingsOption('auth', 'homepageNzbgetAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'nzbgetURL'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('username', 'nzbgetUsername'),
|
||||
$this->settingsOption('password', 'nzbgetPassword'),
|
||||
$this->settingsOption('disable-cert-check', 'nzbgetDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'nzbgetUseCustomCertificate'),
|
||||
],
|
||||
'API SOCKS' => [
|
||||
$this->settingsOption('socks', 'nzbget'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('enable', 'nzbgetSocksEnabled'),
|
||||
$this->settingsOption('auth', 'nzbgetSocksAuth'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('refresh', 'nzbgetRefresh'),
|
||||
$this->settingsOption('combine', 'nzbgetCombine'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'nzbget'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionNZBGet()
|
||||
{
|
||||
if (empty($this->config['nzbgetURL'])) {
|
||||
$this->setAPIResponse('error', 'NZBGet URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['nzbgetURL']);
|
||||
$options = $this->requestOptions($url, null, $this->config['nzbgetDisableCertCheck'], $this->config['nzbgetUseCustomCertificate']);
|
||||
$urlGroups = $url . '/jsonrpc/listgroups';
|
||||
if ($this->config['nzbgetUsername'] !== '' && $this->decrypt($this->config['nzbgetPassword']) !== '') {
|
||||
$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['nzbgetUsername'], $this->decrypt($this->config['nzbgetPassword']))));
|
||||
$options = array_merge($options, $credentials);
|
||||
}
|
||||
$response = Requests::get($urlGroups, array(), $options);
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('success', 'NZBGet: An Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('NZBGet')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function nzbgetHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageNzbgetEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageNzbgetAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'nzbgetURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrdernzbget()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->nzbgetHomepagePermissions('main'))) {
|
||||
$loadingBox = ($this->config['nzbgetCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['nzbgetCombine']) ? 'buildDownloaderCombined(\'nzbget\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("nzbget"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrdernzbget
|
||||
' . $builder . '
|
||||
homepageDownloader("nzbget", "' . $this->config['nzbgetRefresh'] . '");
|
||||
// End homepageOrdernzbget
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getNzbgetHomepageQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->nzbgetHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['nzbgetURL']);
|
||||
$options = $this->requestOptions($url, $this->config['nzbgetRefresh'], $this->config['nzbgetDisableCertCheck'], $this->config['nzbgetUseCustomCertificate']);
|
||||
$urlGroups = $url . '/jsonrpc/listgroups';
|
||||
$urlHistory = $url . '/jsonrpc/history';
|
||||
if ($this->config['nzbgetUsername'] !== '' && $this->decrypt($this->config['nzbgetPassword']) !== '') {
|
||||
$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['nzbgetUsername'], $this->decrypt($this->config['nzbgetPassword']))));
|
||||
$options = array_merge($options, $credentials);
|
||||
}
|
||||
$response = Requests::get($urlGroups, array(), $options);
|
||||
if ($response->success) {
|
||||
$api['content']['queueItems'] = json_decode($response->body, true);
|
||||
}
|
||||
$response = Requests::get($urlHistory, array(), $options);
|
||||
if ($response->success) {
|
||||
$api['content']['historyItems'] = json_decode($response->body, true);
|
||||
}
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('NZBGet')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
trait OctoPrintHomepageItem
|
||||
{
|
||||
public function octoprintSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Octoprint',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/octoprint.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageOctoprintEnabled'),
|
||||
$this->settingsOption('auth', 'homepageOctoprintAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'octoprintURL'),
|
||||
$this->settingsOption('token', 'octoprintToken'),
|
||||
$this->settingsOption('disable-cert-check', 'octoprintDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'octoprintUseCustomCertificate'),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('title', 'octoprintHeader'),
|
||||
$this->settingsOption('toggle-title', 'octoprintHeaderToggle'),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function octoprintHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageOctoprintEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageOctoprintAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'octoprintURL',
|
||||
'octoprintToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderOctoprint()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->octoprintHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading OctoPrint...</h2></div>
|
||||
<script>
|
||||
// Octoprint
|
||||
homepageOctoprint("' . $this->config['homepageOctoprintRefresh'] . '");
|
||||
// End Octoprint
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getOctoprintHomepageData()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->octoprintHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api = [];
|
||||
$url = $this->qualifyURL($this->config['octoprintURL']);
|
||||
$endpoints = ['job', 'settings'];
|
||||
$api['data']['url'] = $this->config['octoprintURL'];
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$dataUrl = $url . '/api/' . $endpoint;
|
||||
try {
|
||||
$headers = array('X-API-KEY' => $this->config['octoprintToken']);
|
||||
$options = $this->requestOptions($url, $this->config['homepageOctoprintRefresh'], $this->config['octoprintDisableCertCheck'], $this->config['octoprintUseCustomCertificate']);
|
||||
$response = Requests::get($dataUrl, $headers, $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
$api['data'][$endpoint] = $json;
|
||||
$api['options'] = [
|
||||
'title' => $this->config['octoprintHeader'],
|
||||
'titleToggle' => $this->config['octoprintHeaderToggle'],
|
||||
];
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'OctoPrint connection error', 409);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Octoprint')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
}
|
||||
$api = isset($api) ? $api : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
439
docker/test/tvtracker/config/www/organizr/api/homepage/ombi.php
Normal file
439
docker/test/tvtracker/config/www/organizr/api/homepage/ombi.php
Normal file
@@ -0,0 +1,439 @@
|
||||
<?php
|
||||
|
||||
trait OmbiHomepageItem
|
||||
{
|
||||
|
||||
public function ombiSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Ombi',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/ombi.png',
|
||||
'category' => 'Requests',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageOmbiEnabled'),
|
||||
$this->settingsOption('auth', 'homepageOmbiAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'ombiURL'),
|
||||
$this->settingsOption('token', 'ombiToken'),
|
||||
$this->settingsOption('username', 'ombiFallbackUser', ['label' => 'Ombi Fallback User', 'help' => 'Organizr will request an Ombi User Token based off of this user credentials']),
|
||||
$this->settingsOption('password', 'ombiFallbackPassword', ['label' => 'Ombi Fallback Password',]),
|
||||
$this->settingsOption('disable-cert-check', 'ombiDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'ombiUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('auth', 'homepageOmbiRequestAuth', ['label' => 'Minimum Group to Request']),
|
||||
$this->settingsOption('select', 'ombiTvDefault', ['label' => 'TV Show Default Request', 'options' => $this->requestTvOptions()]),
|
||||
$this->settingsOption('switch', 'ombiLimitUser', ['label' => 'Limit to User']),
|
||||
$this->settingsOption('limit', 'ombiLimit'),
|
||||
$this->settingsOption('refresh', 'ombiRefresh'),
|
||||
$this->settingsOption('switch', 'ombiAlias', ['label' => 'Use Ombi Alias Names', 'help' => 'Use Ombi Alias Names instead of Usernames - If Alias is blank, Alias will fallback to Username']),
|
||||
],
|
||||
'Default Filter' => [
|
||||
$this->settingsOption('switch', 'ombiDefaultFilterAvailable', ['label' => 'Show Available', 'help' => 'Show All Available Ombi Requests']),
|
||||
$this->settingsOption('switch', 'ombiDefaultFilterUnavailable', ['label' => 'Show Unavailable', 'help' => 'Show All Unavailable Ombi Requests']),
|
||||
$this->settingsOption('switch', 'ombiDefaultFilterApproved', ['label' => 'Show Approved', 'help' => 'Show All Approved Ombi Requests']),
|
||||
$this->settingsOption('switch', 'ombiDefaultFilterUnapproved', ['label' => 'Show Unapproved', 'help' => 'Show All Unapproved Ombi Requests']),
|
||||
$this->settingsOption('switch', 'ombiDefaultFilterDenied', ['label' => 'Show Denied', 'help' => 'Show All Denied Ombi Requests']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'ombi'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionOmbi()
|
||||
{
|
||||
if (empty($this->config['ombiURL'])) {
|
||||
$this->setAPIResponse('error', 'Ombi URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['ombiToken'])) {
|
||||
$this->setAPIResponse('error', 'Ombi Token is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Apikey" => $this->config['ombiToken'],
|
||||
);
|
||||
$url = $this->qualifyURL($this->config['ombiURL']);
|
||||
try {
|
||||
$options = $this->requestOptions($url, null, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
|
||||
$test = Requests::get($url . "/api/v1/Settings/about", $headers, $options);
|
||||
if ($test->success) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setResponse(401, $test->body);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Ombi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function ombiHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageOmbiEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageOmbiAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'ombiURL',
|
||||
'ombiToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderombi()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->ombiHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Requests...</h2></div>
|
||||
<script>
|
||||
// Ombi Requests
|
||||
homepageRequests("ombi", "' . $this->config['ombiRefresh'] . '");
|
||||
// End Ombi Requests
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getOmbiRequests($type = "both", $limit = 50, $offset = 0)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->ombiHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api['count'] = array(
|
||||
'movie' => 0,
|
||||
'tv' => 0,
|
||||
'limit' => (integer)$limit,
|
||||
'offset' => (integer)$offset
|
||||
);
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Apikey" => $this->config['ombiToken'],
|
||||
);
|
||||
$requests = array();
|
||||
$url = $this->qualifyURL($this->config['ombiURL']);
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['ombiRefresh'], $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
|
||||
switch ($type) {
|
||||
case 'movie':
|
||||
$movie = Requests::get($url . "/api/v1/Request/movie", $headers, $options);
|
||||
break;
|
||||
case 'tv':
|
||||
$tv = Requests::get($url . "/api/v1/Request/tv", $headers, $options);
|
||||
break;
|
||||
default:
|
||||
$movie = Requests::get($url . "/api/v1/Request/movie", $headers, $options);
|
||||
$tv = Requests::get($url . "/api/v1/Request/tv", $headers, $options);
|
||||
break;
|
||||
}
|
||||
if ($movie->success || $tv->success) {
|
||||
if (isset($movie)) {
|
||||
$movie = json_decode($movie->body, true);
|
||||
//$movie = array_reverse($movie);
|
||||
foreach ($movie as $key => $value) {
|
||||
$proceed = (($this->config['ombiLimitUser']) && strtolower($this->user['username']) == strtolower($value['requestedUser']['userName'])) || (strtolower($value['requestedUser']['userName']) == strtolower($this->config['ombiFallbackUser'])) || (!$this->config['ombiLimitUser']) || $this->qualifyRequest(1);
|
||||
if ($proceed) {
|
||||
$api['count']['movie']++;
|
||||
$requests[] = array(
|
||||
'id' => $value['theMovieDbId'],
|
||||
'title' => $value['title'],
|
||||
'overview' => $value['overview'],
|
||||
'poster' => (isset($value['posterPath']) && $value['posterPath'] !== '') ? 'https://image.tmdb.org/t/p/w300/' . $value['posterPath'] : 'plugins/images/homepage/no-list.png',
|
||||
'background' => (isset($value['background']) && $value['background'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $value['background'] : '',
|
||||
'approved' => $value['approved'],
|
||||
'available' => $value['available'],
|
||||
'denied' => $value['denied'],
|
||||
'deniedReason' => $value['deniedReason'],
|
||||
'user' => $value['requestedUser']['userName'],
|
||||
'userAlias' => $value['requestedUser']['userAlias'],
|
||||
'request_id' => $value['id'],
|
||||
'request_date' => $value['requestedDate'],
|
||||
'release_date' => $value['releaseDate'],
|
||||
'type' => 'movie',
|
||||
'icon' => 'mdi mdi-filmstrip',
|
||||
'color' => 'palette-Deep-Purple-900 bg white',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($tv) && (is_array($tv) || is_object($tv))) {
|
||||
$tv = json_decode($tv->body, true);
|
||||
foreach ($tv as $key => $value) {
|
||||
if (count($value['childRequests']) > 0) {
|
||||
$proceed = (($this->config['ombiLimitUser']) && strtolower($this->user['username']) == strtolower($value['childRequests'][0]['requestedUser']['userName'])) || (!$this->config['ombiLimitUser']) || $this->qualifyRequest(1);
|
||||
if ($proceed) {
|
||||
$api['count']['tv']++;
|
||||
$requests[] = array(
|
||||
'id' => $value['tvDbId'],
|
||||
'title' => $value['title'],
|
||||
'overview' => $value['overview'],
|
||||
'poster' => (isset($value['posterPath']) && $value['posterPath'] !== '') ? (str_starts_with($value['posterPath'], '/') ? 'https://image.tmdb.org/t/p/w300/' . $value['posterPath'] : $value['posterPath']) : 'plugins/images/homepage/no-list.png',
|
||||
'background' => (isset($value['background']) && $value['background'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $value['background'] : '',
|
||||
'approved' => $value['childRequests'][0]['approved'],
|
||||
'available' => $value['childRequests'][0]['available'],
|
||||
'denied' => $value['childRequests'][0]['denied'],
|
||||
'deniedReason' => $value['childRequests'][0]['deniedReason'],
|
||||
'user' => $value['childRequests'][0]['requestedUser']['userName'],
|
||||
'userAlias' => $value['childRequests'][0]['requestedUser']['userAlias'],
|
||||
'request_id' => $value['id'],
|
||||
'request_date' => $value['childRequests'][0]['requestedDate'],
|
||||
'release_date' => $value['releaseDate'],
|
||||
'type' => 'tv',
|
||||
'icon' => 'mdi mdi-television',
|
||||
'color' => 'grayish-blue-bg',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//sort here
|
||||
usort($requests, function ($item1, $item2) {
|
||||
if ($item1['request_date'] == $item2['request_date']) {
|
||||
return 0;
|
||||
}
|
||||
return $item1['request_date'] > $item2['request_date'] ? -1 : 1;
|
||||
});
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Ombi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($requests) ? array_slice($requests, $offset, $limit) : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function addOmbiRequest($id, $type)
|
||||
{
|
||||
$id = ($id) ?? null;
|
||||
$type = ($type) ?? null;
|
||||
if (!$id) {
|
||||
$this->setAPIResponse('error', 'Id was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$type) {
|
||||
$this->setAPIResponse('error', 'Type was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$this->homepageItemPermissions($this->ombiHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['ombiURL']);
|
||||
switch ($type) {
|
||||
case 'season':
|
||||
case 'tv':
|
||||
$type = 'tv';
|
||||
$add = array(
|
||||
'tvDbId' => $id,
|
||||
'requestAll' => $this->ombiTVDefault('all'),
|
||||
'latestSeason' => $this->ombiTVDefault('last'),
|
||||
'firstSeason' => $this->ombiTVDefault('first')
|
||||
);
|
||||
break;
|
||||
default:
|
||||
$type = 'movie';
|
||||
$add = array("theMovieDbId" => (int)$id);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
$options = array('timeout' => 30);
|
||||
if (isset($_COOKIE['Auth'])) {
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json",
|
||||
"Authorization" => "Bearer " . $_COOKIE['Auth']
|
||||
);
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'User does not have Auth Cookie', 500);
|
||||
return false;
|
||||
}
|
||||
//https://api.themoviedb.org/3/movie/157336?api_key=83cf4ee97bb728eeaf9d4a54e64356a1
|
||||
// Lets check if it exists inside Ombi first... but since I can't search with ID - i have to query title from id
|
||||
$tmdbResponse = Requests::get('https://api.themoviedb.org/3/' . $type . '/' . $id . '?api_key=83cf4ee97bb728eeaf9d4a54e64356a1', [], $options);
|
||||
if ($tmdbResponse->success) {
|
||||
$details = json_decode($tmdbResponse->body, true);
|
||||
if (count($details) > 0) {
|
||||
switch ($type) {
|
||||
case 'tv':
|
||||
$title = $details['name'];
|
||||
$idType = 'theTvDbId';
|
||||
$tmdbResponseID = Requests::get('https://api.themoviedb.org/3/tv/' . $id . '/external_ids?api_key=83cf4ee97bb728eeaf9d4a54e64356a1', [], $options);
|
||||
if ($tmdbResponseID->success) {
|
||||
$detailsID = json_decode($tmdbResponseID->body, true);
|
||||
if (count($detailsID) > 0) {
|
||||
if (isset($detailsID['tvdb_id'])) {
|
||||
$id = $detailsID['tvdb_id'];
|
||||
$add['tvDbId'] = $id;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Could not get TVDB Id', 422);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Could not get TVDB Id', 422);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'movie':
|
||||
$title = $details['title'];
|
||||
$idType = 'theMovieDbId';
|
||||
break;
|
||||
default:
|
||||
$this->setAPIResponse('error', 'Ombi Type was not found', 422);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'No data returned from TMDB', 422);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Could not contact TMDB', 422);
|
||||
return false;
|
||||
}
|
||||
$options = $this->requestOptions($url, null, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
|
||||
$searchResponse = Requests::get($url . '/api/v1/Search/' . $type . '/' . urlencode($title), $headers, $options);
|
||||
if ($searchResponse->success) {
|
||||
$details = json_decode($searchResponse->body, true);
|
||||
if (count($details) > 0) {
|
||||
foreach ($details as $k => $v) {
|
||||
if ($v[$idType] == $id) {
|
||||
if ($v['available']) {
|
||||
$this->setAPIResponse('error', 'Request is already available', 409);
|
||||
return false;
|
||||
} elseif ($v['requested']) {
|
||||
$this->setAPIResponse('error', 'Request is already requested', 409);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Ombi Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
$response = Requests::post($url . "/api/v1/Request/" . $type, $headers, json_encode($add), $options);
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', 'Ombi Request submitted', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Ombi Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Ombi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function actionOmbiRequest($id, $type, $action)
|
||||
{
|
||||
$id = ($id) ?? null;
|
||||
$type = ($type) ?? null;
|
||||
$action = ($action) ?? null;
|
||||
if (!$id) {
|
||||
$this->setAPIResponse('error', 'Id was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$type) {
|
||||
$this->setAPIResponse('error', 'Type was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$action) {
|
||||
$this->setAPIResponse('error', 'Action was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$this->homepageItemPermissions($this->ombiHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['ombiURL']);
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json",
|
||||
"Apikey" => $this->config['ombiToken']
|
||||
);
|
||||
$data = array(
|
||||
'id' => $id,
|
||||
);
|
||||
switch ($type) {
|
||||
case 'season':
|
||||
case 'tv':
|
||||
$type = 'tv';
|
||||
break;
|
||||
default:
|
||||
$type = 'movie';
|
||||
break;
|
||||
}
|
||||
try {
|
||||
$options = $this->requestOptions($url, 60, $this->config['ombiDisableCertCheck'], $this->config['ombiUseCustomCertificate']);
|
||||
switch ($action) {
|
||||
case 'approve':
|
||||
$response = Requests::post($url . "/api/v1/Request/" . $type . "/approve", $headers, json_encode($data), $options);
|
||||
$message = 'Ombi Request has been approved';
|
||||
break;
|
||||
case 'available':
|
||||
$response = Requests::post($url . "/api/v1/Request/" . $type . "/available", $headers, json_encode($data), $options);
|
||||
$message = 'Ombi Request has been marked available';
|
||||
break;
|
||||
case 'unavailable':
|
||||
$response = Requests::post($url . "/api/v1/Request/" . $type . "/unavailable", $headers, json_encode($data), $options);
|
||||
$message = 'Ombi Request has been marked unavailable';
|
||||
break;
|
||||
case 'deny':
|
||||
$response = Requests::put($url . "/api/v1/Request/" . $type . "/deny", $headers, json_encode($data), $options);
|
||||
$message = 'Ombi Request has been denied';
|
||||
break;
|
||||
case 'delete':
|
||||
$response = Requests::delete($url . "/api/v1/Request/" . $type . "/" . $id, $headers, $options);
|
||||
$message = 'Ombi Request has been deleted';
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', $message, 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Ombi Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Ombi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function ombiTVDefault($type)
|
||||
{
|
||||
return $type == $this->config['ombiTvDefault'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
<?php
|
||||
|
||||
trait OverseerrHomepageItem
|
||||
{
|
||||
|
||||
public function overseerrSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Overseerr',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/overseerr.png',
|
||||
'category' => 'Requests',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageOverseerrEnabled'),
|
||||
$this->settingsOption('auth', 'homepageOverseerrAuth'),
|
||||
$this->settingsOption('notice', '', ['title' => 'Attention', 'body' => 'Since Organizr supports multiple Request Providers, You must now select which service you want to submit requests through']),
|
||||
$this->settingsOption('select', 'defaultRequestService', ['label' => 'Default Request Service', 'options' => $this->requestServiceOptions()]),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'overseerrURL'),
|
||||
$this->settingsOption('token', 'overseerrToken'),
|
||||
$this->settingsOption('username', 'overseerrFallbackUser', ['label' => 'Overseerr Fallback User', 'help' => 'Organizr will request an Overseerr User Token based off of this user credentials']),
|
||||
$this->settingsOption('password', 'overseerrFallbackPassword', ['label' => 'Overseerr Fallback Password',]),
|
||||
$this->settingsOption('disable-cert-check', 'overseerrDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'overseerrUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('auth', 'homepageOverseerrRequestAuth', ['label' => 'Minimum Group to Request']),
|
||||
$this->settingsOption('select', 'overseerrTvDefault', ['label' => 'TV Show Default Request', 'options' => $this->requestTvOptions(true)]),
|
||||
$this->settingsOption('switch', 'overseerrLimitUser', ['label' => 'Limit to User']),
|
||||
$this->settingsOption('limit', 'overseerrLimit'),
|
||||
$this->settingsOption('switch', 'overseerrPrefer4K', ['label' => 'Prefer 4K Server']),
|
||||
$this->settingsOption('refresh', 'overseerrRefresh'),
|
||||
],
|
||||
'Default Filter' => [
|
||||
$this->settingsOption('switch', 'overseerrDefaultFilterAvailable', ['label' => 'Show Available', 'help' => 'Show All Available Overseerr Requests']),
|
||||
$this->settingsOption('switch', 'overseerrDefaultFilterUnavailable', ['label' => 'Show Unavailable', 'help' => 'Show All Unavailable Overseerr Requests']),
|
||||
$this->settingsOption('switch', 'overseerrDefaultFilterApproved', ['label' => 'Show Approved', 'help' => 'Show All Approved Overseerr Requests']),
|
||||
$this->settingsOption('switch', 'overseerrDefaultFilterUnapproved', ['label' => 'Show Unapproved', 'help' => 'Show All Unapproved Overseerr Requests']),
|
||||
$this->settingsOption('switch', 'overseerrDefaultFilterDenied', ['label' => 'Show Denied', 'help' => 'Show All Denied Overseerr Requests']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'overseerr'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionOverseerr()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('test'), true)) {
|
||||
return false;
|
||||
}
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"X-Api-Key" => $this->config['overseerrToken'],
|
||||
);
|
||||
$url = $this->qualifyURL($this->config['overseerrURL']);
|
||||
try {
|
||||
$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
|
||||
$test = Requests::get($url . "/api/v1/settings/main", $headers, $options);
|
||||
$testData = json_decode($test->body, true);
|
||||
if ($test->success && isset($testData["apiKey"]) && $testData["apiKey"] == $this->config['overseerrToken']) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setResponse(401, 'API Connection failed');
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Overseerr')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function overseerrHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageOverseerrEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageOverseerrAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'overseerrURL',
|
||||
'overseerrToken'
|
||||
]
|
||||
],
|
||||
'request' => [
|
||||
'enabled' => [
|
||||
'homepageOverseerrEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageOverseerrAuth',
|
||||
'homepageOverseerrRequestAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'overseerrURL',
|
||||
'overseerrToken'
|
||||
]
|
||||
],
|
||||
'test' => [
|
||||
'not_empty' => [
|
||||
'overseerrURL',
|
||||
'overseerrToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderoverseerr()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->overseerrHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Requests...</h2></div>
|
||||
<script>
|
||||
// Overseerr Requests
|
||||
homepageRequests("overseerr", "' . $this->config['overseerrRefresh'] . '");
|
||||
// End Overseerr Requests
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getOverseerrRequests($limit = 50, $offset = 0)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$limit = is_numeric($limit) ? (int)$limit : 50;
|
||||
$offset = is_numeric($offset) ? (int)$offset : 0;
|
||||
$api['count'] = [
|
||||
'movie' => 0,
|
||||
'tv' => 0,
|
||||
'limit' => $limit,
|
||||
'offset' => $offset
|
||||
];
|
||||
$headers = [
|
||||
"Accept" => "application/json",
|
||||
"X-Api-Key" => $this->config['overseerrToken'],
|
||||
];
|
||||
$requests = [];
|
||||
$url = $this->qualifyURL($this->config['overseerrURL']);
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['overseerrRefresh'], $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
|
||||
$request = Requests::get($url . "/api/v1/request?take=" . $limit . '&skip=' . $offset, $headers, $options);
|
||||
if ($request->success) {
|
||||
$requestAll = [];
|
||||
$requestsData = json_decode($request->body, true);
|
||||
foreach ($requestsData['results'] as $key => $value) {
|
||||
$requester = ($value['requestedBy']['username'] !== '' && $value['requestedBy']['username'] !== null) ? $value['requestedBy']['username'] : $value['requestedBy']['plexUsername'];
|
||||
$requesterEmail = $value['requestedBy']['email'];
|
||||
$proceed = (($this->config['overseerrLimitUser']) && strtolower($this->user['username']) == strtolower($requester)) || (strtolower($requester) == strtolower($this->config['overseerrFallbackUser'])) || (!$this->config['overseerrLimitUser']) || $this->qualifyRequest(1);
|
||||
if ($proceed) {
|
||||
$requestAll[$value['media']['tmdbId']] = [
|
||||
'url' => $url . '/api/v1/' . $value['type'] . '/' . $value['media']['tmdbId'],
|
||||
'headers' => $headers,
|
||||
'type' => Requests::GET,
|
||||
];
|
||||
$api['count'][$value['type']]++;
|
||||
$requests[$value['media']['tmdbId']] = [
|
||||
'id' => $value['media']['tmdbId'],
|
||||
'approved' => $value['status'] == 2,
|
||||
'available' => $value['media']['status'] == 5,
|
||||
'denied' => $value['status'] == 3,
|
||||
'deniedReason' => 'n/a',
|
||||
'user' => $requester,
|
||||
'userAlias' => $value['requestedBy']['displayName'],
|
||||
'request_id' => $value['id'],
|
||||
'request_date' => $value['createdAt'],
|
||||
'type' => $value['type'],
|
||||
'icon' => 'mdi mdi-' . ($value['type'] == 'movie') ? 'filmstrip' : 'television',
|
||||
'color' => ($value['type'] == 'movie') ? 'palette-Deep-Purple-900 bg white' : 'grayish-blue-bg',
|
||||
];
|
||||
/* OLD WAY
|
||||
$requestItem = Requests::get($url . '/api/v1/' . $value['type'] . '/' . $value['media']['tmdbId'], $headers, $options);
|
||||
$requestsItemData = json_decode($requestItem->body, true);
|
||||
if ($requestItem->success) {
|
||||
$api['count'][$value['type']]++;
|
||||
$requests[] = array(
|
||||
'id' => $value['media']['tmdbId'],
|
||||
'title' => ($value['type'] == 'movie') ? $requestsItemData['title'] : $requestsItemData['name'],
|
||||
'overview' => $requestsItemData['overview'],
|
||||
'poster' => (isset($requestsItemData['posterPath']) && $requestsItemData['posterPath'] !== '') ? 'https://image.tmdb.org/t/p/w300/' . $requestsItemData['posterPath'] : 'plugins/images/homepage/no-list.png',
|
||||
'background' => (isset($requestsItemData['backdropPath']) && $requestsItemData['backdropPath'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $requestsItemData['backdropPath'] : '',
|
||||
'approved' => $value['status'] == 2,
|
||||
'available' => $value['media']['status'] == 5,
|
||||
'denied' => $value['status'] == 3,
|
||||
'deniedReason' => 'n/a',
|
||||
'user' => $requester,
|
||||
'userAlias' => $value['requestedBy']['displayName'],
|
||||
'request_id' => $value['id'],
|
||||
'request_date' => $value['createdAt'],
|
||||
'release_date' => ($value['type'] == 'movie') ? $requestsItemData['releaseDate'] : $requestsItemData['firstAirDate'],
|
||||
'type' => $value['type'],
|
||||
'icon' => 'mdi mdi-' . ($value['type'] == 'movie') ? 'filmstrip' : 'television',
|
||||
'color' => ($value['type'] == 'movie') ? 'palette-Deep-Purple-900 bg white' : 'grayish-blue-bg',
|
||||
);
|
||||
}*/
|
||||
}
|
||||
}
|
||||
$requestItems = Requests::request_multiple($requestAll, $options);
|
||||
foreach ($requestItems as $key => $requestedItem) {
|
||||
if ($requestedItem->success) {
|
||||
$requestsItemData = json_decode($requestedItem->body, true);
|
||||
$requests[$key]['title'] = $requestsItemData['title'] ?? $requestsItemData['name'];
|
||||
$requests[$key]['release_date'] = $requestsItemData['releaseDate'] ?? $requestsItemData['firstAirDate'];
|
||||
$requests[$key]['background'] = (isset($requestsItemData['backdropPath']) && $requestsItemData['backdropPath'] !== '') ? 'https://image.tmdb.org/t/p/w1280/' . $requestsItemData['backdropPath'] : '';
|
||||
$requests[$key]['poster'] = (isset($requestsItemData['posterPath']) && $requestsItemData['posterPath'] !== '') ? 'https://image.tmdb.org/t/p/w300/' . $requestsItemData['posterPath'] : 'plugins/images/homepage/no-list.png';
|
||||
$requests[$key]['overview'] = $requestsItemData['overview'];
|
||||
} else {
|
||||
unset($requests[$key]);
|
||||
}
|
||||
}
|
||||
//sort here
|
||||
usort($requests, function ($item1, $item2) {
|
||||
if ($item1['request_date'] == $item2['request_date']) {
|
||||
return 0;
|
||||
}
|
||||
return $item1['request_date'] > $item2['request_date'] ? -1 : 1;
|
||||
});
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Overseerr')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
$api['content'] = $requests ?? false;
|
||||
$this->setResponse(200, null, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function getDefaultService($services)
|
||||
{
|
||||
if (empty($services)) {
|
||||
return null;
|
||||
} else {
|
||||
$default = false;
|
||||
foreach ($services as $key => $service) {
|
||||
if ($service['isDefault']) {
|
||||
if ($service['is4k']) {
|
||||
if ($this->config['overseerrPrefer4K']) {
|
||||
$default = (int)$key;
|
||||
}
|
||||
} else {
|
||||
if (!$this->config['overseerrPrefer4K']) {
|
||||
$default = (int)$key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ($default) ? $services[$default] : $services[0];
|
||||
}
|
||||
}
|
||||
|
||||
public function addOverseerrRequest($id, $type, $seasons = null)
|
||||
{
|
||||
$id = ($id) ?? null;
|
||||
$type = ($type) ?? null;
|
||||
if (!$id) {
|
||||
$this->setAPIResponse('error', 'Id was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$type) {
|
||||
$this->setAPIResponse('error', 'Type was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['overseerrURL']);
|
||||
try {
|
||||
if (!isset($_COOKIE['connect_sid']) && !isset($_COOKIE['connect.sid'])) {
|
||||
$this->setAPIResponse('error', 'User does not have Auth Cookie', 500);
|
||||
return false;
|
||||
}
|
||||
$headers = array(
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Api-Key' => $this->config['overseerrToken']
|
||||
);
|
||||
$cookieJar = new Requests_Cookie_Jar(['connect.sid' => $_COOKIE['connect_sid']]);
|
||||
$optionsUser = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate'], ['cookies' => $cookieJar]);
|
||||
$optionsAPI = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
|
||||
// Check if requested already
|
||||
$searchResponse = Requests::get($url . '/api/v1/request/', $headers, $optionsAPI);
|
||||
if ($searchResponse->success) {
|
||||
$details = json_decode($searchResponse->body, true);
|
||||
if (count($details['results']) > 0) {
|
||||
foreach ($details['results'] as $k => $v) {
|
||||
if ($v['media']['tmdbId'] == $id) {
|
||||
if ($v['media']['status'] == 5) {
|
||||
$this->setAPIResponse('error', 'Request is already available', 409);
|
||||
return false;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Request is already requested', 409);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Overseerr Connection Error Occurred', 500);
|
||||
return false;
|
||||
}
|
||||
// Get User info
|
||||
$response = Requests::get($url . '/api/v1/auth/me', [], $optionsUser);
|
||||
if ($response->success) {
|
||||
$userInfo = json_decode($response->body, true);
|
||||
} else {
|
||||
$this->setResponse(500, 'Error getting user information');
|
||||
return false;
|
||||
}
|
||||
switch ($type) {
|
||||
case 'season':
|
||||
case 'tv':
|
||||
if ($this->config['overseerrTvDefault'] == 'user') {
|
||||
if ($seasons) {
|
||||
if (strpos($seasons, ',') !== false) {
|
||||
$seasonsExplode = explode(',', $seasons);
|
||||
foreach ($seasonsExplode as $season) {
|
||||
$seasonsArray[] = (int)$season;
|
||||
}
|
||||
$seasons = $seasonsArray;
|
||||
} else {
|
||||
$seasonsArray[] = (int)$seasons;
|
||||
$seasons = $seasonsArray;
|
||||
}
|
||||
} else {
|
||||
$this->setResponse(500, 'Seasons requested was not supplied');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$response = Requests::get($url . '/api/v1/tv/' . $id, $headers, $optionsAPI);
|
||||
if ($response->success) {
|
||||
$seriesInfo = json_decode($response->body, true);
|
||||
} else {
|
||||
$this->setResponse(500, 'Error getting series information');
|
||||
return false;
|
||||
}
|
||||
if ($this->config['overseerrTvDefault'] == 'first') {
|
||||
$seasons = [1];
|
||||
} elseif ($this->config['overseerrTvDefault'] == 'last') {
|
||||
$lastSeason = end($seriesInfo['seasons']);
|
||||
$lastSeasonNumber = $lastSeason['seasonNumber'];
|
||||
$seasons = [$lastSeasonNumber];
|
||||
} elseif ($this->config['overseerrTvDefault'] == 'all') {
|
||||
$seasons = [];
|
||||
foreach ($seriesInfo['seasons'] as $season) {
|
||||
if ($season['seasonNumber'] !== 0) {
|
||||
$seasons[] = $season['seasonNumber'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$response = Requests::get($url . '/api/v1/service/sonarr', $headers, $optionsAPI);
|
||||
if ($response->success) {
|
||||
$serviceInfo = $this->getDefaultService(json_decode($response->body, true));
|
||||
if (!$serviceInfo) {
|
||||
$this->setResponse(404, 'No Sonarr service was found in Overseerr');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setResponse(500, 'Error getting service information');
|
||||
return false;
|
||||
}
|
||||
$add = [
|
||||
'mediaId' => (int)$id,
|
||||
'tvdbId' => $seriesInfo['externalIds']['tvdbId'],
|
||||
'mediaType' => 'tv',
|
||||
'is4k' => (bool)$serviceInfo['is4k'],
|
||||
'seasons' => $seasons,
|
||||
'serverId' => (int)$serviceInfo['id'],
|
||||
'profileId' => (int)$serviceInfo['activeProfileId'],
|
||||
'rootFolder' => $serviceInfo['activeDirectory'],
|
||||
'languageProfileId' => (int)$serviceInfo['activeLanguageProfileId'],
|
||||
//'userId' => (int)$userInfo['id'],
|
||||
'tags' => []
|
||||
];
|
||||
break;
|
||||
default:
|
||||
$response = Requests::get($url . '/api/v1/service/radarr', $headers, $optionsAPI);
|
||||
if ($response->success) {
|
||||
$serviceInfo = $this->getDefaultService(json_decode($response->body, true));
|
||||
if (!$serviceInfo) {
|
||||
$this->setResponse(404, 'No Radarr service was found in Overseerr');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setResponse(500, 'Error getting service information');
|
||||
return false;
|
||||
}
|
||||
$add = [
|
||||
'mediaId' => (int)$id,
|
||||
'mediaType' => 'movie',
|
||||
'is4k' => (bool)$serviceInfo['is4k'],
|
||||
'serverId' => (int)$serviceInfo['id'],
|
||||
'profileId' => (int)$serviceInfo['activeProfileId'],
|
||||
//'userId' => (int)$userInfo['id'],
|
||||
'tags' => []
|
||||
];
|
||||
break;
|
||||
}
|
||||
$response = Requests::post($url . "/api/v1/request", ['Accept' => 'application/json', 'Content-Type' => 'application/json'], json_encode($add), $optionsUser);
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', 'Overseerr Request submitted', 200);
|
||||
return true;
|
||||
} else {
|
||||
$message = 'Overseerr Error Occurred';
|
||||
if ($this->isJSON($response->body)) {
|
||||
$messageJSON = json_decode($response->body, true);
|
||||
$message = $messageJSON['message'] ?? $message;
|
||||
}
|
||||
$this->setAPIResponse('error', $message, 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Overseerr')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function actionOverseerrRequest($id, $type, $action)
|
||||
{
|
||||
$id = ($id) ?? null;
|
||||
$type = ($type) ?? null;
|
||||
$action = ($action) ?? null;
|
||||
if (!$id) {
|
||||
$this->setAPIResponse('error', 'Id was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$type) {
|
||||
$this->setAPIResponse('error', 'Type was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$action) {
|
||||
$this->setAPIResponse('error', 'Action was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['overseerrURL']);
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"Content-Type" => "application/json",
|
||||
'X-Api-Key' => $this->config['overseerrToken']
|
||||
);
|
||||
try {
|
||||
$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
|
||||
switch ($action) {
|
||||
case 'approve':
|
||||
$response = Requests::post($url . "/api/v1/request/" . $id . "/approve", $headers, [], $options);
|
||||
$message = 'Overseerr Request has been approved';
|
||||
break;
|
||||
case 'pending':
|
||||
$response = Requests::post($url . '/api/v1/request/' . $id . '/pending', $headers, [], $options);
|
||||
$message = 'Overseerr Request has been approved';
|
||||
break;
|
||||
case 'available':
|
||||
$requestInfoResponse = Requests::get($url . '/api/v1/request/' . $id, $headers, $options);
|
||||
if ($requestInfoResponse->success) {
|
||||
$requestInfo = json_decode($requestInfoResponse->body, true);
|
||||
$mediaId = $requestInfo['media']['id'];
|
||||
} else {
|
||||
$this->setResponse(500, 'Error getting request information');
|
||||
return false;
|
||||
}
|
||||
$response = Requests::post($url . '/api/v1/media/' . $mediaId . '/available', $headers, [], $options);
|
||||
$message = 'Overseerr Request has been marked available';
|
||||
break;
|
||||
case 'unavailable':
|
||||
$requestInfoResponse = Requests::get($url . '/api/v1/request/' . $id, $headers, $options);
|
||||
if ($requestInfoResponse->success) {
|
||||
$requestInfo = json_decode($requestInfoResponse->body, true);
|
||||
$mediaId = $requestInfo['media']['id'];
|
||||
} else {
|
||||
$this->setResponse(500, 'Error getting request information');
|
||||
return false;
|
||||
}
|
||||
$response = Requests::post($url . "/api/v1/media/" . $mediaId . "/pending", $headers, [], $options);
|
||||
$message = 'Overseerr Request has been marked unavailable';
|
||||
break;
|
||||
case 'deny':
|
||||
$response = Requests::post($url . "/api/v1/request/" . $id . "/decline", $headers, [], $options);
|
||||
$message = 'Overseerr Request has been denied';
|
||||
break;
|
||||
case 'delete':
|
||||
$response = Requests::delete($url . "/api/v1/request/" . $id, $headers, $options);
|
||||
$message = 'Overseerr Request has been deleted';
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', $message, 200);
|
||||
return true;
|
||||
} else {
|
||||
$message = 'Overseerr Error Occurred';
|
||||
if ($this->isJSON($response->body)) {
|
||||
$messageJSON = json_decode($response->body, true);
|
||||
$message = $messageJSON['message'] ?? $message;
|
||||
}
|
||||
$this->setAPIResponse('error', $message, 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Overseerr')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getOverseerrMetadata($id, $type)
|
||||
{
|
||||
if (!$id) {
|
||||
$this->setAPIResponse('error', 'Id was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$type) {
|
||||
$this->setAPIResponse('error', 'Type was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$this->homepageItemPermissions($this->overseerrHomepagePermissions('request'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$url = $this->qualifyURL($this->config['overseerrURL']);
|
||||
$headers = array(
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Api-Key' => $this->config['overseerrToken']
|
||||
);
|
||||
$options = $this->requestOptions($url, null, $this->config['overseerrDisableCertCheck'], $this->config['overseerrUseCustomCertificate']);
|
||||
$response = Requests::get($url . '/api/v1/' . $type . '/' . $id, $headers, $options);
|
||||
if ($response->success) {
|
||||
$metadata = json_decode($response->body, true);
|
||||
$this->setResponse(200, null, $metadata);
|
||||
return $metadata;
|
||||
} else {
|
||||
$this->setResponse(500, 'Error getting series information');
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Overseerr')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function overseerrTVDefault($type)
|
||||
{
|
||||
return $type == $this->config['overseerrTvDefault'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
trait PiHoleHomepageItem
|
||||
{
|
||||
public function piholeSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Pi-hole',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/pihole.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepagePiholeEnabled'),
|
||||
$this->settingsOption('auth', 'homepagePiholeAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('multiple-url', 'piholeURL', ['help' => 'Please make sure to use local IP address and port at the end of the URL. You can add multiple Pi-holes by comma separating the URLs.', 'placeholder' => 'http(s)://hostname:port/']),
|
||||
$this->settingsOption('multiple-token', 'piholeToken'),
|
||||
],
|
||||
'Misc' => [
|
||||
$this->settingsOption('toggle-title', 'piholeHeaderToggle'),
|
||||
$this->settingsOption('switch', 'homepagePiholeCombine', ['label' => 'Combine stat cards', 'help' => 'This controls whether to combine the stats for multiple pihole instances into 1 card.']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'pihole'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionPihole()
|
||||
{
|
||||
if (empty($this->config['piholeURL'])) {
|
||||
$this->setAPIResponse('error', 'Pihole URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$failed = false;
|
||||
$errors = '';
|
||||
$list = $this->csvHomepageUrlToken($this->config['piholeURL'], $this->config['piholeToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
$response = $this->getAuth($value['url'], $value['token']);
|
||||
$errors = $response["errors"];
|
||||
}
|
||||
if ($errors != '') {
|
||||
$this->setAPIResponse('error', $errors, 500);
|
||||
return false;
|
||||
} else {
|
||||
$this->setAPIResponse('success', null, 200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function piholeHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepagePiholeEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepagePiholeAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'piholeURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderPihole()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->piholeHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Pihole...</h2></div>
|
||||
<script>
|
||||
// Pi-hole Stats
|
||||
homepagePihole("' . $this->config['homepagePiholeRefresh'] . '");
|
||||
// End Pi-hole Stats
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getAuth($base_url, $token)
|
||||
{
|
||||
$sid = null;
|
||||
if ($token === '' || $token === null) {
|
||||
$errors .= $base_url . ': Missing API Token';
|
||||
$this->setAPIResponse('error', $errors, 500);
|
||||
return $errors;
|
||||
}
|
||||
try {
|
||||
$sid = $this->doRequest($base_url, "createAuth", [], ['password' => $token]);
|
||||
$this->cleanSessions($base_url, $sid);
|
||||
} catch (Requests_Exception $e) {
|
||||
$errors .= $ip . ': ' . $e->getMessage();
|
||||
$this->setLoggerChannel('PiHole')->error($e);
|
||||
};
|
||||
|
||||
return ["errors" => $errors, "sid" => $sid];
|
||||
}
|
||||
|
||||
public function cleanSessions($base_url, $sid)
|
||||
{
|
||||
$sessions = $this->doRequest($base_url, "getAuths", ["sid" => $sid]);
|
||||
foreach ($sessions as $session) {
|
||||
// Skip if not right user agent, skip if current session
|
||||
if ($session['user_agent'] != 'Organizr' || $session['current_session'] == '1') {
|
||||
continue;
|
||||
}
|
||||
$this->doRequest($base_url,"deleteAuth", ["sid" => $sid], $session['id']);
|
||||
}
|
||||
}
|
||||
|
||||
public function endpoints($endpoint)
|
||||
{
|
||||
return [
|
||||
"createAuth" => [
|
||||
"type" => "post",
|
||||
"urlHandler" => function() {
|
||||
return "auth";
|
||||
},
|
||||
"responseHandler" => function($payload) {
|
||||
return $payload['session']['sid'];
|
||||
},
|
||||
"payloadHandler" => function($data) {
|
||||
return json_encode($data);
|
||||
}
|
||||
],
|
||||
"getAuths" => [
|
||||
"type" => "get",
|
||||
"urlHandler" => function() {
|
||||
return "auth/sessions";
|
||||
},
|
||||
"responseHandler" => function($payload) {
|
||||
return $payload['sessions'];
|
||||
}
|
||||
],
|
||||
"deleteAuth" => [
|
||||
"type" => "delete",
|
||||
"urlHandler" => function($id) {
|
||||
return "auth/session/$id";
|
||||
},
|
||||
],
|
||||
"get24HourStatsSummary" => [
|
||||
"type" => "get",
|
||||
"urlHandler" => function() {
|
||||
$nowUnixTimestamp = time();
|
||||
$oneDayAgoUnixTimestamp = $nowUnixTimestamp - (24 * 60 * 60);
|
||||
return "stats/database/summary?from=$oneDayAgoUnixTimestamp&until=$nowUnixTimestamp";
|
||||
}
|
||||
],
|
||||
"get24HourBlockedDomains" => [
|
||||
"type" => "get",
|
||||
"urlHandler" => function() {
|
||||
$nowUnixTimestamp = time();
|
||||
$oneDayAgoUnixTimestamp = $nowUnixTimestamp - (24 * 60 * 60);
|
||||
return "stats/database/top_domains?from=$oneDayAgoUnixTimestamp&until=$nowUnixTimestamp&blocked=true&count=1000";
|
||||
},
|
||||
"responseHandler" => function($payload) {
|
||||
return array_map(
|
||||
function($item) {
|
||||
return $item['domain'];
|
||||
}, $payload['domains']
|
||||
);
|
||||
|
||||
}
|
||||
],
|
||||
][$endpoint];
|
||||
}
|
||||
|
||||
public function doRequest($baseUrl, $endpoint, $headers = [], $data = null)
|
||||
{
|
||||
$endpointDictionary = $this->endpoints($endpoint);
|
||||
$urlHandler = $endpointDictionary['urlHandler'];
|
||||
$payloadHandler = $endpointDictionary['payloadHandler'] ?? function() {
|
||||
return null;
|
||||
};
|
||||
$requestType = $endpointDictionary['type'];
|
||||
$responseHandler = $endpointDictionary['responseHandler'] ?? function($payload) {
|
||||
return $payload;
|
||||
};
|
||||
$url = $this->qualifyURL("$baseUrl/api/{$urlHandler($data)}");
|
||||
$headers = $headers + ["User-Agent" => 'Organizr'];
|
||||
try {
|
||||
$response = Requests::$requestType($url, $headers, $payloadHandler($data));
|
||||
|
||||
if ($response->success) {
|
||||
$processedResponse = $responseHandler($this->testAndFormatString($response->body)["data"]);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
$this->setLoggerChannel('PiHole')->error($e);
|
||||
};
|
||||
return $processedResponse ?? [];
|
||||
}
|
||||
|
||||
public function getPiholeHomepageStats()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->piholeHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api = [];
|
||||
$list = $this->csvHomepageUrlToken($this->config['piholeURL'], $this->config['piholeToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
$base_url = $value['url'];
|
||||
$sid = $this->getAuth($base_url, $value['token'])["sid"];
|
||||
$stats = $this->doRequest($base_url, "get24HourStatsSummary", ["sid" => $sid]);
|
||||
$stats["domains_being_blocked"] = $this->doRequest($base_url, "get24HourBlockedDomains", ["sid" => $sid]);
|
||||
$api['data'][$base_url] = $stats;
|
||||
}
|
||||
$api['options']['combine'] = $this->config['homepagePiholeCombine'];
|
||||
$api['options']['title'] = $this->config['piholeHeaderToggle'];
|
||||
$api = $api ?? null;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
739
docker/test/tvtracker/config/www/organizr/api/homepage/plex.php
Normal file
739
docker/test/tvtracker/config/www/organizr/api/homepage/plex.php
Normal file
@@ -0,0 +1,739 @@
|
||||
<?php
|
||||
|
||||
trait PlexHomepageItem
|
||||
{
|
||||
public function plexSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Plex',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/plex.png',
|
||||
'category' => 'Media Server',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$libraryList = [['name' => 'Refresh page to update List', 'value' => '', 'disabled' => true]];
|
||||
if ($this->config['plexID'] !== '' && $this->config['plexToken'] !== '') {
|
||||
$loop = $this->plexLibraryList('key');
|
||||
if ($loop) {
|
||||
$loop = $loop['libraries'];
|
||||
foreach ($loop as $key => $value) {
|
||||
$libraryList[] = ['name' => $key, 'value' => $value];
|
||||
}
|
||||
}
|
||||
}
|
||||
$homepageSettings = [
|
||||
'docs' => $this->docs('features/homepage/plex-homepage-item'),
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepagePlexEnabled'),
|
||||
$this->settingsOption('auth', 'homepagePlexAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'plexURL'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('disable-cert-check', 'plexDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'plexUseCustomCertificate'),
|
||||
$this->settingsOption('token', 'plexToken'),
|
||||
$this->settingsOption('button', '', ['label' => 'Get Plex Token', 'icon' => 'fa fa-ticket', 'text' => 'Retrieve', 'attr' => 'onclick="PlexOAuth(oAuthSuccess,oAuthError, oAuthMaxRetry, null, null, \'#homepage-Plex-form [name=plexToken]\')"']),
|
||||
$this->settingsOption('password-alt', 'plexID', ['label' => 'Plex Machine']),
|
||||
$this->settingsOption('button', '', ['label' => 'Get Plex Machine', 'icon' => 'fa fa-id-badge', 'text' => 'Retrieve', 'attr' => 'onclick="showPlexMachineForm(\'#homepage-Plex-form [name=plexID]\')"']),
|
||||
],
|
||||
'Active Streams' => [
|
||||
$this->settingsOption('enable', 'homepagePlexStreams'),
|
||||
$this->settingsOption('auth', 'homepagePlexStreamsAuth'),
|
||||
$this->settingsOption('switch', 'homepageShowStreamNames', ['label' => 'User Information', 'help' => 'Show user and IP information']),
|
||||
$this->settingsOption('auth', 'homepageShowStreamNamesAuth'),
|
||||
$this->settingsOption('switch', 'homepageShowStreamNamesWithoutIp', ['label' => 'User Information Without IP', 'help' => 'Only shows username and no IP information']),
|
||||
$this->settingsOption('auth', 'homepageShowStreamNamesWithoutIpAuth'),
|
||||
$this->settingsOption('refresh', 'homepageStreamRefresh'),
|
||||
$this->settingsOption('plex-library-exclude', 'homepagePlexStreamsExclude', ['options' => $libraryList]),
|
||||
],
|
||||
'Recent Items' => [
|
||||
$this->settingsOption('enable', 'homepagePlexRecent'),
|
||||
$this->settingsOption('auth', 'homepagePlexRecentAuth'),
|
||||
$this->settingsOption('plex-library-exclude', 'homepagePlexRecentExclude', ['options' => $libraryList]),
|
||||
$this->settingsOption('limit', 'homepageRecentLimit'),
|
||||
$this->settingsOption('refresh', 'homepageRecentRefresh'),
|
||||
],
|
||||
'Media Search' => [
|
||||
$this->settingsOption('enable', 'mediaSearch'),
|
||||
$this->settingsOption('auth', 'mediaSearchAuth'),
|
||||
$this->settingsOption('plex-library-exclude', 'homepagePlexSearchExclude', ['options' => $libraryList]),
|
||||
$this->settingsOption('media-search-server', 'mediaSearchType'),
|
||||
],
|
||||
'Playlists' => [
|
||||
$this->settingsOption('enable', 'homepagePlexPlaylist'),
|
||||
$this->settingsOption('auth', 'homepagePlexPlaylistAuth'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('input', 'plexTabName', ['label' => 'Plex Tab Name', 'placeholder' => 'Only use if you have Plex in a reverse proxy']),
|
||||
$this->settingsOption('input', 'plexTabURL', ['label' => 'Plex Tab WAN URL', 'placeholder' => 'http(s)://domain.com/plex']),
|
||||
$this->settingsOption('image-cache-quality', 'cacheImageSize'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('switch', 'homepageUseCustomStreamNames', ['label' => 'Use Tautulli custom names for users']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'plex'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionPlex()
|
||||
{
|
||||
if (!empty($this->config['plexURL']) && !empty($this->config['plexToken'])) {
|
||||
$url = $this->qualifyURL($this->config['plexURL']) . "/servers?X-Plex-Token=" . $this->config['plexToken'];
|
||||
try {
|
||||
$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
|
||||
$response = Requests::get($url, [], $options);
|
||||
libxml_use_internal_errors(true);
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'URL and/or Token not setup correctly', 422);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'URL and/or Token not setup', 422);
|
||||
return 'URL and/or Token not setup';
|
||||
}
|
||||
}
|
||||
|
||||
public function plexHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'streams' => [
|
||||
'enabled' => [
|
||||
'homepagePlexEnabled',
|
||||
'homepagePlexStreams'
|
||||
],
|
||||
'auth' => [
|
||||
'homepagePlexAuth',
|
||||
'homepagePlexStreamsAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'plexURL',
|
||||
'plexToken',
|
||||
'plexID'
|
||||
]
|
||||
],
|
||||
'recent' => [
|
||||
'enabled' => [
|
||||
'homepagePlexEnabled',
|
||||
'homepagePlexRecent'
|
||||
],
|
||||
'auth' => [
|
||||
'homepagePlexAuth',
|
||||
'homepagePlexRecentAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'plexURL',
|
||||
'plexToken',
|
||||
'plexID'
|
||||
]
|
||||
],
|
||||
'playlists' => [
|
||||
'enabled' => [
|
||||
'homepagePlexEnabled',
|
||||
'homepagePlexPlaylist'
|
||||
],
|
||||
'auth' => [
|
||||
'homepagePlexAuth',
|
||||
'homepagePlexPlaylistAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'plexURL',
|
||||
'plexToken',
|
||||
'plexID'
|
||||
]
|
||||
],
|
||||
'metadata' => [
|
||||
'enabled' => [
|
||||
'homepagePlexEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepagePlexAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'plexURL',
|
||||
'plexToken',
|
||||
'plexID'
|
||||
]
|
||||
],
|
||||
'search' => [
|
||||
'enabled' => [
|
||||
'homepagePlexEnabled',
|
||||
'mediaSearch'
|
||||
],
|
||||
'auth' => [
|
||||
'homepagePlexAuth',
|
||||
'mediaSearchAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'plexURL',
|
||||
'plexToken',
|
||||
'plexID'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderplexnowplaying()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->plexHomepagePermissions('streams'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Now Playing...</h2></div>
|
||||
<script>
|
||||
// Plex Stream
|
||||
homepageStream("plex", "' . $this->config['homepageStreamRefresh'] . '");
|
||||
// End Plex Stream
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function homepageOrderplexrecent()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->plexHomepagePermissions('recent'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Recent...</h2></div>
|
||||
<script>
|
||||
// Plex Recent
|
||||
homepageRecent("plex", "' . $this->config['homepageRecentRefresh'] . '");
|
||||
// End Plex Recent
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function homepageOrderplexplaylist()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->plexHomepagePermissions('playlists'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Playlists...</h2></div>
|
||||
<script>
|
||||
// Plex Playlist
|
||||
homepagePlaylist("plex");
|
||||
// End Plex Playlist
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getPlexHomepageStreams()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('streams'), true)) {
|
||||
return false;
|
||||
}
|
||||
if ($this->demo) {
|
||||
return $this->demoData('plex/plex-streams.json');
|
||||
}
|
||||
$this->setTautulliFriendlyNames();
|
||||
$ignore = array();
|
||||
$exclude = explode(',', $this->config['homepagePlexStreamsExclude']);
|
||||
$resolve = true;
|
||||
$url = $this->qualifyURL($this->config['plexURL']);
|
||||
$url = $url . "/status/sessions?X-Plex-Token=" . $this->config['plexToken'];
|
||||
$options = $this->requestOptions($url, $this->config['homepageStreamRefresh'], $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
libxml_use_internal_errors(true);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$plex = simplexml_load_string($response->body);
|
||||
foreach ($plex as $child) {
|
||||
if (!in_array($child['type'], $ignore) && !in_array($child['librarySectionID'], $exclude) && isset($child['librarySectionID'])) {
|
||||
$items[] = $this->resolvePlexItem($child);
|
||||
}
|
||||
}
|
||||
$api['content'] = ($resolve) ? $items : $plex;
|
||||
$api['plexID'] = $this->config['plexID'];
|
||||
$api['showNames'] = true;
|
||||
$api['group'] = '1';
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} else {
|
||||
$this->setAPIResponse('error', null, 401, []);
|
||||
return [];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPlexHomepageRecent()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('recent'), true)) {
|
||||
return false;
|
||||
}
|
||||
$ignore = array();
|
||||
$exclude = explode(',', $this->config['homepagePlexRecentExclude']);
|
||||
$resolve = true;
|
||||
$url = $this->qualifyURL($this->config['plexURL']);
|
||||
$urls['movie'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $this->config['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $this->config['homepageRecentLimit'] . "&type=1";
|
||||
$urls['tv'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $this->config['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $this->config['homepageRecentLimit'] . "&type=2";
|
||||
$urls['music'] = $url . "/hubs/home/recentlyAdded?X-Plex-Token=" . $this->config['plexToken'] . "&X-Plex-Container-Start=0&X-Plex-Container-Size=" . $this->config['homepageRecentLimit'] . "&type=8";
|
||||
try {
|
||||
foreach ($urls as $k => $v) {
|
||||
$options = $this->requestOptions($url, $this->config['homepageRecentRefresh'], $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
|
||||
$response = Requests::get($v, [], $options);
|
||||
libxml_use_internal_errors(true);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$plex = simplexml_load_string($response->body);
|
||||
foreach ($plex as $child) {
|
||||
if (!in_array($child['type'], $ignore) && !in_array($child['librarySectionID'], $exclude) && isset($child['librarySectionID'])) {
|
||||
$items[] = $this->resolvePlexItem($child);
|
||||
}
|
||||
}
|
||||
if (isset($api)) {
|
||||
$api['content'] = array_merge($api['content'], ($resolve) ? $items : $plex);
|
||||
} else {
|
||||
$api['content'] = ($resolve) ? $items : $plex;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($api['content'])) {
|
||||
usort($api['content'], function ($a, $b) {
|
||||
return $b['addedAt'] <=> $a['addedAt'];
|
||||
});
|
||||
}
|
||||
$api['plexID'] = $this->config['plexID'];
|
||||
$api['showNames'] = true;
|
||||
$api['group'] = '1';
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPlexHomepagePlaylists()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('playlists'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['plexURL']);
|
||||
$url = $url . "/playlists?X-Plex-Token=" . $this->config['plexToken'];
|
||||
$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
libxml_use_internal_errors(true);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$plex = simplexml_load_string($response->body);
|
||||
foreach ($plex as $child) {
|
||||
if ($child['playlistType'] == "video" && strpos(strtolower($child['title']), 'private') === false) {
|
||||
$playlistTitleClean = preg_replace("/(\W)+/", "", (string)$child['title']);
|
||||
$playlistURL = $this->qualifyURL($this->config['plexURL']);
|
||||
$playlistURL = $playlistURL . $child['key'] . "?X-Plex-Token=" . $this->config['plexToken'];
|
||||
$options = ($this->localURL($url)) ? array('verify' => false) : array();
|
||||
$playlistResponse = Requests::get($playlistURL, array(), $options);
|
||||
if ($playlistResponse->success) {
|
||||
$playlistResponse = simplexml_load_string($playlistResponse->body);
|
||||
$items[$playlistTitleClean]['title'] = (string)$child['title'];
|
||||
foreach ($playlistResponse->Video as $playlistItem) {
|
||||
$items[$playlistTitleClean][] = $this->resolvePlexItem($playlistItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$api['content'] = $items;
|
||||
$api['plexID'] = $this->config['plexID'];
|
||||
$api['showNames'] = true;
|
||||
$api['group'] = '1';
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Plex API error', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPlexHomepageMetadata($array)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('metadata'), true)) {
|
||||
return false;
|
||||
}
|
||||
if ($this->demo) {
|
||||
return $this->demoData('plex/plex-metadata.json');
|
||||
}
|
||||
$key = $array['key'] ?? null;
|
||||
if (!$key) {
|
||||
$this->setAPIResponse('error', 'Plex Metadata key is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$ignore = array();
|
||||
$resolve = true;
|
||||
$url = $this->qualifyURL($this->config['plexURL']);
|
||||
$url = $url . "/library/metadata/" . $key . "?X-Plex-Token=" . $this->config['plexToken'];
|
||||
$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
libxml_use_internal_errors(true);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$plex = simplexml_load_string($response->body);
|
||||
foreach ($plex as $child) {
|
||||
if (!in_array($child['type'], $ignore) && isset($child['librarySectionID'])) {
|
||||
$items[] = $this->resolvePlexItem($child);
|
||||
}
|
||||
}
|
||||
$api['content'] = ($resolve) ? $items : $plex;
|
||||
$api['plexID'] = $this->config['plexID'];
|
||||
$api['showNames'] = true;
|
||||
$api['group'] = '1';
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getPlexHomepageSearch($query)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->plexHomepagePermissions('search'), true)) {
|
||||
return false;
|
||||
}
|
||||
$query = $query ?? null;
|
||||
if (!$query) {
|
||||
$this->setAPIResponse('error', 'Plex Metadata key is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$ignore = array('artist', 'episode');
|
||||
$exclude = explode(',', $this->config['homepagePlexSearchExclude']);
|
||||
$resolve = true;
|
||||
$url = $this->qualifyURL($this->config['plexURL']);
|
||||
$url = $url . "/search?query=" . rawurlencode($query) . "&X-Plex-Token=" . $this->config['plexToken'];
|
||||
$options = $this->requestOptions($url, null, $this->config['plexDisableCertCheck'], $this->config['plexUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
libxml_use_internal_errors(true);
|
||||
if ($response->success) {
|
||||
$items = array();
|
||||
$plex = simplexml_load_string($response->body);
|
||||
foreach ($plex as $child) {
|
||||
if (!in_array($child['type'], $ignore) && !in_array($child['librarySectionID'], $exclude) && isset($child['librarySectionID'])) {
|
||||
$items[] = $this->resolvePlexItem($child);
|
||||
}
|
||||
}
|
||||
$api['content'] = ($resolve) ? $items : $plex;
|
||||
$api['plexID'] = $this->config['plexID'];
|
||||
$api['showNames'] = true;
|
||||
$api['group'] = '1';
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function resolvePlexItem($item)
|
||||
{
|
||||
// Static Height & Width
|
||||
$height = $this->getCacheImageSize('h');
|
||||
$width = $this->getCacheImageSize('w');
|
||||
$nowPlayingHeight = $this->getCacheImageSize('nph');
|
||||
$nowPlayingWidth = $this->getCacheImageSize('npw');
|
||||
// Cache Directories
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$cacheDirectoryWeb = 'data/cache/';
|
||||
// Types
|
||||
switch ($item['type']) {
|
||||
case 'show':
|
||||
$plexItem['type'] = 'tv';
|
||||
$plexItem['title'] = (string)$item['title'];
|
||||
$plexItem['secondaryTitle'] = (string)$item['year'];
|
||||
$plexItem['summary'] = (string)$item['summary'];
|
||||
$plexItem['ratingKey'] = (string)$item['ratingKey'];
|
||||
$plexItem['thumb'] = (string)$item['thumb'];
|
||||
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
|
||||
$plexItem['nowPlayingThumb'] = (string)$item['art'];
|
||||
$plexItem['nowPlayingKey'] = (string)$item['ratingKey'] . "-np";
|
||||
$plexItem['nowPlayingTitle'] = (string)$item['title'];
|
||||
$plexItem['nowPlayingBottom'] = (string)$item['year'];
|
||||
$plexItem['metadataKey'] = (string)$item['ratingKey'];
|
||||
break;
|
||||
case 'season':
|
||||
$plexItem['type'] = 'tv';
|
||||
$plexItem['title'] = (string)$item['parentTitle'];
|
||||
$plexItem['secondaryTitle'] = (string)$item['title'];
|
||||
$plexItem['summary'] = (string)$item['parentSummary'];
|
||||
$plexItem['ratingKey'] = (string)$item['parentRatingKey'];
|
||||
$plexItem['thumb'] = (string)$item['thumb'];
|
||||
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
|
||||
$plexItem['nowPlayingThumb'] = (string)$item['art'];
|
||||
$plexItem['nowPlayingKey'] = (string)$item['ratingKey'] . "-np";
|
||||
$plexItem['metadataKey'] = (string)$item['parentRatingKey'];
|
||||
break;
|
||||
case 'episode':
|
||||
$useImage = (isset($item['live']) ? 'plugins/images/homepage/livetv.png' : null);
|
||||
$plexItem['type'] = 'tv';
|
||||
$plexItem['title'] = (string)$item['grandparentTitle'];
|
||||
$plexItem['secondaryTitle'] = (string)$item['parentTitle'] . ' - Episode ' . (string)$item['index'];
|
||||
$plexItem['summary'] = (string)$item['title'];
|
||||
$plexItem['ratingKey'] = (string)($item['parentRatingKey'] ?? $item['ratingKey']);
|
||||
$plexItem['thumb'] = ($item['parentThumb'] ? (string)$item['parentThumb'] : (string)$item['grandparentThumb']);
|
||||
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
|
||||
$plexItem['nowPlayingThumb'] = (string)$item['grandparentArt'];
|
||||
$plexItem['nowPlayingKey'] = (string)$item['grandparentRatingKey'] . "-np";
|
||||
$plexItem['nowPlayingTitle'] = (string)$item['grandparentTitle'] . ' - ' . (string)$item['title'];
|
||||
$plexItem['nowPlayingBottom'] = 'S' . (string)$item['parentIndex'] . ' · E' . (string)$item['index'];
|
||||
$plexItem['metadataKey'] = (string)($item['grandparentRatingKey'] ?? $item['parentRatingKey'] ?? $item['ratingKey']);
|
||||
break;
|
||||
case 'clip':
|
||||
$useImage = (isset($item['live']) ? "plugins/images/homepage/livetv.png" : null);
|
||||
$plexItem['type'] = 'clip';
|
||||
$plexItem['title'] = (isset($item['live']) ? 'Live TV' : (string)$item['title']);
|
||||
$plexItem['secondaryTitle'] = '';
|
||||
$plexItem['summary'] = (string)$item['summary'];
|
||||
$plexItem['ratingKey'] = (string)$item['parentRatingKey'];
|
||||
$plexItem['thumb'] = (string)$item['thumb'];
|
||||
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
|
||||
$plexItem['nowPlayingThumb'] = (string)$item['art'];
|
||||
$plexItem['nowPlayingKey'] = isset($item['ratingKey']) ? (string)$item['ratingKey'] . "-np" : (isset($item['live']) ? "livetv.png" : ":)");
|
||||
$plexItem['nowPlayingTitle'] = $plexItem['title'];
|
||||
$plexItem['nowPlayingBottom'] = isset($item['extraType']) ? "Trailer" : (isset($item['live']) ? "Live TV" : ":)");
|
||||
break;
|
||||
case 'album':
|
||||
case 'track':
|
||||
$plexItem['type'] = 'music';
|
||||
$plexItem['title'] = (string)$item['parentTitle'];
|
||||
$plexItem['secondaryTitle'] = (string)$item['title'];
|
||||
$plexItem['summary'] = (string)$item['title'];
|
||||
$plexItem['ratingKey'] = (string)$item['parentRatingKey'];
|
||||
$plexItem['thumb'] = (string)$item['thumb'];
|
||||
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
|
||||
$plexItem['nowPlayingThumb'] = ($item['parentThumb']) ? (string)$item['parentThumb'] : (string)$item['art'];
|
||||
$plexItem['nowPlayingKey'] = (string)$item['parentRatingKey'] . "-np";
|
||||
$plexItem['nowPlayingTitle'] = (string)$item['grandparentTitle'] . ' - ' . (string)$item['title'];
|
||||
$plexItem['nowPlayingBottom'] = (string)$item['parentTitle'];
|
||||
$plexItem['metadataKey'] = isset($item['grandparentRatingKey']) ? (string)$item['grandparentRatingKey'] : (string)$item['parentRatingKey'];
|
||||
break;
|
||||
default:
|
||||
$useImage = (isset($item['live']) ? 'plugins/images/homepage/livetv.png' : null);
|
||||
$plexItem['type'] = 'movie';
|
||||
$plexItem['title'] = (string)$item['title'];
|
||||
$plexItem['secondaryTitle'] = (string)$item['year'];
|
||||
$plexItem['summary'] = (string)$item['summary'];
|
||||
$plexItem['ratingKey'] = (string)$item['ratingKey'];
|
||||
$plexItem['thumb'] = (string)$item['thumb'];
|
||||
$plexItem['key'] = (string)$item['ratingKey'] . "-list";
|
||||
$plexItem['nowPlayingThumb'] = (string)$item['art'];
|
||||
$plexItem['nowPlayingKey'] = (string)$item['ratingKey'] . "-np";
|
||||
$plexItem['nowPlayingTitle'] = (string)$item['title'];
|
||||
$plexItem['nowPlayingBottom'] = (string)$item['year'];
|
||||
$plexItem['metadataKey'] = (string)$item['ratingKey'];
|
||||
}
|
||||
$plexItem['originalType'] = $item['type'];
|
||||
$plexItem['uid'] = (string)$item['ratingKey'];
|
||||
$plexItem['elapsed'] = isset($item['viewOffset']) && $item['viewOffset'] !== '0' ? (int)$item['viewOffset'] : null;
|
||||
$plexItem['duration'] = isset($item['duration']) ? (int)$item['duration'] : (int)$item->Media['duration'];
|
||||
$plexItem['addedAt'] = isset($item['addedAt']) ? (int)$item['addedAt'] : null;
|
||||
$plexItem['watched'] = ($plexItem['elapsed'] && $plexItem['duration'] ? floor(($plexItem['elapsed'] / $plexItem['duration']) * 100) : 0);
|
||||
$plexItem['transcoded'] = isset($item->TranscodeSession['progress']) ? floor((int)$item->TranscodeSession['progress'] - $plexItem['watched']) : '';
|
||||
$plexItem['stream'] = isset($item->Media->Part->Stream['decision']) ? (string)$item->Media->Part->Stream['decision'] : '';
|
||||
$plexItem['id'] = str_replace('"', '', (string)$item->Player['machineIdentifier']);
|
||||
$plexItem['session'] = (string)$item->Session['id'];
|
||||
$plexItem['bandwidth'] = (string)$item->Session['bandwidth'];
|
||||
$plexItem['bandwidthType'] = (string)$item->Session['location'];
|
||||
$plexItem['sessionType'] = isset($item->TranscodeSession['progress']) ? 'Transcoding' : 'Direct Playing';
|
||||
$plexItem['state'] = (((string)$item->Player['state'] == "paused") ? "pause" : "play");
|
||||
$plexItem['user'] = $this->formatPlexUserName($item);
|
||||
$plexItem['userThumb'] = (($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) || ($this->config['homepageShowStreamNamesWithoutIp'] && $this->qualifyRequest($this->config['homepageShowStreamNamesWithoutIpAuth']))) ? (string)$item->User['thumb'] : "";
|
||||
$plexItem['userAddress'] = ($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) ? (string)$item->Player['address'] : "x.x.x.x";
|
||||
$plexItem['address'] = $this->config['plexTabURL'] ? $this->config['plexTabURL'] . "/web/index.html#!/server/" . $this->config['plexID'] . "/details?key=/library/metadata/" . $item['ratingKey'] : "https://app.plex.tv/web/app#!/server/" . $this->config['plexID'] . "/details?key=/library/metadata/" . $item['ratingKey'];
|
||||
$plexItem['nowPlayingOriginalImage'] = 'api/v2/homepage/image?source=plex&img=' . $plexItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $plexItem['nowPlayingKey'] . '$' . $this->randString();
|
||||
$plexItem['originalImage'] = 'api/v2/homepage/image?source=plex&img=' . $plexItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $plexItem['key'] . '$' . $this->randString();
|
||||
$plexItem['openTab'] = $this->config['plexTabURL'] && $this->config['plexTabName'] ? true : false;
|
||||
$plexItem['tabName'] = $this->config['plexTabName'] ? $this->config['plexTabName'] : '';
|
||||
// Stream info
|
||||
$plexItem['userStream'] = array(
|
||||
'platform' => (string)$item->Player['platform'],
|
||||
'product' => (string)$item->Player['product'],
|
||||
'device' => (string)$item->Player['device'],
|
||||
'stream' => isset($item->Media) ? (string)$item->Media->Part['decision'] . ($item->TranscodeSession['throttled'] == '1' ? ' (Throttled)' : '') : '',
|
||||
'videoResolution' => (string)$item->Media['videoResolution'],
|
||||
'throttled' => ($item->TranscodeSession['throttled'] == 1) ? true : false,
|
||||
'sourceVideoCodec' => (string)$item->TranscodeSession['sourceVideoCodec'],
|
||||
'videoCodec' => (string)$item->TranscodeSession['videoCodec'],
|
||||
'audioCodec' => (string)$item->TranscodeSession['audioCodec'],
|
||||
'sourceAudioCodec' => (string)$item->TranscodeSession['sourceAudioCodec'],
|
||||
'videoDecision' => $this->streamType((string)$item->TranscodeSession['videoDecision']),
|
||||
'audioDecision' => $this->streamType((string)$item->TranscodeSession['audioDecision']),
|
||||
'container' => (string)$item->TranscodeSession['container'],
|
||||
'audioChannels' => (string)$item->TranscodeSession['audioChannels']
|
||||
);
|
||||
// Genre catch all
|
||||
if ($item->Genre) {
|
||||
$genres = array();
|
||||
foreach ($item->Genre as $key => $value) {
|
||||
$genres[] = (string)$value['tag'];
|
||||
}
|
||||
}
|
||||
// Actor catch all
|
||||
if ($item->Role) {
|
||||
$actors = array();
|
||||
foreach ($item->Role as $key => $value) {
|
||||
if ($value['thumb']) {
|
||||
$actors[] = array(
|
||||
'name' => (string)$value['tag'],
|
||||
'role' => (string)$value['role'],
|
||||
'thumb' => (string)$value['thumb']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Metadata information
|
||||
$plexItem['metadata'] = array(
|
||||
'guid' => (string)$item['guid'],
|
||||
'summary' => (string)$item['summary'],
|
||||
'rating' => (string)$item['rating'],
|
||||
'duration' => (string)$item['duration'],
|
||||
'originallyAvailableAt' => (string)$item['originallyAvailableAt'],
|
||||
'year' => (string)$item['year'],
|
||||
'studio' => (string)$item['studio'],
|
||||
'tagline' => (string)$item['tagline'],
|
||||
'genres' => ($item->Genre) ? $genres : '',
|
||||
'actors' => ($item->Role) ? $actors : ''
|
||||
);
|
||||
if (file_exists($cacheDirectory . $plexItem['nowPlayingKey'] . '.jpg')) {
|
||||
$plexItem['nowPlayingImageURL'] = $cacheDirectoryWeb . $plexItem['nowPlayingKey'] . '.jpg';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $plexItem['key'] . '.jpg')) {
|
||||
$plexItem['imageURL'] = $cacheDirectoryWeb . $plexItem['key'] . '.jpg';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $plexItem['nowPlayingKey'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $plexItem['nowPlayingKey'] . '.jpg') || !file_exists($cacheDirectory . $plexItem['nowPlayingKey'] . '.jpg')) {
|
||||
$plexItem['nowPlayingImageURL'] = 'api/v2/homepage/image?source=plex&img=' . $plexItem['nowPlayingThumb'] . '&height=' . $nowPlayingHeight . '&width=' . $nowPlayingWidth . '&key=' . $plexItem['nowPlayingKey'] . '';
|
||||
}
|
||||
if (file_exists($cacheDirectory . $plexItem['key'] . '.jpg') && (time() - 604800) > filemtime($cacheDirectory . $plexItem['key'] . '.jpg') || !file_exists($cacheDirectory . $plexItem['key'] . '.jpg')) {
|
||||
$plexItem['imageURL'] = 'api/v2/homepage/image?source=plex&img=' . $plexItem['thumb'] . '&height=' . $height . '&width=' . $width . '&key=' . $plexItem['key'] . '';
|
||||
}
|
||||
if (!$plexItem['nowPlayingThumb']) {
|
||||
$plexItem['nowPlayingOriginalImage'] = $plexItem['nowPlayingImageURL'] = "plugins/images/homepage/no-np.png";
|
||||
$plexItem['nowPlayingKey'] = "no-np";
|
||||
}
|
||||
if (!$plexItem['thumb'] || $plexItem['addedAt'] >= (time() - 300)) {
|
||||
$plexItem['originalImage'] = $plexItem['imageURL'] = "plugins/images/homepage/no-list.png";
|
||||
$plexItem['key'] = "no-list";
|
||||
}
|
||||
if (isset($useImage)) {
|
||||
$plexItem['useImage'] = $useImage;
|
||||
}
|
||||
return $plexItem;
|
||||
}
|
||||
|
||||
public function getTautulliFriendlyNames($bypass = null)
|
||||
{
|
||||
$names = [];
|
||||
if (!$this->qualifyRequest(1) && !$bypass) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['tautulliURL']);
|
||||
$url .= '/api/v2?apikey=' . $this->config['tautulliApikey'];
|
||||
$url .= '&cmd=get_users';
|
||||
$options = $this->requestOptions($url, null, $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
|
||||
try {
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$response = json_decode($response->body, true);
|
||||
foreach ($response['response']['data'] as $user) {
|
||||
if ($user['user_id'] != 0) {
|
||||
$names[$user['username']] = $user['friendly_name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setAPIResponse('error', null, 422, [$e->getMessage()]);
|
||||
}
|
||||
$this->setAPIResponse('success', null, 200, $names);
|
||||
return $names;
|
||||
}
|
||||
|
||||
public function setTautulliFriendlyNames()
|
||||
{
|
||||
if ($this->config['tautulliURL'] && $this->config['tautulliApikey'] && $this->config['homepageUseCustomStreamNames']) {
|
||||
$names = $this->getTautulliFriendlyNames(true);
|
||||
$names = json_encode($names);
|
||||
if ($names !== $this->config['homepageCustomStreamNames']) {
|
||||
$this->updateConfig(array('homepageCustomStreamNames' => $names));
|
||||
$this->config['homepageCustomStreamNames'] = $names;
|
||||
$this->setLoggerChannel('Tautulli');
|
||||
$this->logger->debug('Updating Tautulli custom names config item', $names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function formatPlexUserName($item)
|
||||
{
|
||||
$name = (($this->config['homepageShowStreamNames'] && $this->qualifyRequest($this->config['homepageShowStreamNamesAuth'])) || ($this->config['homepageShowStreamNamesWithoutIp'] && $this->qualifyRequest($this->config['homepageShowStreamNamesWithoutIpAuth']))) ? (string)$item->User['title'] : "";
|
||||
try {
|
||||
if ($this->config['homepageUseCustomStreamNames']) {
|
||||
$customNames = json_decode($this->config['homepageCustomStreamNames'], true);
|
||||
if (array_key_exists($name, $customNames)) {
|
||||
$name = $customNames[$name];
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// don't do anythig if it goes wrong, like if the JSON is badly formatted
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function plexLibraryList($value = 'id')
|
||||
{
|
||||
|
||||
if (!empty($this->config['plexToken']) && !empty($this->config['plexID'])) {
|
||||
$url = 'https://plex.tv/api/servers/' . $this->config['plexID'];
|
||||
try {
|
||||
$headers = array(
|
||||
"Accept" => "application/json",
|
||||
"X-Plex-Token" => $this->config['plexToken']
|
||||
);
|
||||
$response = Requests::get($url, $headers, array());
|
||||
libxml_use_internal_errors(true);
|
||||
if ($response->success) {
|
||||
$libraryList = array();
|
||||
$plex = simplexml_load_string($response->body);
|
||||
foreach ($plex->Server->Section as $child) {
|
||||
$libraryList['libraries'][(string)$child['title']] = (string)$child[$value];
|
||||
}
|
||||
$libraryList = array_change_key_case($libraryList, CASE_LOWER);
|
||||
return $libraryList;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Plex')->error($e);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
trait PromPageHomepageItem
|
||||
{
|
||||
private static Client $kumaClient;
|
||||
|
||||
public function promPageSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'PromPage',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/prompage.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('html', null, ['override' => 6, 'label' => 'Info', 'html' => '<p>This homepage item requires <a href="https://github.com/henrywhitaker3/prompage" target="_blank" rel="noreferrer noopener">PromPage <i class="fa fa-external-link" aria-hidden="true"></i></a> to be running.</p>']),
|
||||
$this->settingsOption('enable', 'homepagePromPageEnabled'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'promPageURL', ['help' => 'URL for Uptime Kuma e.g. http://kuma:3001 (no trailing slash)', 'placeholder' => 'http://prompage:3000']),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('refresh', 'homepagePromPageRefresh'),
|
||||
$this->settingsOption('title', 'homepagePromPageHeader'),
|
||||
$this->settingsOption('toggle-title', 'homepagePromPageHeaderToggle'),
|
||||
$this->settingsOption('switch', 'homepagePromPageCompact', ['label' => 'Compact view', 'help' => 'Toggles the compact view of this homepage module']),
|
||||
$this->settingsOption('switch', 'homepagePromPageShowUptime', ['label' => 'Show monitor uptime']),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function promPageHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepagePromPageEnabled'
|
||||
],
|
||||
'not_empty' => [
|
||||
'promPageURL',
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderPromPage()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->promPageHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Status Page...</h2></div>
|
||||
<script>
|
||||
// PromPage
|
||||
homepagePromPage("' . $this->config['homepagePromPageRefresh'] . '");
|
||||
// End PromPage
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getpromPageHomepageData()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->promPageHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api = [];
|
||||
$url = $this->qualifyURL($this->config['promPageURL']);
|
||||
try {
|
||||
$services = json_decode($this->getPromPageClient($url, $this->config['promPageToken'])
|
||||
->get('/api/services')
|
||||
->getBody()
|
||||
->getContents())->services;
|
||||
|
||||
$api = [
|
||||
'data' => $services,
|
||||
'options' => [
|
||||
'title' => $this->config['homepagePromPageHeader'],
|
||||
'titleToggle' => $this->config['homepagePromPageHeaderToggle'],
|
||||
'compact' => $this->config['homepagePromPageCompact'],
|
||||
'showUptime' => $this->config['homepagePromPageShowUptime'],
|
||||
]
|
||||
];
|
||||
} catch (GuzzleException $e) {
|
||||
$this->setLoggerChannel('promPage')->error($e);
|
||||
$this->setAPIResponse('error', $e->getMessage(), 401);
|
||||
return false;
|
||||
};
|
||||
$api = isset($api) ? $api : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
private function getPromPageClient(string $url): Client
|
||||
{
|
||||
if (!isset(static::$kumaClient)) {
|
||||
static::$kumaClient = new Client([
|
||||
'base_uri' => $url,
|
||||
]);
|
||||
}
|
||||
|
||||
return static::$kumaClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
trait ProwlarrHomepageItem
|
||||
{
|
||||
public function prowlarrSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Prowlarr',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/prowlarr.png',
|
||||
'category' => 'Utility',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageProwlarrEnabled'),
|
||||
$this->settingsOption('auth', 'homepageProwlarrAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'prowlarrURL'),
|
||||
$this->settingsOption('token', 'prowlarrToken'),
|
||||
$this->settingsOption('disable-cert-check', 'prowlarrDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'prowlarrUseCustomCertificate'),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('switch', 'homepageProwlarrBackholeDownload', ['label' => 'Prefer black hole download', 'help' => 'Prefer black hole download link instead of direct/magnet download']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'prowlarr'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function prowlarrHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageProwlarrEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageProwlarrAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'prowlarrURL',
|
||||
'prowlarrToken'
|
||||
]
|
||||
],
|
||||
'test' => [
|
||||
'auth' => [
|
||||
'homepageProwlarrAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'prowlarrURL',
|
||||
'prowlarrToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderProwlarr()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->prowlarrHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Prowlarr...</h2></div>
|
||||
<script>
|
||||
// Prowlarr
|
||||
homepageProwlarr();
|
||||
// End Prowlarr
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnectionProwlarr()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->prowlarrHomepagePermissions('test'), true)) {
|
||||
return false;
|
||||
}
|
||||
$apiURL = $this->qualifyURL($this->config['prowlarrURL']);
|
||||
$endpoint = $apiURL . '/api/v1/search?apikey=' . $this->config['prowlarrToken'] . '&query=this-is-just-a-test-for-organizr';
|
||||
try {
|
||||
$headers = [];
|
||||
$options = $this->requestOptions($apiURL, 120, $this->config['prowlarrDisableCertCheck'], $this->config['prowlarrUseCustomCertificate']);
|
||||
$response = Requests::get($endpoint, $headers, $options);
|
||||
if ($response->success) {
|
||||
$apiData = json_decode($response->body, true);
|
||||
$api['content'] = $apiData;
|
||||
unset($apiData);
|
||||
} else {
|
||||
$this->setResponse(403, 'Error connecting to Prowlarr');
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setResponse(200, null, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function searchProwlarrIndexers($query = null)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->prowlarrHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
if (!$query) {
|
||||
$this->setAPIResponse('error', 'Query was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
$apiURL = $this->qualifyURL($this->config['prowlarrURL']);
|
||||
$endpoint = $apiURL . '/api/v1/search?apikey=' . $this->config['prowlarrToken'] . '&query=' . urlencode($query);
|
||||
try {
|
||||
$headers = [];
|
||||
$options = $this->requestOptions($apiURL, 120, $this->config['prowlarrDisableCertCheck'], $this->config['prowlarrUseCustomCertificate']);
|
||||
$response = Requests::get($endpoint, $headers, $options);
|
||||
if ($response->success) {
|
||||
$apiData = json_decode($response->body, true);
|
||||
$api['content'] = $apiData;
|
||||
unset($apiData);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function performProwlarrBackHoleDownload($guid = null, $indexerId = null)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->prowlarrHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
if (!$guid) {
|
||||
$this->setAPIResponse('error', 'guid was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$indexerId) {
|
||||
$this->setAPIResponse('error', 'indexerId was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
$apiURL = $this->qualifyURL($this->config['prowlarrURL']);
|
||||
$endpoint = $apiURL . '/api/v1/search?apikey=' . $this->config['prowlarrToken'];
|
||||
try {
|
||||
$headers = [];
|
||||
$data = ['guid'=>$guid,'indexerId'=>$indexerId];
|
||||
$options = $this->requestOptions($apiURL, 120, $this->config['prowlarrDisableCertCheck'], $this->config['prowlarrUseCustomCertificate']);
|
||||
$ch = curl_init($endpoint);
|
||||
$payload = json_encode($data);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
if ($response) {
|
||||
$api['content'] = $response;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Prowlarr')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
if ($api['content']) {
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Unknown error', 400, $api);
|
||||
}
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
trait QBitTorrentHomepageItem
|
||||
{
|
||||
public function qBittorrentSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'qBittorrent',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/qBittorrent.png',
|
||||
'category' => 'Downloader',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageqBittorrentEnabled'),
|
||||
$this->settingsOption('auth', 'homepageqBittorrentAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'qBittorrentURL'),
|
||||
$this->settingsOption('select', 'qBittorrentApiVersion', ['label' => 'API Version', 'options' => $this->qBittorrentApiOptions()]),
|
||||
$this->settingsOption('username', 'qBittorrentUsername'),
|
||||
$this->settingsOption('password', 'qBittorrentPassword'),
|
||||
$this->settingsOption('disable-cert-check', 'qBittorrentDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'qBittorrentUseCustomCertificate'),
|
||||
],
|
||||
'API SOCKS' => [
|
||||
$this->settingsOption('socks', 'qbittorrent'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('enable', 'qBittorrentSocksEnabled'),
|
||||
$this->settingsOption('auth', 'qBittorrentSocksAuth'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('hide-seeding', 'qBittorrentHideSeeding'),
|
||||
$this->settingsOption('hide-completed', 'qBittorrentHideCompleted'),
|
||||
$this->settingsOption('select', 'qBittorrentSortOrder', ['label' => 'Order', 'options' => $this->qBittorrentSortOptions()]),
|
||||
$this->settingsOption('switch', 'qBittorrentReverseSorting', ['label' => 'Reverse Sorting']),
|
||||
$this->settingsOption('refresh', 'qBittorrentRefresh'),
|
||||
$this->settingsOption('combine', 'qBittorrentCombine'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'qbittorrent'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionQBittorrent()
|
||||
{
|
||||
if (empty($this->config['qBittorrentURL'])) {
|
||||
$this->setAPIResponse('error', 'qBittorrent URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$digest = $this->qualifyURL($this->config['qBittorrentURL'], true);
|
||||
$data = array('username' => $this->config['qBittorrentUsername'], 'password' => $this->decrypt($this->config['qBittorrentPassword']));
|
||||
$apiVersionLogin = ($this->config['qBittorrentApiVersion'] == '1') ? '/login' : '/api/v2/auth/login';
|
||||
$apiVersionQuery = ($this->config['qBittorrentApiVersion'] == '1') ? '/query/torrents?sort=' : '/api/v2/torrents/info?sort=';
|
||||
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionLogin;
|
||||
try {
|
||||
$options = $this->requestOptions($this->config['qBittorrentURL'], null, $this->config['qBittorrentDisableCertCheck'], $this->config['qBittorrentUseCustomCertificate']);
|
||||
$response = Requests::post($url, [], $data, $options);
|
||||
$reflection = new ReflectionClass($response->cookies);
|
||||
$cookie = $reflection->getProperty("cookies");
|
||||
$cookie->setAccessible(true);
|
||||
$cookie = $cookie->getValue($response->cookies);
|
||||
if ($cookie) {
|
||||
$headers = array(
|
||||
'Cookie' => 'SID=' . $cookie['SID']->value
|
||||
);
|
||||
$reverse = $this->config['qBittorrentReverseSorting'] ? 'true' : 'false';
|
||||
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionQuery . $this->config['qBittorrentSortOrder'] . '&reverse=' . $reverse;
|
||||
$response = Requests::get($url, $headers, $options);
|
||||
if ($response) {
|
||||
$torrents = json_decode($response->body, true);
|
||||
if (is_array($torrents)) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'qBittorrent Error Occurred - Check URL or Credentials', 500);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'qBittorrent Connection Error Occurred - Check URL or Credentials', 500);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('qBittorrent')->warning('Could not get session ID');
|
||||
$this->setAPIResponse('error', 'qBittorrent Connect Function - Error: Could not get session ID', 409);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('qBittorrent')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function qBittorrentHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageqBittorrentEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageqBittorrentAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'qBittorrentURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderqBittorrent()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->qBittorrentHomepagePermissions('main'))) {
|
||||
$loadingBox = ($this->config['qBittorrentCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['qBittorrentCombine']) ? 'buildDownloaderCombined(\'qBittorrent\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("qBittorrent"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrderqBittorrent
|
||||
' . $builder . '
|
||||
homepageDownloader("qBittorrent", "' . $this->config['qBittorrentRefresh'] . '");
|
||||
// End homepageOrderqBittorrent
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getQBittorrentHomepageQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->qBittorrentHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$digest = $this->qualifyURL($this->config['qBittorrentURL'], true);
|
||||
$data = array('username' => $this->config['qBittorrentUsername'], 'password' => $this->decrypt($this->config['qBittorrentPassword']));
|
||||
$apiVersionLogin = ($this->config['qBittorrentApiVersion'] == '1') ? '/login' : '/api/v2/auth/login';
|
||||
$apiVersionQuery = ($this->config['qBittorrentApiVersion'] == '1') ? '/query/torrents?sort=' : '/api/v2/torrents/info?sort=';
|
||||
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionLogin;
|
||||
try {
|
||||
$options = $this->requestOptions($this->config['qBittorrentURL'], $this->config['qBittorrentRefresh'], $this->config['qBittorrentDisableCertCheck'], $this->config['qBittorrentUseCustomCertificate']);
|
||||
$response = Requests::post($url, [], $data, $options);
|
||||
$reflection = new ReflectionClass($response->cookies);
|
||||
$cookie = $reflection->getProperty("cookies");
|
||||
$cookie->setAccessible(true);
|
||||
$cookie = $cookie->getValue($response->cookies);
|
||||
if ($cookie) {
|
||||
$headers = array(
|
||||
'Cookie' => 'SID=' . $cookie['SID']->value
|
||||
);
|
||||
$reverse = $this->config['qBittorrentReverseSorting'] ? 'true' : 'false';
|
||||
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $apiVersionQuery . $this->config['qBittorrentSortOrder'] . '&reverse=' . $reverse;
|
||||
$response = Requests::get($url, $headers, $options);
|
||||
if ($response) {
|
||||
$torrentList = json_decode($response->body, true);
|
||||
if ($this->config['qBittorrentHideSeeding'] || $this->config['qBittorrentHideCompleted']) {
|
||||
$filter = array();
|
||||
$torrents = array();
|
||||
if ($this->config['qBittorrentHideSeeding']) {
|
||||
array_push($filter, 'uploading', 'stalledUP', 'queuedUP');
|
||||
}
|
||||
if ($this->config['qBittorrentHideCompleted']) {
|
||||
array_push($filter, 'pausedUP');
|
||||
}
|
||||
foreach ($torrentList as $key => $value) {
|
||||
if (!in_array($value['state'], $filter)) {
|
||||
$torrents[] = $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$torrents = json_decode($response->body, true);
|
||||
}
|
||||
$api['content']['queueItems'] = $torrents;
|
||||
$api['content']['historyItems'] = false;
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('qBittorrent')->warning('Could not get session ID');
|
||||
$this->setAPIResponse('error', 'qBittorrent Connect Function - Error: Could not get session ID', 409);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('qBittorrent')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
<?php
|
||||
|
||||
trait RadarrHomepageItem
|
||||
{
|
||||
public function radarrSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Radarr',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/radarr.png',
|
||||
'category' => 'PVR',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageRadarrEnabled'),
|
||||
$this->settingsOption('auth', 'homepageRadarrAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('multiple-url', 'radarrURL'),
|
||||
$this->settingsOption('multiple-token', 'radarrToken'),
|
||||
$this->settingsOption('disable-cert-check', 'radarrDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'radarrUseCustomCertificate'),
|
||||
],
|
||||
'API SOCKS' => [
|
||||
$this->settingsOption('socks', 'radarr'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('enable', 'radarrSocksEnabled'),
|
||||
$this->settingsOption('auth', 'radarrSocksAuth'),
|
||||
],
|
||||
'Queue' => [
|
||||
$this->settingsOption('enable', 'homepageRadarrQueueEnabled'),
|
||||
$this->settingsOption('auth', 'homepageRadarrQueueAuth'),
|
||||
$this->settingsOption('combine', 'homepageRadarrQueueCombine'),
|
||||
$this->settingsOption('refresh', 'homepageRadarrQueueRefresh'),
|
||||
],
|
||||
'Calendar' => [
|
||||
$this->settingsOption('calendar-start', 'calendarStart'),
|
||||
$this->settingsOption('calendar-end', 'calendarEnd'),
|
||||
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
|
||||
$this->settingsOption('calendar-default-view', 'calendarDefault'),
|
||||
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
|
||||
$this->settingsOption('calendar-locale', 'calendarLocale'),
|
||||
$this->settingsOption('calendar-limit', 'calendarLimit'),
|
||||
$this->settingsOption('refresh', 'calendarRefresh'),
|
||||
$this->settingsOption('switch', 'radarrUnmonitored', ['label' => 'Show Unmonitored']),
|
||||
$this->settingsOption('switch', 'radarrPhysicalRelease', ['label' => 'Show Physical Releases']),
|
||||
$this->settingsOption('switch', 'radarrDigitalRelease', ['label' => 'Show Digital Releases']),
|
||||
$this->settingsOption('switch', 'radarrCinemaRelease', ['label' => 'Show Cinema Releases']),
|
||||
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
|
||||
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
|
||||
$this->settingsOption('enable', 'radarrIcon', ['label' => 'Show Radarr Icon']),
|
||||
$this->settingsOption('calendar-link-url', 'radarrCalendarLink'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('calendar-frame-target', 'radarrFrameTarget')
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'radarr'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionRadarr()
|
||||
{
|
||||
if (empty($this->config['radarrURL'])) {
|
||||
$this->setAPIResponse('error', 'Radarr URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['radarrToken'])) {
|
||||
$this->setAPIResponse('error', 'Radarr Token is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$failed = false;
|
||||
$errors = '';
|
||||
$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], null, $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
|
||||
$results = $downloader->getRootFolder();
|
||||
$downloadList = json_decode($results, true);
|
||||
if (is_array($downloadList) || is_object($downloadList)) {
|
||||
$queue = (array_key_exists('error', $downloadList)) ? $downloadList['error']['msg'] : $downloadList;
|
||||
if (!is_array($queue)) {
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': ' . $queue;
|
||||
$failed = true;
|
||||
}
|
||||
} else {
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': Response was not JSON';
|
||||
$failed = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$failed = true;
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': ' . $e->getMessage();
|
||||
$this->setLoggerChannel('Radarr')->error($e);
|
||||
}
|
||||
}
|
||||
if ($failed) {
|
||||
$this->setAPIResponse('error', $errors, 500);
|
||||
return false;
|
||||
} else {
|
||||
$this->setAPIResponse('success', null, 200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function radarrHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'calendar' => [
|
||||
'enabled' => [
|
||||
'homepageRadarrEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageRadarrAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'radarrURL',
|
||||
'radarrToken'
|
||||
]
|
||||
],
|
||||
'queue' => [
|
||||
'enabled' => [
|
||||
'homepageRadarrEnabled',
|
||||
'homepageRadarrQueueEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageRadarrAuth',
|
||||
'homepageRadarrQueueAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'radarrURL',
|
||||
'radarrToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderRadarrQueue()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->radarrHomepagePermissions('queue'))) {
|
||||
$loadingBox = ($this->config['homepageRadarrQueueCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['homepageRadarrQueueCombine']) ? 'buildDownloaderCombined(\'radarr\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("radarr"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrderRadarrQueue
|
||||
' . $builder . '
|
||||
homepageDownloader("radarr", "' . $this->config['homepageRadarrQueueRefresh'] . '");
|
||||
// End homepageOrderRadarrQueue
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getRadarrQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->radarrHomepagePermissions('queue'), true)) {
|
||||
return false;
|
||||
}
|
||||
$queueItems = array();
|
||||
$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], $this->config['homepageRadarrQueueRefresh'], $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
|
||||
$results = $downloader->getQueue();
|
||||
$downloadList = json_decode($results, true);
|
||||
if (is_array($downloadList) || is_object($downloadList)) {
|
||||
$queue = (array_key_exists('error', $downloadList)) ? [] : $downloadList;
|
||||
$queue = $queue['records'] ?? $queue;
|
||||
} else {
|
||||
$queue = [];
|
||||
}
|
||||
if (!empty($queue)) {
|
||||
$queueItems = array_merge($queueItems, $queue);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error($e);
|
||||
}
|
||||
}
|
||||
$api['content']['queueItems'] = $queueItems;
|
||||
$api['content']['historyItems'] = false;
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function getRadarrCalendar($startDate = null, $endDate = null)
|
||||
{
|
||||
$startDate = ($startDate) ?? $_GET['start'] ?? date('Y-m-d', strtotime('-' . $this->config['calendarStart'] . ' days'));
|
||||
$endDate = ($endDate) ?? $_GET['end'] ?? date('Y-m-d', strtotime('+' . $this->config['calendarEnd'] . ' days'));
|
||||
if (!$this->homepageItemPermissions($this->radarrHomepagePermissions('calendar'), true)) {
|
||||
return false;
|
||||
}
|
||||
if ($this->demo) {
|
||||
return $this->demoData('radarr/calendar.json');
|
||||
}
|
||||
$calendarItems = array();
|
||||
$list = $this->csvHomepageUrlToken($this->config['radarrURL'], $this->config['radarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], $this->config['homepageRadarrQueueRefresh'], $this->config['radarrDisableCertCheck'], $this->config['radarrUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'radarr', null, null, $options);
|
||||
$results = $downloader->getCalendar($startDate, $endDate, $this->config['radarrUnmonitored']);
|
||||
$result = json_decode($results, true);
|
||||
if (is_array($result) || is_object($result)) {
|
||||
$calendar = (array_key_exists('error', $result)) ? '' : $this->formatRadarrCalendar($results, $key, $value['url']);
|
||||
} else {
|
||||
$calendar = '';
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('Radarr')->error($e);
|
||||
}
|
||||
if (!empty($calendar)) {
|
||||
$calendarItems = array_merge($calendarItems, $calendar);
|
||||
}
|
||||
}
|
||||
$this->setAPIResponse('success', null, 200, $calendarItems);
|
||||
return $calendarItems;
|
||||
}
|
||||
|
||||
public function formatRadarrCalendar($array, $number, $url)
|
||||
{
|
||||
$url = rtrim($url, '/'); //remove trailing slash
|
||||
$url = $url . '/api';
|
||||
$array = json_decode($array, true);
|
||||
$gotCalendar = array();
|
||||
$i = 0;
|
||||
foreach ($array as $child) {
|
||||
for ($j = 0; $j < 3; $j++) {
|
||||
$type = [];
|
||||
if ($j == 0 && $this->config['radarrPhysicalRelease'] && isset($child['physicalRelease'])) {
|
||||
$releaseDate = $child['physicalRelease'];
|
||||
array_push($type, "physical");
|
||||
if (isset($child['digitalRelease']) && $child['physicalRelease'] == $child['digitalRelease']) {
|
||||
array_push($type, "digital");
|
||||
$j++;
|
||||
}
|
||||
if (isset($child['inCinemas']) && $child['physicalRelease'] == $child['inCinemas']) {
|
||||
array_push($type, "cinema");
|
||||
$j += 2;
|
||||
}
|
||||
} elseif ($j == 1 && $this->config['radarrDigitalRelease'] && isset($child['digitalRelease'])) {
|
||||
$releaseDate = $child['digitalRelease'];
|
||||
array_push($type, "digital");
|
||||
if (isset($child['inCinemas']) && $child['digitalRelease'] == $child['inCinemas']) {
|
||||
array_push($type, "cinema");
|
||||
$j++;
|
||||
}
|
||||
} elseif ($j == 2 && $this->config['radarrCinemaRelease'] && isset($child['inCinemas'])) {
|
||||
$releaseDate = $child['inCinemas'];
|
||||
array_push($type, "cinema");
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
$i++;
|
||||
$movieName = $child['title'];
|
||||
$movieID = $child['tmdbId'];
|
||||
if (!isset($movieID)) {
|
||||
$movieID = "";
|
||||
}
|
||||
$releaseDate = strtotime($releaseDate);
|
||||
$releaseDate = date("Y-m-d", $releaseDate);
|
||||
if (new DateTime() < new DateTime($releaseDate)) {
|
||||
$notReleased = "true";
|
||||
} else {
|
||||
$notReleased = "false";
|
||||
}
|
||||
$downloaded = $child['hasFile'];
|
||||
if ($downloaded == "0" && $notReleased == "true") {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
$banner = "/plugins/images/homepage/no-np.png";
|
||||
foreach ($child['images'] as $image) {
|
||||
if ($image['coverType'] == "banner" || $image['coverType'] == "fanart") {
|
||||
if ($image['coverType'] == 'fanart' && (isset($image['remoteUrl']) && $image['remoteUrl'] !== '')) {
|
||||
$banner = $image['remoteUrl'];
|
||||
}elseif (strpos($image['url'], '://') === false) {
|
||||
$imageUrl = $image['url'];
|
||||
$urlParts = explode("/", $url);
|
||||
$imageParts = explode("/", $image['url']);
|
||||
if ($imageParts[1] == end($urlParts)) {
|
||||
unset($imageParts[1]);
|
||||
$imageUrl = implode("/", $imageParts);
|
||||
}
|
||||
$banner = $url . $imageUrl . '?apikey=' . $this->config['radarrToken'];
|
||||
} else {
|
||||
$banner = $image['url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($banner !== "/plugins/images/homepage/no-np.png" && (strpos($banner, 'apikey') !== false)) {
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$imageURL = $banner;
|
||||
$cacheFile = $cacheDirectory . $movieID . '.jpg';
|
||||
$banner = 'data/cache/' . $movieID . '.jpg';
|
||||
if (!file_exists($cacheFile)) {
|
||||
$this->cacheImage($imageURL, $movieID);
|
||||
unset($imageURL);
|
||||
unset($cacheFile);
|
||||
}
|
||||
}
|
||||
$alternativeTitles = "";
|
||||
if (!empty($child['alternativeTitles'])) {
|
||||
foreach ($child['alternativeTitles'] as $alternative) {
|
||||
$alternativeTitles .= $alternative['title'] . ', ';
|
||||
}
|
||||
} elseif (!empty($child['alternateTitles'])) { //v3 API
|
||||
foreach ($child['alternateTitles'] as $alternative) {
|
||||
$alternativeTitles .= $alternative['title'] . ', ';
|
||||
}
|
||||
}
|
||||
$alternativeTitles = empty($alternativeTitles) ? "" : substr($alternativeTitles, 0, -2);
|
||||
$href = $this->config['radarrCalendarLink'] ?? '';
|
||||
if (empty($href) && !empty($this->config['radarrURL'])) {
|
||||
$href_arr = explode(',', $this->config['radarrURL']);
|
||||
$href = reset($href_arr);
|
||||
}
|
||||
if (!empty($href)) {
|
||||
$href = $href . '/movie/' . $movieID;
|
||||
$href = str_replace("//movie/", "/movie/", $href);
|
||||
}
|
||||
$details = array(
|
||||
"topTitle" => $movieName,
|
||||
"bottomTitle" => $alternativeTitles,
|
||||
"status" => $child['status'],
|
||||
"overview" => $child['overview'],
|
||||
"runtime" => $child['runtime'],
|
||||
"image" => $banner,
|
||||
"ratings" => $child['ratings']['value'] ?? 0,
|
||||
"videoQuality" => $child["hasFile"] ? @$child['movieFile']['quality']['quality']['name'] : "unknown",
|
||||
"audioChannels" => $child["hasFile"] ? @$child['movieFile']['mediaInfo']['audioChannels'] : "unknown",
|
||||
"audioCodec" => $child["hasFile"] ? @$child['movieFile']['mediaInfo']['audioFormat'] : "unknown",
|
||||
"videoCodec" => $child["hasFile"] ? @$child['movieFile']['mediaInfo']['videoCodec'] : "unknown",
|
||||
"size" => $child["hasFile"] ? @$child['movieFile']['size'] : "unknown",
|
||||
"genres" => $child['genres'],
|
||||
"year" => $child['year'] ?? '',
|
||||
"studio" => $child['studio'] ?? '',
|
||||
"href" => strtolower($href),
|
||||
"icon" => "/plugins/images/tabs/radarr.png",
|
||||
"frame" => $this->config['radarrFrameTarget'],
|
||||
"showLink" => $this->config['radarrIcon']
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "Radarr-" . $number . "-" . $i,
|
||||
"title" => $movieName,
|
||||
"start" => $releaseDate,
|
||||
"className" => "inline-popups bg-calendar movieID--" . $movieID,
|
||||
"imagetype" => "film " . $downloaded,
|
||||
"imagetypeFilter" => "film",
|
||||
"downloadFilter" => $downloaded,
|
||||
"releaseType" => $type,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details
|
||||
));
|
||||
}
|
||||
}
|
||||
if ($i != 0) {
|
||||
return $gotCalendar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
trait RTorrentHomepageItem
|
||||
{
|
||||
public function rTorrentSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'rTorrent',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/rTorrent.png',
|
||||
'category' => 'Downloader',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$xmlStatus = (extension_loaded('xmlrpc')) ? 'Installed' : 'Not Installed';
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'FYI' => [
|
||||
$this->settingsOption('html', null, ['label' => '', 'override' => 12,
|
||||
'html' => '
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading">
|
||||
<span lang="en">ATTENTION</span>
|
||||
</div>
|
||||
<div class="panel-wrapper collapse in" aria-expanded="true">
|
||||
<div class="panel-body">
|
||||
<h4 lang="en">This module requires XMLRPC</h4>
|
||||
<span lang="en">Status: [ <b>' . $xmlStatus . '</b> ]</span>
|
||||
<br/></br>
|
||||
<span lang="en">
|
||||
<h4><b>Note about API URL</b></h4>
|
||||
Organizr appends the url with <code>/RPC2</code> unless the URL ends in <code>.php</code><br/>
|
||||
<h5>Possible URLs:</h5>
|
||||
<li>http://localhost:8080</li>
|
||||
<li>https://domain.site/xmlrpc.php</li>
|
||||
<li>https://seedbox.site/rutorrent/plugins/httprpc/action.php</li>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
'
|
||||
]),
|
||||
],
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepagerTorrentEnabled'),
|
||||
$this->settingsOption('auth', 'homepagerTorrentAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'rTorrentURL'),
|
||||
$this->settingsOption('input', 'rTorrentURLOverride', ['label' => 'rTorrent API URL Override', 'help' => 'Only use if you cannot connect. Please make sure to use local IP address and port - You also may use local dns name too.', 'placeholder' => 'http(s)://hostname:port/xmlrpc']),
|
||||
$this->settingsOption('username', 'rTorrentUsername'),
|
||||
$this->settingsOption('password', 'rTorrentPassword'),
|
||||
$this->settingsOption('disable-cert-check', 'rTorrentDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'rTorrentUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('hide-seeding', 'rTorrentHideSeeding'),
|
||||
$this->settingsOption('hide-completed', 'rTorrentHideCompleted'),
|
||||
$this->settingsOption('select', 'rTorrentSortOrder', ['label' => 'Order', 'options' => $this->rTorrentSortOptions()]),
|
||||
$this->settingsOption('limit', 'rTorrentLimit'),
|
||||
$this->settingsOption('multiple', 'rTorrentIgnoreLabel', ['label' => 'Ignore Torrent with Label(s)']),
|
||||
$this->settingsOption('refresh', 'rTorrentRefresh'),
|
||||
$this->settingsOption('combine', 'rTorrentCombine'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'rtorrent'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionRTorrent()
|
||||
{
|
||||
if (empty($this->config['rTorrentURL']) && empty($this->config['rTorrentURLOverride'])) {
|
||||
$this->setAPIResponse('error', 'rTorrent URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$url = $this->rTorrentURL();
|
||||
$options = $this->requestOptions($url, null, $this->config['rTorrentDisableCertCheck'], $this->config['rTorrentUseCustomCertificate']);
|
||||
if ($this->config['rTorrentUsername'] !== '' && $this->decrypt($this->config['rTorrentPassword']) !== '') {
|
||||
$credentials = array('auth' => new Requests_Auth_Digest(array($this->config['rTorrentUsername'], $this->decrypt($this->config['rTorrentPassword']))));
|
||||
$options = array_merge($options, $credentials);
|
||||
}
|
||||
$data = xmlrpc_encode_request("system.listMethods", null);
|
||||
$response = Requests::post($url, [], $data, $options);
|
||||
if ($response->success) {
|
||||
$methods = xmlrpc_decode(str_replace('i8>', 'i4>', $response->body));
|
||||
if (count($methods) !== 0) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$this->setAPIResponse('error', 'rTorrent error occurred', 500);
|
||||
return false;
|
||||
} catch
|
||||
(Requests_Exception $e) {
|
||||
$this->setLoggerChannel('rTorrent')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function rTorrentHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepagerTorrentEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepagerTorrentAuth'
|
||||
],
|
||||
'not_empty' => []
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderrTorrent()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->rTorrentHomepagePermissions('main'))) {
|
||||
$loadingBox = ($this->config['rTorrentCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['rTorrentCombine']) ? 'buildDownloaderCombined(\'rTorrent\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("rTorrent"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrderrTorrent
|
||||
' . $builder . '
|
||||
homepageDownloader("rTorrent", "' . $this->config['rTorrentRefresh'] . '");
|
||||
// End homepageOrderrTorrent
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function checkOverrideURL($url, $override)
|
||||
{
|
||||
if (strpos($override, $url) !== false) {
|
||||
return $override;
|
||||
} else {
|
||||
return $url . $override;
|
||||
}
|
||||
}
|
||||
|
||||
public function rTorrentStatus($completed, $state, $status)
|
||||
{
|
||||
if ($completed && $state && $status == 'seed') {
|
||||
$state = 'Seeding';
|
||||
} elseif (!$completed && !$state && $status == 'leech') {
|
||||
$state = 'Stopped';
|
||||
} elseif (!$completed && $state && $status == 'leech') {
|
||||
$state = 'Downloading';
|
||||
} elseif ($completed && !$state && $status == 'seed') {
|
||||
$state = 'Finished';
|
||||
} elseif ($completed && !$state && $status == 'leech') {
|
||||
$state = 'Finished';
|
||||
}
|
||||
return ($state) ?: $status;
|
||||
}
|
||||
|
||||
public function getRTorrentHomepageQueue()
|
||||
{
|
||||
if (empty($this->config['rTorrentURL']) && empty($this->config['rTorrentURLOverride'])) {
|
||||
$this->setAPIResponse('error', 'rTorrent URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (!$this->homepageItemPermissions($this->rTorrentHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if ($this->config['rTorrentLimit'] == '0') {
|
||||
$this->config['rTorrentLimit'] = '1000';
|
||||
}
|
||||
$torrents = array();
|
||||
$url = $this->rTorrentURL();
|
||||
$options = $this->requestOptions($url, $this->config['rTorrentRefresh'], $this->config['rTorrentDisableCertCheck'], $this->config['rTorrentUseCustomCertificate']);
|
||||
if ($this->config['rTorrentUsername'] !== '' && $this->decrypt($this->config['rTorrentPassword']) !== '') {
|
||||
$credentials = array('auth' => new Requests_Auth_Digest(array($this->config['rTorrentUsername'], $this->decrypt($this->config['rTorrentPassword']))));
|
||||
$options = array_merge($options, $credentials);
|
||||
}
|
||||
$data = xmlrpc_encode_request("d.multicall2", array(
|
||||
"",
|
||||
"main",
|
||||
"d.name=",
|
||||
"d.base_path=",
|
||||
"d.up.total=",
|
||||
"d.size_bytes=",
|
||||
"d.down.total=",
|
||||
"d.completed_bytes=",
|
||||
"d.connection_current=",
|
||||
"d.down.rate=",
|
||||
"d.up.rate=",
|
||||
"d.timestamp.started=",
|
||||
"d.state=",
|
||||
"d.group.name=",
|
||||
"d.hash=",
|
||||
"d.complete=",
|
||||
"d.ratio=",
|
||||
"d.chunk_size=",
|
||||
"f.size_bytes=",
|
||||
"f.size_chunks=",
|
||||
"f.completed_chunks=",
|
||||
"d.custom=",
|
||||
"d.custom1=",
|
||||
"d.custom2=",
|
||||
"d.custom3=",
|
||||
"d.custom4=",
|
||||
"d.custom5=",
|
||||
), array());
|
||||
$response = Requests::post($url, [], $data, $options);
|
||||
if ($response->success) {
|
||||
$torrentList = xmlrpc_decode(str_replace('i8>', 'string>', $response->body));
|
||||
if (is_array($torrentList)) {
|
||||
foreach ($torrentList as $key => $value) {
|
||||
$tempStatus = $this->rTorrentStatus($value[13], $value[10], $value[6]);
|
||||
if ($tempStatus == 'Seeding' && $this->config['rTorrentHideSeeding']) {
|
||||
//do nothing
|
||||
} elseif ($tempStatus == 'Finished' && $this->config['rTorrentHideCompleted']) {
|
||||
//do nothing
|
||||
} elseif (stripos($this->config['rTorrentIgnoreLabel'], $value[20]) !== false) {
|
||||
//do nothing
|
||||
} else {
|
||||
$torrents[$key] = array(
|
||||
'name' => $value[0],
|
||||
'base' => $value[1],
|
||||
'upTotal' => $value[2],
|
||||
'size' => $value[3],
|
||||
'downTotal' => $value[4],
|
||||
'downloaded' => $value[5],
|
||||
'connectionState' => $value[6],
|
||||
'leech' => $value[7],
|
||||
'seed' => $value[8],
|
||||
'date' => $value[9],
|
||||
'state' => ($value[10]) ? 'on' : 'off',
|
||||
'group' => $value[11],
|
||||
'hash' => $value[12],
|
||||
'complete' => ($value[13]) ? 'yes' : 'no',
|
||||
'ratio' => $value[14],
|
||||
'label' => $value[20],
|
||||
'status' => $tempStatus,
|
||||
'temp' => $value[16] . ' - ' . $value[17] . ' - ' . $value[18],
|
||||
'custom' => $value[19] . ' - ' . $value[20] . ' - ' . $value[21],
|
||||
'custom2' => $value[22] . ' - ' . $value[23] . ' - ' . $value[24],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count($torrents) !== 0) {
|
||||
usort($torrents, function ($a, $b) {
|
||||
$direction = substr($this->config['rTorrentSortOrder'], -1);
|
||||
$sort = substr($this->config['rTorrentSortOrder'], 0, strlen($this->config['rTorrentSortOrder']) - 1);
|
||||
switch ($direction) {
|
||||
case 'a':
|
||||
return $a[$sort] <=> $b[$sort];
|
||||
case 'd':
|
||||
return $b[$sort] <=> $a[$sort];
|
||||
default:
|
||||
return $b['date'] <=> $a['date'];
|
||||
}
|
||||
});
|
||||
$torrents = array_slice($torrents, 0, $this->config['rTorrentLimit']);
|
||||
}
|
||||
$api['content']['queueItems'] = $torrents;
|
||||
$api['content']['historyItems'] = false;
|
||||
}
|
||||
} catch
|
||||
(Requests_Exception $e) {
|
||||
$this->setLoggerChannel('rTorrent')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function rTorrentURL(): string
|
||||
{
|
||||
$digest = (empty($this->config['rTorrentURLOverride'])) ? $this->qualifyURL($this->config['rTorrentURL'], true) : $this->qualifyURL($this->checkOverrideURL($this->config['rTorrentURL'], $this->config['rTorrentURLOverride']), true);
|
||||
$extraPath = (strpos($this->config['rTorrentURL'], '.php') !== false) ? '' : '/RPC2';
|
||||
$extraPath = (empty($this->config['rTorrentURLOverride'])) ? $extraPath : '';
|
||||
return $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $extraPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
trait SabNZBdHomepageItem
|
||||
{
|
||||
|
||||
public function sabNZBdSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'SabNZBD',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/sabnzbd.png',
|
||||
'category' => 'Downloader',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageSabnzbdEnabled'),
|
||||
$this->settingsOption('auth', 'homepageSabnzbdAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'sabnzbdURL'),
|
||||
$this->settingsOption('token', 'sabnzbdToken'),
|
||||
$this->settingsOption('disable-cert-check', 'sabnzbdDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'sabnzbdUseCustomCertificate'),
|
||||
],
|
||||
'API SOCKS' => [
|
||||
$this->settingsOption('socks', 'sabnzbd'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('enable', 'sabnzbdSocksEnabled'),
|
||||
$this->settingsOption('auth', 'sabnzbdSocksAuth'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('refresh', 'sabnzbdRefresh'),
|
||||
$this->settingsOption('combine', 'sabnzbdCombine'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'sabnzbd'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionSabNZBd()
|
||||
{
|
||||
$this->setLoggerChannel('Sabnzbd Homepage');
|
||||
$this->logger->debug('Starting API Connection Test');
|
||||
if (!empty($this->config['sabnzbdURL']) && !empty($this->config['sabnzbdToken'])) {
|
||||
$url = $this->qualifyURL($this->config['sabnzbdURL']);
|
||||
$url = $url . '/api?mode=queue&output=json&apikey=' . $this->config['sabnzbdToken'];
|
||||
try {
|
||||
$options = $this->requestOptions($url, null, $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$data = json_decode($response->body, true);
|
||||
$status = 'success';
|
||||
$responseCode = 200;
|
||||
$message = 'API Connection succeeded';
|
||||
if (isset($data['error'])) {
|
||||
$status = 'error';
|
||||
$responseCode = 500;
|
||||
$message = $data['error'];
|
||||
}
|
||||
$this->setAPIResponse($status, $message, $responseCode, $data);
|
||||
$this->logger->debug('API Connection Test was successful');
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', $response->body, 500);
|
||||
$this->logger->debug('API Connection Test was unsuccessful');
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->logger->critical($e, [$url]);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->logger->debug('URL and/or Token not setup');
|
||||
$this->setAPIResponse('error', 'URL and/or Token not setup', 422);
|
||||
return 'URL and/or Token not setup';
|
||||
}
|
||||
}
|
||||
|
||||
public function sabNZBdHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageSabnzbdEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageSabnzbdAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'sabnzbdURL',
|
||||
'sabnzbdToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrdersabnzbd()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->sabNZBdHomepagePermissions('main'))) {
|
||||
$loadingBox = ($this->config['sabnzbdCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['sabnzbdCombine']) ? 'buildDownloaderCombined(\'sabnzbd\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("sabnzbd"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrdersabnzbd
|
||||
' . $builder . '
|
||||
homepageDownloader("sabnzbd", "' . $this->config['sabnzbdRefresh'] . '");
|
||||
// End homepageOrdersabnzbd
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getSabNZBdHomepageQueue()
|
||||
{
|
||||
$this->setLoggerChannel('Sabnzbd Homepage');
|
||||
if (!$this->homepageItemPermissions($this->sabNZBdHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['sabnzbdURL']);
|
||||
$url = $url . '/api?mode=queue&output=json&apikey=' . $this->config['sabnzbdToken'];
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$api['content']['queueItems'] = json_decode($response->body, true);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->logger->critical($e, [$url]);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$url = $this->qualifyURL($this->config['sabnzbdURL']);
|
||||
$url = $url . '/api?mode=history&output=json&limit=100&apikey=' . $this->config['sabnzbdToken'];
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
|
||||
$response = Requests::get($url, array(), $options);
|
||||
if ($response->success) {
|
||||
$api['content']['historyItems'] = json_decode($response->body, true);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->logger->critical($e, [$url]);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function pauseSabNZBdQueue($target = null)
|
||||
{
|
||||
$this->setLoggerChannel('Sabnzbd Homepage');
|
||||
if (!$this->homepageItemPermissions($this->sabNZBdHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['sabnzbdURL']);
|
||||
$id = ($target !== '' && $target !== 'main' && isset($target)) ? 'mode=queue&name=pause&value=' . $target . '&' : 'mode=pause';
|
||||
$url = $url . '/api?' . $id . '&output=json&apikey=' . $this->config['sabnzbdToken'];
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$api['content'] = json_decode($response->body, true);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->logger->critical($e, [$url]);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function resumeSabNZBdQueue($target = null)
|
||||
{
|
||||
$this->setLoggerChannel('Sabnzbd Homepage');
|
||||
if (!$this->homepageItemPermissions($this->sabNZBdHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['sabnzbdURL']);
|
||||
$id = ($target !== '' && $target !== 'main' && isset($target)) ? 'mode=queue&name=resume&value=' . $target . '&' : 'mode=resume';
|
||||
$url = $url . '/api?' . $id . '&output=json&apikey=' . $this->config['sabnzbdToken'];
|
||||
try {
|
||||
$options = $this->requestOptions($url, $this->config['sabnzbdRefresh'], $this->config['sabnzbdDisableCertCheck'], $this->config['sabnzbdUseCustomCertificate']);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
$api['content'] = json_decode($response->body, true);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->logger->critical($e, [$url]);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
<?php
|
||||
|
||||
trait SickRageHomepageItem
|
||||
{
|
||||
public function sickrageSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'SickRage',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/sickrage.png',
|
||||
'category' => 'PVR',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageSickrageEnabled'),
|
||||
$this->settingsOption('auth', 'homepageSickrageAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'sickrageURL'),
|
||||
$this->settingsOption('token', 'sickrageToken'),
|
||||
$this->settingsOption('disable-cert-check', 'sickrageDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'sickrageUseCustomCertificate'),
|
||||
],
|
||||
'Calendar' => [
|
||||
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
|
||||
$this->settingsOption('calendar-default-view', 'calendarDefault'),
|
||||
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
|
||||
$this->settingsOption('calendar-locale', 'calendarLocale'),
|
||||
$this->settingsOption('calendar-limit', 'calendarLimit'),
|
||||
$this->settingsOption('refresh', 'calendarRefresh'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'sickrage'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionSickRage()
|
||||
{
|
||||
if (empty($this->config['sickrageURL'])) {
|
||||
$this->setAPIResponse('error', 'SickRage URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['sickrageToken'])) {
|
||||
$this->setAPIResponse('error', 'SickRage Token is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$failed = false;
|
||||
$errors = '';
|
||||
$list = $this->csvHomepageUrlToken($this->config['sickrageURL'], $this->config['sickrageToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], null, $this->config['sickrageDisableCertCheck'], $this->config['sickrageUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\SickRage\SickRage($value['url'], $value['token'], null, null, $options);
|
||||
$results = $downloader->sb();
|
||||
$downloadList = json_decode($results, true);
|
||||
if (is_array($downloadList) || is_object($downloadList)) {
|
||||
$queue = (array_key_exists('error', $downloadList)) ? $downloadList['error']['msg'] : $downloadList;
|
||||
if (!is_array($queue)) {
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': ' . $queue;
|
||||
$failed = true;
|
||||
}
|
||||
} else {
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': Response was not JSON';
|
||||
$failed = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$failed = true;
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': ' . $e->getMessage();
|
||||
$this->setLoggerChannel('SickRage')->error($e);
|
||||
}
|
||||
}
|
||||
if ($failed) {
|
||||
$this->setAPIResponse('error', $errors, 500);
|
||||
return false;
|
||||
} else {
|
||||
$this->setAPIResponse('success', null, 200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function sickrageHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'calendar' => [
|
||||
'enabled' => [
|
||||
'homepageSickrageEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageSickrageAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'sickrageURL',
|
||||
'sickrageToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function getSickRageCalendar($startDate = null, $endDate = null)
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->sickrageHomepagePermissions('calendar'), true)) {
|
||||
return false;
|
||||
}
|
||||
$calendarItems = array();
|
||||
$list = $this->csvHomepageUrlToken($this->config['sickrageURL'], $this->config['sickrageToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], null, $this->config['sickrageDisableCertCheck'], $this->config['sickrageUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\SickRage\SickRage($value['url'], $value['token'], null, null, $options);
|
||||
$sickrageFuture = $this->formatSickrageCalendarWanted($downloader->future(), $key);
|
||||
$sickrageHistory = $this->formatSickrageCalendarHistory($downloader->history("100", "downloaded"), $key);
|
||||
if (!empty($sickrageFuture)) {
|
||||
$calendarItems = array_merge($calendarItems, $sickrageFuture);
|
||||
}
|
||||
if (!empty($sickrageHistory)) {
|
||||
$calendarItems = array_merge($calendarItems, $sickrageHistory);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('SickRage')->error($e);
|
||||
}
|
||||
}
|
||||
$this->setAPIResponse('success', null, 200, $calendarItems);
|
||||
return $calendarItems;
|
||||
}
|
||||
|
||||
public function formatSickrageCalendarWanted($array, $number)
|
||||
{
|
||||
$array = json_decode($array, true);
|
||||
$gotCalendar = array();
|
||||
$i = 0;
|
||||
foreach ($array['data']['missed'] as $child) {
|
||||
$i++;
|
||||
$seriesName = $child['show_name'];
|
||||
$seriesID = $child['tvdbid'];
|
||||
$episodeID = $child['tvdbid'];
|
||||
$episodeAirDate = $child['airdate'];
|
||||
$episodeAirDateTime = explode(" ", $child['airs']);
|
||||
$episodeAirDateTime = date("H:i:s", strtotime($episodeAirDateTime[1] . $episodeAirDateTime[2]));
|
||||
$episodeAirDate = strtotime($episodeAirDate . $episodeAirDateTime);
|
||||
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
|
||||
if (new DateTime() < new DateTime($episodeAirDate)) {
|
||||
$unaired = true;
|
||||
}
|
||||
$downloaded = "0";
|
||||
if ($downloaded == "0" && isset($unaired)) {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']) . ' - ' . $child['ep_name'];
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
|
||||
$fanart = "/plugins/images/homepage/no-np.png";
|
||||
if (file_exists($cacheFile)) {
|
||||
$fanart = 'data/cache/' . $seriesID . '.jpg';
|
||||
unset($cacheFile);
|
||||
}
|
||||
$details = array(
|
||||
"seasonCount" => "",
|
||||
"status" => $child['show_status'],
|
||||
"topTitle" => $seriesName,
|
||||
"bottomTitle" => $bottomTitle,
|
||||
"overview" => isset($child['ep_plot']) ? $child['ep_plot'] : '',
|
||||
"runtime" => "",
|
||||
"image" => $fanart,
|
||||
"ratings" => "",
|
||||
"videoQuality" => isset($child["quality"]) ? $child["quality"] : "",
|
||||
"audioChannels" => "",
|
||||
"audioCodec" => "",
|
||||
"videoCodec" => "",
|
||||
"size" => "",
|
||||
"genres" => "",
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "Sick-" . $number . "-Miss-" . $i,
|
||||
"title" => $seriesName,
|
||||
"start" => $episodeAirDate,
|
||||
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
|
||||
"imagetype" => "tv " . $downloaded,
|
||||
"imagetypeFilter" => "tv",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details,
|
||||
));
|
||||
}
|
||||
foreach ($array['data']['today'] as $child) {
|
||||
$i++;
|
||||
$seriesName = $child['show_name'];
|
||||
$seriesID = $child['tvdbid'];
|
||||
$episodeID = $child['tvdbid'];
|
||||
$episodeAirDate = $child['airdate'];
|
||||
$episodeAirDateTime = explode(" ", $child['airs']);
|
||||
$episodeAirDateTime = date("H:i:s", strtotime($episodeAirDateTime[1] . $episodeAirDateTime[2]));
|
||||
$episodeAirDate = strtotime($episodeAirDate . $episodeAirDateTime);
|
||||
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
|
||||
if (new DateTime() < new DateTime($episodeAirDate)) {
|
||||
$unaired = true;
|
||||
}
|
||||
$downloaded = "0";
|
||||
if ($downloaded == "0" && isset($unaired)) {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']) . ' - ' . $child['ep_name'];
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
|
||||
$fanart = "/plugins/images/homepage/no-np.png";
|
||||
if (file_exists($cacheFile)) {
|
||||
$fanart = 'data/cache/' . $seriesID . '.jpg';
|
||||
unset($cacheFile);
|
||||
}
|
||||
$details = array(
|
||||
"seasonCount" => "",
|
||||
"status" => $child['show_status'],
|
||||
"topTitle" => $seriesName,
|
||||
"bottomTitle" => $bottomTitle,
|
||||
"overview" => isset($child['ep_plot']) ? $child['ep_plot'] : '',
|
||||
"runtime" => "",
|
||||
"image" => $fanart,
|
||||
"ratings" => "",
|
||||
"videoQuality" => isset($child["quality"]) ? $child["quality"] : "",
|
||||
"audioChannels" => "",
|
||||
"audioCodec" => "",
|
||||
"videoCodec" => "",
|
||||
"size" => "",
|
||||
"genres" => "",
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "Sick-" . $number . "-Today-" . $i,
|
||||
"title" => $seriesName,
|
||||
"start" => $episodeAirDate,
|
||||
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
|
||||
"imagetype" => "tv " . $downloaded,
|
||||
"imagetypeFilter" => "tv",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details,
|
||||
));
|
||||
}
|
||||
foreach ($array['data']['soon'] as $child) {
|
||||
$i++;
|
||||
$seriesName = $child['show_name'];
|
||||
$seriesID = $child['tvdbid'];
|
||||
$episodeID = $child['tvdbid'];
|
||||
$episodeAirDate = $child['airdate'];
|
||||
$episodeAirDateTime = explode(" ", $child['airs']);
|
||||
$episodeAirDateTime = date("H:i:s", strtotime($episodeAirDateTime[1] . $episodeAirDateTime[2]));
|
||||
$episodeAirDate = strtotime($episodeAirDate . $episodeAirDateTime);
|
||||
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
|
||||
if (new DateTime() < new DateTime($episodeAirDate)) {
|
||||
$unaired = true;
|
||||
}
|
||||
$downloaded = "0";
|
||||
if ($downloaded == "0" && isset($unaired)) {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']) . ' - ' . $child['ep_name'];
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
|
||||
$fanart = "/plugins/images/homepage/no-np.png";
|
||||
if (file_exists($cacheFile)) {
|
||||
$fanart = 'data/cache/' . $seriesID . '.jpg';
|
||||
unset($cacheFile);
|
||||
}
|
||||
$details = array(
|
||||
"seasonCount" => "",
|
||||
"status" => $child['show_status'],
|
||||
"topTitle" => $seriesName,
|
||||
"bottomTitle" => $bottomTitle,
|
||||
"overview" => isset($child['ep_plot']) ? $child['ep_plot'] : '',
|
||||
"runtime" => "",
|
||||
"image" => $fanart,
|
||||
"ratings" => "",
|
||||
"videoQuality" => isset($child["quality"]) ? $child["quality"] : "",
|
||||
"audioChannels" => "",
|
||||
"audioCodec" => "",
|
||||
"videoCodec" => "",
|
||||
"size" => "",
|
||||
"genres" => "",
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "Sick-" . $number . "-Soon-" . $i,
|
||||
"title" => $seriesName,
|
||||
"start" => $episodeAirDate,
|
||||
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
|
||||
"imagetype" => "tv " . $downloaded,
|
||||
"imagetypeFilter" => "tv",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details,
|
||||
));
|
||||
}
|
||||
foreach ($array['data']['later'] as $child) {
|
||||
$i++;
|
||||
$seriesName = $child['show_name'];
|
||||
$seriesID = $child['tvdbid'];
|
||||
$episodeID = $child['tvdbid'];
|
||||
$episodeAirDate = $child['airdate'];
|
||||
$episodeAirDateTime = explode(" ", $child['airs']);
|
||||
$episodeAirDateTime = date("H:i:s", strtotime($episodeAirDateTime[1] . $episodeAirDateTime[2]));
|
||||
$episodeAirDate = strtotime($episodeAirDate . $episodeAirDateTime);
|
||||
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
|
||||
if (new DateTime() < new DateTime($episodeAirDate)) {
|
||||
$unaired = true;
|
||||
}
|
||||
$downloaded = "0";
|
||||
if ($downloaded == "0" && isset($unaired)) {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']) . ' - ' . $child['ep_name'];
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
|
||||
$fanart = "/plugins/images/homepage/no-np.png";
|
||||
if (file_exists($cacheFile)) {
|
||||
$fanart = 'data/cache/' . $seriesID . '.jpg';
|
||||
unset($cacheFile);
|
||||
}
|
||||
$details = array(
|
||||
"seasonCount" => "",
|
||||
"status" => $child['show_status'],
|
||||
"topTitle" => $seriesName,
|
||||
"bottomTitle" => $bottomTitle,
|
||||
"overview" => isset($child['ep_plot']) ? $child['ep_plot'] : '',
|
||||
"runtime" => "",
|
||||
"image" => $fanart,
|
||||
"ratings" => "",
|
||||
"videoQuality" => isset($child["quality"]) ? $child["quality"] : "",
|
||||
"audioChannels" => "",
|
||||
"audioCodec" => "",
|
||||
"videoCodec" => "",
|
||||
"size" => "",
|
||||
"genres" => "",
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "Sick-" . $number . "-Later-" . $i,
|
||||
"title" => $seriesName,
|
||||
"start" => $episodeAirDate,
|
||||
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
|
||||
"imagetype" => "tv " . $downloaded,
|
||||
"imagetypeFilter" => "tv",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details,
|
||||
));
|
||||
}
|
||||
if ($i != 0) {
|
||||
return $gotCalendar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function formatSickrageCalendarHistory($array, $number)
|
||||
{
|
||||
$array = json_decode($array, true);
|
||||
$gotCalendar = array();
|
||||
$i = 0;
|
||||
foreach ($array['data'] as $child) {
|
||||
$i++;
|
||||
$seriesName = $child['show_name'];
|
||||
$seriesID = $child['tvdbid'];
|
||||
$episodeID = $child['tvdbid'];
|
||||
$episodeAirDate = $child['date'];
|
||||
$downloaded = "text-success";
|
||||
$bottomTitle = 'S' . sprintf("%02d", $child['season']) . 'E' . sprintf("%02d", $child['episode']);
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$cacheFile = $cacheDirectory . $seriesID . '.jpg';
|
||||
$fanart = "/plugins/images/homepage/no-np.png";
|
||||
if (file_exists($cacheFile)) {
|
||||
$fanart = 'data/cache/' . $seriesID . '.jpg';
|
||||
unset($cacheFile);
|
||||
}
|
||||
$details = array(
|
||||
"seasonCount" => "",
|
||||
"status" => $child['status'],
|
||||
"topTitle" => $seriesName,
|
||||
"bottomTitle" => $bottomTitle,
|
||||
"overview" => '',
|
||||
"runtime" => isset($child['series']['runtime']) ? $child['series']['runtime'] : 30,
|
||||
"image" => $fanart,
|
||||
"ratings" => isset($child['series']['ratings']['value']) ? $child['series']['ratings']['value'] : "unknown",
|
||||
"videoQuality" => isset($child["quality"]) ? $child['quality'] : "unknown",
|
||||
"audioChannels" => "",
|
||||
"audioCodec" => "",
|
||||
"videoCodec" => "",
|
||||
"size" => "",
|
||||
"genres" => "",
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "Sick-" . $number . "-History-" . $i,
|
||||
"title" => $seriesName,
|
||||
"start" => $episodeAirDate,
|
||||
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
|
||||
"imagetype" => "tv " . $downloaded,
|
||||
"imagetypeFilter" => "tv",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details,
|
||||
));
|
||||
}
|
||||
if ($i != 0) {
|
||||
return $gotCalendar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
trait SonarrHomepageItem
|
||||
{
|
||||
public function sonarrSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Sonarr',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/sonarr.png',
|
||||
'category' => 'PVR',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'docs' => $this->docs('features/homepage/sonarr-homepage-item'),
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'About' => [
|
||||
$this->settingsOption('about', 'Sonarr', ['about' => 'This item allows access to Sonarr\'s calendar data and aggregates it to Organizr\'s calendar. Along with that you also have the Downloader function that allow access to Sonarr\'s queue. The last item that is included is the API SOCKS function which acts as a middleman between API\'s which is useful if you are not port forwarding or reverse proxying Sonarr.']),
|
||||
],
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageSonarrEnabled'),
|
||||
$this->settingsOption('auth', 'homepageSonarrAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('multiple-url', 'sonarrURL'),
|
||||
$this->settingsOption('multiple-token', 'sonarrToken'),
|
||||
$this->settingsOption('disable-cert-check', 'sonarrDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'sonarrUseCustomCertificate'),
|
||||
],
|
||||
'API SOCKS' => [
|
||||
$this->settingsOption('socks', 'sonarr'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('enable', 'sonarrSocksEnabled'),
|
||||
$this->settingsOption('auth', 'sonarrSocksAuth'),
|
||||
],
|
||||
'Queue' => [
|
||||
$this->settingsOption('enable', 'homepageSonarrQueueEnabled'),
|
||||
$this->settingsOption('auth', 'homepageSonarrQueueAuth'),
|
||||
$this->settingsOption('combine', 'homepageSonarrQueueCombine'),
|
||||
$this->settingsOption('refresh', 'homepageSonarrQueueRefresh'),
|
||||
],
|
||||
'Calendar' => [
|
||||
$this->settingsOption('calendar-start', 'calendarStart'),
|
||||
$this->settingsOption('calendar-end', 'calendarEnd'),
|
||||
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
|
||||
$this->settingsOption('calendar-default-view', 'calendarDefault'),
|
||||
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
|
||||
$this->settingsOption('calendar-locale', 'calendarLocale'),
|
||||
$this->settingsOption('calendar-limit', 'calendarLimit'),
|
||||
$this->settingsOption('refresh', 'calendarRefresh'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('switch', 'sonarrUnmonitored', ['label' => 'Show Unmonitored']),
|
||||
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
|
||||
$this->settingsOption('blank', '', ['type' => 'html', 'html' => '<hr />']),
|
||||
$this->settingsOption('enable', 'sonarrIcon', ['label' => 'Show Sonarr Icon']),
|
||||
$this->settingsOption('calendar-link-url', 'sonarrCalendarLink'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('calendar-frame-target', 'sonarrFrameTarget')
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'sonarr'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionSonarr()
|
||||
{
|
||||
if (empty($this->config['sonarrURL'])) {
|
||||
$this->setAPIResponse('error', 'Sonarr URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['sonarrToken'])) {
|
||||
$this->setAPIResponse('error', 'Sonarr Token is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$failed = false;
|
||||
$errors = '';
|
||||
$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], null, $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
|
||||
$results = $downloader->getRootFolder();
|
||||
$downloadList = json_decode($results, true);
|
||||
if (is_array($downloadList) || is_object($downloadList)) {
|
||||
$queue = (array_key_exists('error', $downloadList)) ? $downloadList['error']['msg'] : $downloadList;
|
||||
if (!is_array($queue)) {
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': ' . $queue;
|
||||
$failed = true;
|
||||
}
|
||||
} else {
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': Response was not JSON';
|
||||
$failed = true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$failed = true;
|
||||
$ip = $value['url'];
|
||||
$errors .= $ip . ': ' . $e->getMessage();
|
||||
$this->setLoggerChannel('Sonarr')->error($e);
|
||||
}
|
||||
}
|
||||
if ($failed) {
|
||||
$this->setAPIResponse('error', $errors, 500);
|
||||
return false;
|
||||
} else {
|
||||
$this->setAPIResponse('success', null, 200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function sonarrHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'calendar' => [
|
||||
'enabled' => [
|
||||
'homepageSonarrEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageSonarrAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'sonarrURL',
|
||||
'sonarrToken'
|
||||
]
|
||||
],
|
||||
'queue' => [
|
||||
'enabled' => [
|
||||
'homepageSonarrEnabled',
|
||||
'homepageSonarrQueueEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageSonarrAuth',
|
||||
'homepageSonarrQueueAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'sonarrURL',
|
||||
'sonarrToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderSonarrQueue()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->sonarrHomepagePermissions('queue'))) {
|
||||
$loadingBox = ($this->config['homepageSonarrQueueCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['homepageSonarrQueueCombine']) ? 'buildDownloaderCombined(\'sonarr\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("sonarr"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrderSonarrQueue
|
||||
' . $builder . '
|
||||
homepageDownloader("sonarr", "' . $this->config['homepageSonarrQueueRefresh'] . '");
|
||||
// End homepageOrderSonarrQueue
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getSonarrQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->sonarrHomepagePermissions('queue'), true)) {
|
||||
return false;
|
||||
}
|
||||
$queueItems = array();
|
||||
$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], $this->config['homepageSonarrQueueRefresh'], $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
|
||||
$downloader = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
|
||||
$results = $downloader->getQueue();
|
||||
$downloadList = json_decode($results, true);
|
||||
if (is_array($downloadList) || is_object($downloadList)) {
|
||||
$queue = (array_key_exists('error', $downloadList)) ? [] : $downloadList;
|
||||
$queue = $queue['records'] ?? $queue;
|
||||
} else {
|
||||
$queue = [];
|
||||
}
|
||||
if (!empty($queue)) {
|
||||
$queueItems = array_merge($queueItems, $queue);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('Sonarr')->error($e);
|
||||
}
|
||||
}
|
||||
$api['content']['queueItems'] = $queueItems;
|
||||
$api['content']['historyItems'] = false;
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function getSonarrCalendar($startDate = null, $endDate = null)
|
||||
{
|
||||
$startDate = ($startDate) ?? $_GET['start'] ?? date('Y-m-d', strtotime('-' . $this->config['calendarStart'] . ' days'));
|
||||
$endDate = ($endDate) ?? $_GET['end'] ?? date('Y-m-d', strtotime('+' . $this->config['calendarEnd'] . ' days'));
|
||||
if (!$this->homepageItemPermissions($this->sonarrHomepagePermissions('calendar'), true)) {
|
||||
return false;
|
||||
}
|
||||
if ($this->demo) {
|
||||
return $this->demoData('sonarr/calendar.json');
|
||||
}
|
||||
$calendarItems = array();
|
||||
$list = $this->csvHomepageUrlToken($this->config['sonarrURL'], $this->config['sonarrToken']);
|
||||
foreach ($list as $key => $value) {
|
||||
try {
|
||||
$options = $this->requestOptions($value['url'], null, $this->config['sonarrDisableCertCheck'], $this->config['sonarrUseCustomCertificate']);
|
||||
$sonarr = new Kryptonit3\Sonarr\Sonarr($value['url'], $value['token'], 'sonarr', null, null, $options);
|
||||
$sonarr = $sonarr->getCalendar($startDate, $endDate, $this->config['sonarrUnmonitored']);
|
||||
$result = json_decode($sonarr, true);
|
||||
if (is_array($result) || is_object($result)) {
|
||||
$sonarrCalendar = (array_key_exists('error', $result)) ? '' : $this->formatSonarrCalendar($sonarr, $key);
|
||||
} else {
|
||||
$sonarrCalendar = '';
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->setLoggerChannel('Sonarr')->error($e);
|
||||
}
|
||||
if (!empty($sonarrCalendar)) {
|
||||
$calendarItems = array_merge($calendarItems, $sonarrCalendar);
|
||||
}
|
||||
}
|
||||
$this->setAPIResponse('success', null, 200, $calendarItems);
|
||||
return $calendarItems;
|
||||
}
|
||||
|
||||
public function formatSonarrCalendar($array, $number)
|
||||
{
|
||||
$array = json_decode($array, true);
|
||||
$gotCalendar = [];
|
||||
$i = 0;
|
||||
foreach ($array as $child) {
|
||||
$i++;
|
||||
$seriesName = $child['series']['title'];
|
||||
$seriesID = $child['series']['tvdbId'];
|
||||
$episodeID = $child['series']['tvdbId'];
|
||||
$monitored = $child['monitored'];
|
||||
if (!isset($episodeID)) {
|
||||
$episodeID = "";
|
||||
}
|
||||
$episodeAirDate = $child['airDateUtc'];
|
||||
$episodeAirDate = strtotime($episodeAirDate);
|
||||
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
|
||||
if (new DateTime() < new DateTime($episodeAirDate)) {
|
||||
$unAired = true;
|
||||
}
|
||||
if ($child['episodeNumber'] == "1") {
|
||||
$episodePremier = "true";
|
||||
} else {
|
||||
$episodePremier = "false";
|
||||
$date = new DateTime($episodeAirDate);
|
||||
$date->add(new DateInterval("PT1S"));
|
||||
$date->format(DateTime::ATOM);
|
||||
$child['airDateUtc'] = gmdate('Y-m-d\TH:i:s\Z', strtotime($date->format(DateTime::ATOM)));
|
||||
}
|
||||
$downloaded = $child['hasFile'];
|
||||
if ($downloaded == "0" && isset($unAired) && $episodePremier == "true") {
|
||||
$downloaded = "text-primary animated flash";
|
||||
} elseif ($downloaded == "0" && isset($unAired) && $monitored == "0") {
|
||||
$downloaded = "text-dark";
|
||||
} elseif ($downloaded == "0" && isset($unAired)) {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
$fanArt = "/plugins/images/homepage/no-np.png";
|
||||
foreach ($child['series']['images'] as $image) {
|
||||
if ($image['coverType'] == "fanart" && (isset($image['url']) && $image['url'] !== '')) {
|
||||
$fanArt = $image['url'];
|
||||
}
|
||||
if ($image['coverType'] == 'fanart' && (isset($image['remoteUrl']) && $image['remoteUrl'] !== '')) {
|
||||
$fanArt = $image['remoteUrl'];
|
||||
}
|
||||
}
|
||||
if ($fanArt !== "/plugins/images/homepage/no-np.png" || (strpos($fanArt, '://') === false)) {
|
||||
$cacheDirectory = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR;
|
||||
$imageURL = $fanArt;
|
||||
$fanArt = 'data/cache/' . $seriesID . '.jpg';
|
||||
if (!file_exists($cacheDirectory . $seriesID . '.jpg')) {
|
||||
$this->cacheImage($imageURL, $seriesID);
|
||||
unset($imageURL);
|
||||
}
|
||||
}
|
||||
$bottomTitle = 'S' . sprintf("%02d", $child['seasonNumber']) . 'E' . sprintf("%02d", $child['episodeNumber']) . ' - ' . $child['title'];
|
||||
$href = $this->config['sonarrCalendarLink'] ?? '';
|
||||
if (empty($href) && !empty($this->config['sonarrURL'])) {
|
||||
$href_arr = explode(',', $this->config['sonarrURL']);
|
||||
$href = reset($href_arr);
|
||||
}
|
||||
if (!empty($href)) {
|
||||
$href = $href . '/series/' . preg_replace('/[^A-Za-z0-9 -]/', '', str_replace('&', 'and', preg_replace('/[[:space:]]+/', '-', $seriesName)));
|
||||
$href = str_replace("//series/", "/series/", $href);
|
||||
}
|
||||
$details = [
|
||||
"seasonCount" => $child['series']['seasonCount'] ?? isset($child['series']['seasons']) ? count($child['series']['seasons']) : 0,
|
||||
"status" => $child['series']['status'],
|
||||
"topTitle" => $seriesName,
|
||||
"bottomTitle" => $bottomTitle,
|
||||
"overview" => $child['overview'] ?? '',
|
||||
"runtime" => $child['series']['runtime'],
|
||||
"image" => $fanArt,
|
||||
"ratings" => $child['series']['ratings']['value'],
|
||||
"videoQuality" => $child["hasFile"] && isset($child['episodeFile']['quality']['quality']['name']) ? $child['episodeFile']['quality']['quality']['name'] : "unknown",
|
||||
"audioChannels" => $child["hasFile"] && isset($child['episodeFile']['mediaInfo']) ? $child['episodeFile']['mediaInfo']['audioChannels'] : "unknown",
|
||||
"audioCodec" => $child["hasFile"] && isset($child['episodeFile']['mediaInfo']) ? $child['episodeFile']['mediaInfo']['audioCodec'] : "unknown",
|
||||
"videoCodec" => $child["hasFile"] && isset($child['episodeFile']['mediaInfo']) ? $child['episodeFile']['mediaInfo']['videoCodec'] : "unknown",
|
||||
"size" => $child["hasFile"] && isset($child['episodeFile']['size']) ? $child['episodeFile']['size'] : "unknown",
|
||||
"genres" => $child['series']['genres'],
|
||||
"href" => strtolower($href),
|
||||
"icon" => "/plugins/images/tabs/sonarr.png",
|
||||
"frame" => $this->config['sonarrFrameTarget'],
|
||||
"showLink" => $this->config['sonarrIcon']
|
||||
];
|
||||
$gotCalendar[] = [
|
||||
"id" => "Sonarr-" . $number . "-" . $i,
|
||||
"title" => $seriesName,
|
||||
"start" => $child['airDateUtc'],
|
||||
"className" => "inline-popups bg-calendar calendar-item tvID--" . $episodeID,
|
||||
"imagetype" => "tv " . $downloaded,
|
||||
"imagetypeFilter" => "tv",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details
|
||||
];
|
||||
}
|
||||
if ($i != 0) {
|
||||
return $gotCalendar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
trait SpeedTestHomepageItem
|
||||
{
|
||||
public function speedTestSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Speedtest',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/speedtest-icon.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('html', null, ['override' => 6, 'label' => 'Info', 'html' => '<p>This homepage item requires <a href="https://github.com/henrywhitaker3/Speedtest-Tracker" target="_blank" rel="noreferrer noopener">Speedtest-Tracker <i class="fa fa-external-link" aria-hidden="true"></i></a> to be running on your network.</p>']),
|
||||
$this->settingsOption('enable', 'homepageSpeedtestEnabled'),
|
||||
$this->settingsOption('auth', 'homepageSpeedtestAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'speedtestURL'),
|
||||
$this->settingsOption('disable-cert-check', 'speedtestDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'speedtestUseCustomCertificate'),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('title', 'speedtestHeader'),
|
||||
$this->settingsOption('toggle-title', 'speedtestHeaderToggle'),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function speedTestHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageSpeedtestEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageSpeedtestAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'speedtestURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderSpeedtest()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->speedTestHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Speedtest...</h2></div>
|
||||
<script>
|
||||
// Speedtest
|
||||
homepageSpeedtest("' . $this->config['homepageSpeedtestRefresh'] . '");
|
||||
// End Speedtest
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getSpeedtestHomepageData()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->speedTestHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api = [];
|
||||
$url = $this->qualifyURL($this->config['speedtestURL']);
|
||||
$options = $this->requestOptions($url, null, $this->config['speedtestDisableCertCheck'], $this->config['speedtestUseCustomCertificate']);
|
||||
$dataUrl = $url . '/api/speedtest/latest';
|
||||
try {
|
||||
$response = Requests::get($dataUrl, [], $options);
|
||||
if ($response->success) {
|
||||
$json = json_decode($response->body, true);
|
||||
$api['data'] = [
|
||||
'current' => $json['data'],
|
||||
];
|
||||
$keys = [
|
||||
'average',
|
||||
'max',
|
||||
'maximum',
|
||||
'minimum'
|
||||
];
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $json)) {
|
||||
if ($key == 'max') {
|
||||
$api['data']['maximum'] = $json[$key];
|
||||
} else {
|
||||
$api['data'][$key] = $json[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
$api['options'] = [
|
||||
'title' => $this->config['speedtestHeader'],
|
||||
'titleToggle' => $this->config['speedtestHeaderToggle'],
|
||||
];
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'SpeedTest connection error', 409);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Speedtest')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api = isset($api) ? $api : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
trait TautulliHomepageItem
|
||||
{
|
||||
public function tautulliSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Tautulli',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/tautulli.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
|
||||
$libraryList = [['name' => 'Refresh page to update List', 'value' => '', 'disabled' => true]];
|
||||
if (!empty($this->config['tautulliApikey']) && !empty($this->config['tautulliURL'])) {
|
||||
$libraryList = [];
|
||||
$loop = $this->tautulliLibraryList();
|
||||
if ($loop) {
|
||||
$loop = $loop['libraries'];
|
||||
foreach ($loop as $key => $value) {
|
||||
$libraryList[] = ['name' => $key, 'value' => $value];
|
||||
}
|
||||
}
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageTautulliEnabled'),
|
||||
$this->settingsOption('auth', 'homepageTautulliAuth'),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('title', 'tautulliHeader'),
|
||||
$this->settingsOption('toggle-title', 'tautulliHeaderToggle'),
|
||||
$this->settingsOption('refresh', 'homepageTautulliRefresh'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('multiple-url', 'tautulliURL'),
|
||||
$this->settingsOption('multiple-api-key', 'tautulliApikey'),
|
||||
$this->settingsOption('disable-cert-check', 'tautulliDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'tautulliUseCustomCertificate'),
|
||||
],
|
||||
'API SOCKS' => [
|
||||
$this->settingsOption('socks', 'tautulli'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('enable', 'tautulliSocksEnabled'),
|
||||
$this->settingsOption('auth', 'tautulliSocksAuth'),
|
||||
],
|
||||
'Library Stats' => [
|
||||
$this->settingsOption('switch', 'tautulliLibraries', ['label' => 'Libraries', 'help' => 'Shows/hides the card with library information.']),
|
||||
$this->settingsOption('auth', 'homepageTautulliLibraryAuth'),
|
||||
$this->settingsOption('plex-library-exclude', 'homepageTautulliLibraryStatsExclude', ['options' => $libraryList]),
|
||||
],
|
||||
'Viewing Stats' => [
|
||||
$this->settingsOption('switch', 'tautulliPopularMovies', ['label' => 'Popular Movies', 'help' => 'Shows/hides the card with Popular Movie information.']),
|
||||
$this->settingsOption('switch', 'tautulliPopularTV', ['label' => 'Popular TV', 'help' => 'Shows/hides the card with Popular TV information.']),
|
||||
$this->settingsOption('switch', 'tautulliTopMovies', ['label' => 'Top Movies', 'help' => 'Shows/hides the card with Top Movies information.']),
|
||||
$this->settingsOption('switch', 'tautulliTopTV', ['label' => 'Top TV', 'help' => 'Shows/hides the card with Top TV information.']),
|
||||
$this->settingsOption('auth', 'homepageTautulliViewsAuth'),
|
||||
$this->settingsOption('plex-library-exclude', 'homepageTautulliViewingStatsExclude', ['options' => $libraryList]),
|
||||
],
|
||||
'Misc Stats' => [
|
||||
$this->settingsOption('switch', 'tautulliTopUsers', ['label' => 'Top Users', 'help' => 'Shows/hides the card with Top Users information.']),
|
||||
$this->settingsOption('switch', 'tautulliTopPlatforms', ['label' => 'Top Platforms', 'help' => 'Shows/hides the card with Top Platforms information.']),
|
||||
$this->settingsOption('auth', 'homepageTautulliMiscAuth'),
|
||||
$this->settingsOption('switch', 'tautulliFriendlyName', ['label' => 'Use Friendly Name', 'help' => 'Use the friendly name set in tautulli for users.']),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'tautulli'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionTautulli()
|
||||
{
|
||||
$this->setLoggerChannel('Tautulli Homepage');
|
||||
if (empty($this->config['tautulliURL'])) {
|
||||
$this->setAPIResponse('error', 'Tautulli URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
if (empty($this->config['tautulliApikey'])) {
|
||||
$this->setAPIResponse('error', 'Tautulli Token is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL($this->config['tautulliURL']);
|
||||
$apiURL = $url . '/api/v2?apikey=' . $this->config['tautulliApikey'];
|
||||
try {
|
||||
$homestatsUrl = $apiURL . '&cmd=get_home_stats&grouping=1';
|
||||
$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate'], ['follow_redirects' => false]);
|
||||
$homestats = Requests::get($homestatsUrl, [], $options);
|
||||
if ($homestats->success) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Tautulli Error Occurred - Check URL or Credentials', 409);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->logger->critical($e, [$url]);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function tautulliHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageTautulliEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageTautulliAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'tautulliURL',
|
||||
'tautulliApikey'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrdertautulli()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->tautulliHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Tautulli...</h2></div>
|
||||
<script>
|
||||
// Tautulli
|
||||
homepageTautulli("' . $this->config['homepageTautulliRefresh'] . '");
|
||||
// End Tautulli
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getTautulliHomepageData()
|
||||
{
|
||||
$this->setLoggerChannel('Tautulli Homepage');
|
||||
if (!$this->homepageItemPermissions($this->tautulliHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api = [];
|
||||
$url = $this->qualifyURL($this->config['tautulliURL']);
|
||||
$apiURL = $url . '/api/v2?apikey=' . $this->config['tautulliApikey'];
|
||||
$height = $this->getCacheImageSize('h');
|
||||
$width = $this->getCacheImageSize('w');
|
||||
$nowPlayingHeight = $this->getCacheImageSize('nph');
|
||||
$nowPlayingWidth = $this->getCacheImageSize('npw');
|
||||
$api['options'] = [
|
||||
'url' => $url,
|
||||
'libraries' => $this->config['tautulliLibraries'],
|
||||
'topMovies' => $this->config['tautulliTopMovies'],
|
||||
'topTV' => $this->config['tautulliTopTV'],
|
||||
'topUsers' => $this->config['tautulliTopUsers'],
|
||||
'topPlatforms' => $this->config['tautulliTopPlatforms'],
|
||||
'popularMovies' => $this->config['tautulliPopularMovies'],
|
||||
'popularTV' => $this->config['tautulliPopularTV'],
|
||||
'title' => $this->config['tautulliHeaderToggle'],
|
||||
'friendlyName' => $this->config['tautulliFriendlyName'],
|
||||
];
|
||||
try {
|
||||
$homestatsUrl = $apiURL . '&cmd=get_home_stats&grouping=1';
|
||||
$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
|
||||
$homestats = Requests::get($homestatsUrl, [], $options);
|
||||
if ($homestats->success) {
|
||||
$homepageTautulliViewingStatsExclude = explode(",", $this->config['homepageTautulliViewingStatsExclude']);
|
||||
$homestats = json_decode($homestats->body, true);
|
||||
foreach ($homestats['response']['data'] as $s => $stats) {
|
||||
foreach ($stats['rows'] as $i => $v) {
|
||||
if (array_key_exists('section_id', $v)) {
|
||||
if (in_array($v['section_id'], $homepageTautulliViewingStatsExclude)) {
|
||||
unset($homestats['response']['data'][$s]['rows'][$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$homestats['response']['data'] = array_values($homestats['response']['data']);
|
||||
$api['homestats'] = $homestats['response'];
|
||||
// Cache art & thumb for first result in each tautulli API result
|
||||
$categories = ['top_movies', 'top_tv', 'popular_movies', 'popular_tv'];
|
||||
foreach ($categories as $cat) {
|
||||
$key = array_search($cat, array_column($api['homestats']['data'], 'stat_id'));
|
||||
if (count($api['homestats']['data'][$key]['rows']) > 0) {
|
||||
$img = $api['homestats']['data'][$key]['rows'][0];
|
||||
$this->cacheImage($url . '/pms_image_proxy?img=' . $img['art'] . '&rating_key=' . $img['rating_key'] . '&width=' . $nowPlayingWidth . '&height=' . $nowPlayingHeight, $img['rating_key'] . '-np');
|
||||
$this->cacheImage($url . '/pms_image_proxy?img=' . $img['thumb'] . '&rating_key=' . $img['rating_key'] . '&width=' . $width . '&height=' . $height, $img['rating_key'] . '-list');
|
||||
$img['art'] = 'data/cache/' . $img['rating_key'] . '-np.jpg';
|
||||
$img['thumb'] = 'data/cache/' . $img['rating_key'] . '-list.jpg';
|
||||
$api['homestats']['data'][$key]['rows'][0] = $img;
|
||||
}
|
||||
}
|
||||
// Cache the platform icon
|
||||
if (count($api['homestats']['data'][$key]['rows']) > 0) {
|
||||
$key = array_search('top_platforms', array_column($api['homestats']['data'], 'stat_id'));
|
||||
$platform = $api['homestats']['data'][$key]['rows'][0]['platform_name'];
|
||||
$this->cacheImage($url . '/images/platforms/' . $platform . '.svg', 'tautulli-' . $platform, 'svg');
|
||||
}
|
||||
$libstatsUrl = $apiURL . '&cmd=get_libraries_table';
|
||||
$options = $this->requestOptions($this->config['tautulliURL'], $this->config['homepageTautulliRefresh'], $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
|
||||
$libstats = Requests::get($libstatsUrl, [], $options);
|
||||
if ($libstats->success) {
|
||||
$homepageTautulliLibraryStatsExclude = explode(',', $this->config['homepageTautulliLibraryStatsExclude']);
|
||||
$libstats = json_decode($libstats->body, true);
|
||||
foreach ($libstats['response']['data']['data'] as $i => $v) {
|
||||
if (array_key_exists('section_id', $v)) {
|
||||
if (in_array($v['section_id'], $homepageTautulliLibraryStatsExclude)) {
|
||||
unset($libstats['response']['data']['data'][$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$libstats['response']['data']['data'] = array_values($libstats['response']['data']['data']);
|
||||
$api['libstats'] = $libstats['response']['data'];
|
||||
$categories = ['movie.svg', 'show.svg', 'artist.svg'];
|
||||
foreach ($categories as $cat) {
|
||||
$parts = explode('.', $cat);
|
||||
$this->cacheImage($url . '/images/libraries/' . $cat, 'tautulli-' . $parts[0], $parts[1]);
|
||||
}
|
||||
}
|
||||
$ids = []; // Array of stat_ids to remove from the returned array
|
||||
if (!$this->qualifyRequest($this->config['homepageTautulliLibraryAuth'])) {
|
||||
$api['options']['libraries'] = false;
|
||||
unset($api['libstats']);
|
||||
}
|
||||
if (!$this->qualifyRequest($this->config['homepageTautulliViewsAuth'])) {
|
||||
$api['options']['topMovies'] = false;
|
||||
$api['options']['topTV'] = false;
|
||||
$api['options']['popularMovies'] = false;
|
||||
$api['options']['popularTV'] = false;
|
||||
$ids = array_merge(['top_movies', 'popular_movies', 'popular_tv', 'top_tv'], $ids);
|
||||
$api['homestats']['data'] = array_values($api['homestats']['data']);
|
||||
}
|
||||
if (!$this->qualifyRequest($this->config['homepageTautulliMiscAuth'])) {
|
||||
$api['options']['topUsers'] = false;
|
||||
$api['options']['topPlatforms'] = false;
|
||||
$ids = array_merge(['top_platforms', 'top_users'], $ids);
|
||||
$api['homestats']['data'] = array_values($api['homestats']['data']);
|
||||
}
|
||||
$ids = array_merge(['top_music', 'popular_music', 'last_watched', 'most_concurrent'], $ids);
|
||||
foreach ($ids as $id) {
|
||||
if ($key = array_search($id, array_column($api['homestats']['data'], 'stat_id'))) {
|
||||
unset($api['homestats']['data'][$key]);
|
||||
$api['homestats']['data'] = array_values($api['homestats']['data']);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->logger->critical($e, [$url]);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
$api = $api ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
public function tautulliLibraryList()
|
||||
{
|
||||
$url = $this->qualifyURL($this->config['tautulliURL']);
|
||||
$apiURL = $url . '/api/v2?apikey=' . $this->config['tautulliApikey'];
|
||||
if (!empty($this->config['tautulliApikey']) && !empty($this->config['tautulliURL'])) {
|
||||
$liblistUrl = $apiURL . '&cmd=get_libraries';
|
||||
$options = $this->requestOptions($this->config['tautulliURL'], 10, $this->config['tautulliDisableCertCheck'], $this->config['tautulliUseCustomCertificate']);
|
||||
try {
|
||||
$liblist = Requests::get($liblistUrl, [], $options);
|
||||
$libraryList = array();
|
||||
if ($liblist->success) {
|
||||
$liblist = json_decode($liblist->body, true);
|
||||
foreach ($liblist['response']['data'] as $lib) {
|
||||
$libraryList['libraries'][(string)$lib['section_name']] = (string)$lib["section_id"];
|
||||
}
|
||||
$libraryList = array_change_key_case($libraryList, CASE_LOWER);
|
||||
return $libraryList;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Tautulli')->error($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
276
docker/test/tvtracker/config/www/organizr/api/homepage/trakt.php
Normal file
276
docker/test/tvtracker/config/www/organizr/api/homepage/trakt.php
Normal file
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
trait TraktHomepageItem
|
||||
{
|
||||
public function traktSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Trakt',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/trakt.png',
|
||||
'category' => 'Calendar',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'docs' => 'https://docs.organizr.app/books/setup-features/page/trakt',
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'About' => [
|
||||
$this->settingsOption('html', null, [
|
||||
'override' => 12,
|
||||
'html' => '
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-wrapper collapse in">
|
||||
<div class="panel-body">
|
||||
<h3 lang="en">Trakt Homepage Item</h3>
|
||||
<p lang="en">This homepage item enables the calendar on the homepage and displays your movies and/or tv shows from Trakt\'s API.</p>
|
||||
<p lang="en">In order for this item to be setup, you need to goto the following URL to create a new API app.</p>
|
||||
<p><a href="https://trakt.tv/oauth/applications/new" target="_blank">New API App</a></p>
|
||||
<p lang="en">Enter anything for Name and Description. You can leave Javascript and Permissions blank. The only info you have to enter is for Redirect URI. Enter the following URL:</p>
|
||||
<code class="elip hidden-xs">' . $this->getServerPath() . 'api/v2/oauth/trakt</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>'
|
||||
]),
|
||||
],
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageTraktEnabled'),
|
||||
$this->settingsOption('auth', 'homepageTraktAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('input', 'traktClientId', ['label' => 'Client Id']),
|
||||
$this->settingsOption('password-alt', 'traktClientSecret', ['label' => 'Client Secret']),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('button', '', ['label' => 'Please Save before clicking button', 'icon' => 'fa fa-user', 'class' => 'pull-right', 'text' => 'Connect Account', 'attr' => 'onclick="openOAuth(\'trakt\')"']),
|
||||
],
|
||||
'Calendar' => [
|
||||
$this->settingsOption('calendar-start', 'calendarStartTrakt', ['help' => 'Total Days (Adding start and end days) has a maximum of 33 Days from Trakt API']),
|
||||
$this->settingsOption('calendar-end', 'calendarEndTrakt', ['help' => 'Total Days (Adding start and end days) has a maximum of 33 Days from Trakt API']),
|
||||
$this->settingsOption('calendar-starting-day', 'calendarFirstDay'),
|
||||
$this->settingsOption('calendar-default-view', 'calendarDefault'),
|
||||
$this->settingsOption('calendar-time-format', 'calendarTimeFormat'),
|
||||
$this->settingsOption('calendar-locale', 'calendarLocale'),
|
||||
$this->settingsOption('calendar-limit', 'calendarLimit'),
|
||||
$this->settingsOption('refresh', 'calendarRefresh'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function traktHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'calendar' => [
|
||||
'enabled' => [
|
||||
'homepageTraktEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageTraktAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'traktClientId',
|
||||
'traktAccessToken'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function getTraktCalendar($startDate = null)
|
||||
{
|
||||
$startDate = date('Y-m-d', strtotime('-' . $this->config['calendarStartTrakt'] . ' days'));
|
||||
$calendarItems = array();
|
||||
$errors = null;
|
||||
$totalDays = (int)$this->config['calendarStartTrakt'] + (int)$this->config['calendarEndTrakt'];
|
||||
if (!$this->homepageItemPermissions($this->traktHomepagePermissions('calendar'), true)) {
|
||||
return false;
|
||||
}
|
||||
$headers = [
|
||||
'Content-Type' => 'application/json',
|
||||
'Authorization' => 'Bearer ' . $this->config['traktAccessToken'],
|
||||
'trakt-api-version' => 2,
|
||||
'trakt-api-key' => $this->config['traktClientId']
|
||||
];
|
||||
$url = $this->qualifyURL('https://api.trakt.tv/calendars/my/shows/' . $startDate . '/' . $totalDays . '?extended=full');
|
||||
$options = $this->requestOptions($url, $this->config['calendarRefresh']);
|
||||
try {
|
||||
$response = Requests::get($url, $headers, $options);
|
||||
if ($response->success) {
|
||||
$data = json_decode($response->body, true);
|
||||
$traktTv = $this->formatTraktCalendarTv($data);
|
||||
if (!empty($traktTv)) {
|
||||
$calendarItems = array_merge($calendarItems, $traktTv);
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Trakt')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
$errors = true;
|
||||
}
|
||||
$url = $this->qualifyURL('https://api.trakt.tv/calendars/my/movies/' . $startDate . '/' . $totalDays . '?extended=full');
|
||||
try {
|
||||
$response = Requests::get($url, $headers, $options);
|
||||
if ($response->success) {
|
||||
$data = json_decode($response->body, true);
|
||||
$traktMovies = $this->formatTraktCalendarMovies($data);
|
||||
if (!empty($traktTv)) {
|
||||
$calendarItems = array_merge($calendarItems, $traktMovies);
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Trakt')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
$errors = true;
|
||||
}
|
||||
if ($errors) {
|
||||
$this->setAPIResponse('error', 'An error Occurred', 500, null);
|
||||
return false;
|
||||
}
|
||||
$this->setAPIResponse('success', null, 200, $calendarItems);
|
||||
$this->traktOAuthRefresh();
|
||||
return $calendarItems;
|
||||
}
|
||||
|
||||
public function formatTraktCalendarTv($array)
|
||||
{
|
||||
$gotCalendar = array();
|
||||
$i = 0;
|
||||
foreach ($array as $child) {
|
||||
$i++;
|
||||
$seriesName = $child['show']['title'];
|
||||
$seriesID = $child['show']['ids']['tmdb'];
|
||||
$episodeID = $child['show']['ids']['tmdb'];
|
||||
if (!isset($episodeID)) {
|
||||
$episodeID = "";
|
||||
}
|
||||
//$episodeName = htmlentities($child['title'], ENT_QUOTES);
|
||||
$episodeAirDate = $child['first_aired'];
|
||||
$episodeAirDate = strtotime($episodeAirDate);
|
||||
$episodeAirDate = date("Y-m-d H:i:s", $episodeAirDate);
|
||||
if (new DateTime() < new DateTime($episodeAirDate)) {
|
||||
$unaired = true;
|
||||
}
|
||||
if ($child['episode']['number'] == 1) {
|
||||
$episodePremier = "true";
|
||||
} else {
|
||||
$episodePremier = "false";
|
||||
$date = new DateTime($episodeAirDate);
|
||||
$date->add(new DateInterval("PT1S"));
|
||||
$date->format(DateTime::ATOM);
|
||||
$child['first_aired'] = gmdate('Y-m-d\TH:i:s\Z', strtotime($date->format(DateTime::ATOM)));
|
||||
}
|
||||
$downloaded = 0;
|
||||
$monitored = 0;
|
||||
if ($downloaded == "0" && isset($unaired) && $episodePremier == "true") {
|
||||
$downloaded = "text-primary animated flash";
|
||||
} elseif ($downloaded == "0" && isset($unaired) && $monitored == "0") {
|
||||
$downloaded = "text-dark";
|
||||
} elseif ($downloaded == "0" && isset($unaired)) {
|
||||
$downloaded = "text-info";
|
||||
} elseif ($downloaded == "1") {
|
||||
$downloaded = "text-success";
|
||||
} else {
|
||||
$downloaded = "text-danger";
|
||||
}
|
||||
$fanart = "/plugins/images/homepage/no-np.png";
|
||||
$bottomTitle = 'S' . sprintf("%02d", $child['episode']['season']) . 'E' . sprintf("%02d", $child['episode']['number']) . ' - ' . $child['episode']['title'];
|
||||
$details = array(
|
||||
"seasonCount" => $child['episode']['season'],
|
||||
"status" => 'dunno',
|
||||
"topTitle" => $seriesName,
|
||||
"bottomTitle" => $bottomTitle,
|
||||
"overview" => isset($child['episode']['overview']) ? $child['episode']['overview'] : '',
|
||||
"runtime" => isset($child['episode']['runtime']) ? $child['episode']['runtime'] : '',
|
||||
"image" => $fanart,
|
||||
"ratings" => isset($child['show']['rating']) ? $child['show']['rating'] : '',
|
||||
"videoQuality" => "unknown",
|
||||
"audioChannels" => "unknown",
|
||||
"audioCodec" => "unknown",
|
||||
"videoCodec" => "unknown",
|
||||
"size" => "unknown",
|
||||
"genres" => isset($child['show']['genres']) ? $child['show']['genres'] : '',
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
"id" => "Trakt-Tv-" . $i,
|
||||
"title" => $seriesName,
|
||||
"start" => $child['first_aired'],
|
||||
"className" => "inline-popups bg-calendar calendar-item get-tmdb-image tmdb-tv tmdbID--" . $seriesID,
|
||||
"imagetype" => "tv " . $downloaded,
|
||||
"imagetypeFilter" => "tv",
|
||||
"downloadFilter" => $downloaded,
|
||||
"bgColor" => str_replace('text', 'bg', $downloaded),
|
||||
"details" => $details
|
||||
));
|
||||
}
|
||||
if ($i != 0) {
|
||||
return $gotCalendar;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function formatTraktCalendarMovies($array)
|
||||
{
|
||||
$gotCalendar = array();
|
||||
$i = 0;
|
||||
foreach ($array as $child) {
|
||||
$i++;
|
||||
$movieName = $child['movie']['title'];
|
||||
$movieID = $child['movie']['ids']['tmdb'];
|
||||
if (!isset($movieID)) {
|
||||
$movieID = '';
|
||||
}
|
||||
$physicalRelease = (isset($child['movie']['released']) ? $child['movie']['released'] : null);
|
||||
//$backupRelease = (isset($child['info']['release_date']['theater']) ? $child['info']['release_date']['theater'] : null);
|
||||
//$physicalRelease = (isset($physicalRelease) ? $physicalRelease : $backupRelease);
|
||||
$physicalRelease = strtotime($physicalRelease);
|
||||
$physicalRelease = date('Y-m-d', $physicalRelease);
|
||||
$oldestDay = new DateTime ($this->currentTime);
|
||||
$oldestDay->modify('-' . $this->config['calendarStart'] . ' days');
|
||||
$newestDay = new DateTime ($this->currentTime);
|
||||
$newestDay->modify('+' . $this->config['calendarEnd'] . ' days');
|
||||
$startDt = new DateTime ($physicalRelease);
|
||||
if (new DateTime() < $startDt) {
|
||||
$notReleased = 'true';
|
||||
} else {
|
||||
$notReleased = 'false';
|
||||
}
|
||||
$downloaded = 'text-dark';
|
||||
$banner = '/plugins/images/homepage/no-np.png';
|
||||
$details = array(
|
||||
'topTitle' => $movieName,
|
||||
'bottomTitle' => $child['movie']['tagline'],
|
||||
'status' => $child['movie']['status'],
|
||||
'overview' => $child['movie']['overview'],
|
||||
'runtime' => $child['movie']['runtime'],
|
||||
'image' => $banner,
|
||||
'ratings' => isset($child['movie']['rating']) ? $child['movie']['rating'] : '',
|
||||
'videoQuality' => 'unknown',
|
||||
'audioChannels' => '',
|
||||
'audioCodec' => '',
|
||||
'videoCodec' => '',
|
||||
'genres' => $child['movie']['genres'],
|
||||
'year' => isset($child['movie']['year']) ? $child['movie']['year'] : '',
|
||||
'studio' => isset($child['movie']['year']) ? $child['movie']['year'] : '',
|
||||
);
|
||||
array_push($gotCalendar, array(
|
||||
'id' => 'Trakt-Movie-' . $i,
|
||||
'title' => $movieName,
|
||||
'start' => $physicalRelease,
|
||||
'className' => 'inline-popups bg-calendar calendar-item get-tmdb-image tmdb-movie tmdbID--' . $movieID,
|
||||
'imagetype' => 'film ' . $downloaded,
|
||||
'imagetypeFilter' => 'film',
|
||||
'downloadFilter' => $downloaded,
|
||||
'bgColor' => str_replace('text', 'bg', $downloaded),
|
||||
'details' => $details
|
||||
));
|
||||
}
|
||||
if ($i != 0) {
|
||||
return $gotCalendar;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
trait TransmissionHomepageItem
|
||||
{
|
||||
public function transmissionSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Transmission',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/transmission.png',
|
||||
'category' => 'Downloader',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageTransmissionEnabled'),
|
||||
$this->settingsOption('auth', 'homepageTransmissionAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'transmissionURL', ['help' => 'Please do not included /web in URL. Please make sure to use local IP address and port - You also may use local dns name too.']),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('username', 'transmissionUsername'),
|
||||
$this->settingsOption('password', 'transmissionPassword'),
|
||||
$this->settingsOption('disable-cert-check', 'transmissionDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'transmissionUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('hide-seeding', 'transmissionHideSeeding'),
|
||||
$this->settingsOption('hide-completed', 'transmissionHideCompleted'),
|
||||
$this->settingsOption('refresh', 'transmissionRefresh'),
|
||||
$this->settingsOption('combine', 'transmissionCombine'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'transmission'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function testConnectionTransmission()
|
||||
{
|
||||
if (empty($this->config['transmissionURL'])) {
|
||||
$this->setAPIResponse('error', 'Transmission URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
$digest = $this->qualifyURL($this->config['transmissionURL'], true);
|
||||
$passwordInclude = ($this->config['transmissionUsername'] != '' && $this->config['transmissionPassword'] != '') ? $this->config['transmissionUsername'] . ':' . rawurlencode($this->decrypt($this->config['transmissionPassword'])) . "@" : '';
|
||||
$url = $digest['scheme'] . '://' . $passwordInclude . $digest['host'] . $digest['port'] . $digest['path'] . '/rpc';
|
||||
try {
|
||||
$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionRefresh'], $this->config['transmissionDisableCertCheck'], $this->config['transmissionUseCustomCertificate']);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->headers['x-transmission-session-id']) {
|
||||
$headers = array(
|
||||
'X-Transmission-Session-Id' => $response->headers['x-transmission-session-id'],
|
||||
'Content-Type' => 'application/json'
|
||||
);
|
||||
$data = array(
|
||||
'method' => 'torrent-get',
|
||||
'arguments' => array(
|
||||
'fields' => array(
|
||||
"id", "name", "totalSize", "eta", "isFinished", "isStalled", "percentDone", "rateDownload", "status", "downloadDir", "errorString"
|
||||
),
|
||||
),
|
||||
'tags' => ''
|
||||
);
|
||||
$response = Requests::post($url, $headers, json_encode($data), $options);
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Transmission Connect Function - Error: Unknown', 500);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('Transmission')->warning('Could not get session ID');
|
||||
$this->setAPIResponse('error', 'Transmission Connect Function - Error: Could not get session ID', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Transmission')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function transmissionHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageTransmissionEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageTransmissionAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'transmissionURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrdertransmission()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->transmissionHomepagePermissions('main'))) {
|
||||
$loadingBox = ($this->config['transmissionCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['transmissionCombine']) ? 'buildDownloaderCombined(\'transmission\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("transmission"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrdertransmission
|
||||
' . $builder . '
|
||||
homepageDownloader("transmission", "' . $this->config['transmissionRefresh'] . '");
|
||||
// End homepageOrdertransmission
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getTransmissionHomepageQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->transmissionHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$digest = $this->qualifyURL($this->config['transmissionURL'], true);
|
||||
$passwordInclude = ($this->config['transmissionUsername'] != '' && $this->config['transmissionPassword'] != '') ? $this->config['transmissionUsername'] . ':' . rawurlencode($this->decrypt($this->config['transmissionPassword'])) . "@" : '';
|
||||
$url = $digest['scheme'] . '://' . $passwordInclude . $digest['host'] . $digest['port'] . $digest['path'] . '/rpc';
|
||||
try {
|
||||
$options = $this->requestOptions($this->config['transmissionURL'], $this->config['transmissionRefresh'], $this->config['transmissionDisableCertCheck'], $this->config['transmissionUseCustomCertificate']);
|
||||
$response = Requests::get($url, array(), $options);
|
||||
if ($response->headers['x-transmission-session-id']) {
|
||||
$headers = array(
|
||||
'X-Transmission-Session-Id' => $response->headers['x-transmission-session-id'],
|
||||
'Content-Type' => 'application/json'
|
||||
);
|
||||
$data = array(
|
||||
'method' => 'torrent-get',
|
||||
'arguments' => array(
|
||||
'fields' => array(
|
||||
"id", "name", "totalSize", "eta", "isFinished", "isStalled", "percentDone", "rateDownload", "status", "downloadDir", "errorString", "addedDate"
|
||||
),
|
||||
),
|
||||
'tags' => ''
|
||||
);
|
||||
$response = Requests::post($url, $headers, json_encode($data), $options);
|
||||
if ($response->success) {
|
||||
$torrentList = json_decode($response->body, true)['arguments']['torrents'];
|
||||
if ($this->config['transmissionHideSeeding'] || $this->config['transmissionHideCompleted']) {
|
||||
$filter = array();
|
||||
$torrents = array();
|
||||
if ($this->config['transmissionHideSeeding']) {
|
||||
array_push($filter, 6, 5);
|
||||
}
|
||||
if ($this->config['transmissionHideCompleted']) {
|
||||
array_push($filter, 0);
|
||||
}
|
||||
foreach ($torrentList as $key => $value) {
|
||||
if (!in_array($value['status'], $filter)) {
|
||||
$torrents[] = $value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$torrents = json_decode($response->body, true)['arguments']['torrents'];
|
||||
}
|
||||
usort($torrents, function ($a, $b) {
|
||||
return $a["addedDate"] <=> $b["addedDate"];
|
||||
});
|
||||
$api['content']['queueItems'] = $torrents;
|
||||
$api['content']['historyItems'] = false;
|
||||
}
|
||||
} else {
|
||||
$this->setLoggerChannel('Transmission')->warning('Could not get session ID');
|
||||
$this->setAPIResponse('error', 'Transmission Connect Function - Error: Could not get session ID', 500);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Transmission')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
240
docker/test/tvtracker/config/www/organizr/api/homepage/unifi.php
Normal file
240
docker/test/tvtracker/config/www/organizr/api/homepage/unifi.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
trait UnifiHomepageItem
|
||||
{
|
||||
public function unifiSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'UniFi',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/unifi.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageUnifiEnabled'),
|
||||
$this->settingsOption('auth', 'homepageUnifiAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'unifiURL'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('disable-cert-check', 'unifiDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'unifiUseCustomCertificate'),
|
||||
$this->settingsOption('username', 'unifiUsername', ['help' => 'Username is case-sensitive']),
|
||||
$this->settingsOption('password', 'unifiPassword'),
|
||||
$this->settingsOption('input', 'unifiSiteName', ['label' => 'Site Name (Not for UnifiOS)', 'help' => 'Site Name - not Site ID nor Site Description']),
|
||||
$this->settingsOption('button', '', ['label' => 'Grab Unifi Site (Not for UnifiOS)', 'icon' => 'fa fa-building', 'text' => 'Get Unifi Site', 'attr' => 'onclick="getUnifiSite()"']),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('refresh', 'homepageUnifiRefresh'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'unifi'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function unifiHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageUnifiEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageUnifiAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'unifiURL',
|
||||
'unifiUsername',
|
||||
'unifiPassword'
|
||||
]
|
||||
],
|
||||
'test' => [
|
||||
'auth' => [
|
||||
'homepageUnifiAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'unifiURL',
|
||||
'unifiUsername',
|
||||
'unifiPassword'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderunifi()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->unifiHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Unifi...</h2></div>
|
||||
<script>
|
||||
// Unifi
|
||||
homepageUnifi("' . $this->config['homepageHealthChecksRefresh'] . '");
|
||||
// End Unifi
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getUnifiSiteName()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->unifiHomepagePermissions('test'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$login = $this->unifiLogin();
|
||||
if ($login) {
|
||||
$url = $this->qualifyURL($this->config['unifiURL']);
|
||||
$unifiOS = $login['unifiOS'];
|
||||
if ($unifiOS) {
|
||||
$this->setResponse(500, 'Unifi OS does not support Multi Site');
|
||||
return false;
|
||||
}
|
||||
$response = Requests::get($url . '/api/self/sites', [], $login['options']);
|
||||
if ($response->success) {
|
||||
$body = json_decode($response->body, true);
|
||||
$this->setAPIResponse('success', null, 200, $body);
|
||||
return $body;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Unifi response error3', 409);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Unifi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function isUnifiOS()
|
||||
{
|
||||
try {
|
||||
// Is this UnifiOs or Regular
|
||||
$url = $this->qualifyURL($this->config['unifiURL']);
|
||||
$options = $this->requestOptions($url, $this->config['homepageUnifiRefresh'], $this->config['unifiDisableCertCheck'], $this->config['unifiUseCustomCertificate'], ['follow_redirects' => true]);
|
||||
$response = Requests::get($url, [], $options);
|
||||
if ($response->success) {
|
||||
return ($response->headers['x-csrf-token']) ?? false;
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Unifi response error - Check URL', 409);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Unifi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function unifiLogin()
|
||||
{
|
||||
$csrfToken = $this->isUnifiOS();
|
||||
$url = $this->qualifyURL($this->config['unifiURL']);
|
||||
$options = $this->requestOptions($url, $this->config['homepageUnifiRefresh'], $this->config['unifiDisableCertCheck'], $this->config['unifiUseCustomCertificate'], ['follow_redirects' => true]);
|
||||
$data = array(
|
||||
'username' => $this->config['unifiUsername'],
|
||||
'password' => $this->decrypt($this->config['unifiPassword']),
|
||||
'remember' => true,
|
||||
'strict' => true
|
||||
);
|
||||
try {
|
||||
$data = ($csrfToken) ? $data : json_encode($data);
|
||||
$headers = ($csrfToken) ? ['x-csrf-token' => $csrfToken] : [];
|
||||
$urlLogin = ($csrfToken) ? $url . '/api/auth/login' : $url . '/api/login';
|
||||
$response = Requests::post($urlLogin, $headers, $data, $options);
|
||||
if ($response->success) {
|
||||
$options['cookies'] = $response->cookies;
|
||||
return [
|
||||
'unifiOS' => $csrfToken,
|
||||
'options' => $options
|
||||
];
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Unifi response error - Check Credentials', 409);
|
||||
return false;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Unifi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnectionUnifi()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->unifiHomepagePermissions('test'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// Is this UnifiOs or Regular
|
||||
$api['content']['unifi'] = array();
|
||||
$login = $this->unifiLogin();
|
||||
if ($login) {
|
||||
$url = $this->qualifyURL($this->config['unifiURL']);
|
||||
$unifiOS = $login['unifiOS'];
|
||||
$headers = ($unifiOS) ? ['x-csrf-token' => $unifiOS] : [];
|
||||
$urlStat = ($unifiOS) ? $url . '/proxy/network/api/s/default/stat/health' : $url . '/api/s/' . $this->config['unifiSiteName'] . '/stat/health';
|
||||
$response = Requests::get($urlStat, $headers, $login['options']);
|
||||
if ($response->success) {
|
||||
$api['content']['unifi'] = json_decode($response->body, true);
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Unifi response error3', 409);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Unifi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
$api['content']['unifi'] = $api['content']['unifi'] ?? false;
|
||||
$this->setAPIResponse('success', 'API Connection succeeded', 200);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getUnifiHomepageData()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->unifiHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$api['content']['unifi'] = array();
|
||||
$login = $this->unifiLogin();
|
||||
if ($login) {
|
||||
$url = $this->qualifyURL($this->config['unifiURL']);
|
||||
$unifiOS = $login['unifiOS'];
|
||||
$headers = ($unifiOS) ? ['x-csrf-token' => $unifiOS] : [];
|
||||
$urlStat = ($unifiOS) ? $url . '/proxy/network/api/s/default/stat/health' : $url . '/api/s/' . $this->config['unifiSiteName'] . '/stat/health';
|
||||
$response = Requests::get($urlStat, $headers, $login['options']);
|
||||
if ($response->success) {
|
||||
$api['content']['unifi'] = json_decode($response->body, true);
|
||||
} else {
|
||||
$this->setAPIResponse('error', 'Unifi response error3', 409);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Unifi')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
$api['content']['unifi'] = $api['content']['unifi'] ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
trait UptimeKumaHomepageItem
|
||||
{
|
||||
private static Client $kumaClient;
|
||||
|
||||
public function uptimeKumaSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'UptimeKuma',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/kuma.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageUptimeKumaEnabled'),
|
||||
$this->settingsOption('auth', 'homepageUptimeKumaAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'uptimeKumaURL', ['help' => 'URL for Uptime Kuma e.g. http://kuma:3001 (no trailing slash)', 'placeholder' => 'http://kuma:3001']),
|
||||
$this->settingsOption('token', 'uptimeKumaToken'),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('refresh', 'homepageUptimeKumaRefresh'),
|
||||
$this->settingsOption('title', 'homepageUptimeKumaHeader'),
|
||||
$this->settingsOption('toggle-title', 'homepageUptimeKumaHeaderToggle'),
|
||||
$this->settingsOption('switch', 'homepageUptimeKumaCompact', ['label' => 'Compact view', 'help' => 'Toggles the compact view of this homepage module']),
|
||||
$this->settingsOption('switch', 'homepageUptimeKumaShowLatency', ['label' => 'Show monitor latency']),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function uptimeKumaHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageUptimeKumaEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageUptimeKumaAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'uptimeKumaURL',
|
||||
'uptimeKumaToken',
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderUptimeKuma()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->uptimeKumaHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Uptime Kuma...</h2></div>
|
||||
<script>
|
||||
// Uptime Kuma
|
||||
homepageUptimeKuma("' . $this->config['homepageUptimeKumaRefresh'] . '");
|
||||
// End Uptime Kuma
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getUptimeKumaHomepageData()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->uptimeKumaHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api = [];
|
||||
$url = $this->qualifyURL($this->config['uptimeKumaURL']);
|
||||
try {
|
||||
$metrics = (new UptimeKumaMetrics(
|
||||
$this->getKumaClient($url, $this->config['uptimeKumaToken'])
|
||||
->get('/metrics')
|
||||
->getBody()
|
||||
->getContents()
|
||||
))->process();
|
||||
|
||||
$api = [
|
||||
'data' => $metrics->getMonitors(),
|
||||
'options' => [
|
||||
'title' => $this->config['homepageUptimeKumaHeader'],
|
||||
'titleToggle' => $this->config['homepageUptimeKumaHeaderToggle'],
|
||||
'compact' => $this->config['homepageUptimeKumaCompact'],
|
||||
'showLatency' => $this->config['homepageUptimeKumaShowLatency'],
|
||||
]
|
||||
];
|
||||
} catch (GuzzleException $e) {
|
||||
$this->setLoggerChannel('UptimeKuma')->error($e);
|
||||
$this->setAPIResponse('error', $e->getMessage(), 401);
|
||||
return false;
|
||||
};
|
||||
$api = isset($api) ? $api : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
|
||||
private function getKumaClient(string $url, string $token): Client
|
||||
{
|
||||
if (!isset(static::$kumaClient)) {
|
||||
static::$kumaClient = new Client([
|
||||
'base_uri' => $url,
|
||||
'headers' => [
|
||||
'Authorization' => sprintf("Basic %s", base64_encode(':'.$token)),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return static::$kumaClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
trait uTorrentHomepageItem
|
||||
{
|
||||
public function uTorrentSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'uTorrent',
|
||||
'enabled' => strpos('personal', $this->config['license']) !== false,
|
||||
'image' => 'plugins/images/tabs/utorrent.png',
|
||||
'category' => 'Downloader',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageuTorrentEnabled'),
|
||||
$this->settingsOption('auth', 'homepageuTorrentAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('url', 'uTorrentURL'),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('username', 'uTorrentUsername'),
|
||||
$this->settingsOption('password', 'uTorrentPassword'),
|
||||
$this->settingsOption('disable-cert-check', 'uTorrentDisableCertCheck'),
|
||||
$this->settingsOption('use-custom-certificate', 'uTorrentUseCustomCertificate'),
|
||||
],
|
||||
'Misc Options' => [
|
||||
$this->settingsOption('hide-seeding', 'uTorrentHideSeeding', ['label' => 'Hide Seeding']),
|
||||
$this->settingsOption('hide-completed', 'uTorrentHideCompleted'),
|
||||
$this->settingsOption('refresh', 'uTorrentRefresh'),
|
||||
$this->settingsOption('combine', 'uTorrentCombine'),
|
||||
],
|
||||
'Test Connection' => [
|
||||
$this->settingsOption('blank', null, ['label' => 'Please Save before Testing']),
|
||||
$this->settingsOption('test', 'utorrent'),
|
||||
]
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function uTorrentHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageuTorrentEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageuTorrentAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'uTorrentURL'
|
||||
]
|
||||
]
|
||||
];
|
||||
if (array_key_exists($key, $permissions)) {
|
||||
return $permissions[$key];
|
||||
} elseif ($key == 'all') {
|
||||
return $permissions;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function testConnectionuTorrent()
|
||||
{
|
||||
if (empty($this->config['uTorrentURL'])) {
|
||||
$this->setAPIResponse('error', 'uTorrent URL is not defined', 422);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
|
||||
$response = $this->getuTorrentToken();
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('uTorrent')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function homepageOrderuTorrent()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->uTorrentHomepagePermissions('main'))) {
|
||||
$loadingBox = ($this->config['uTorrentCombine']) ? '' : '<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Download Queue...</h2></div>';
|
||||
$builder = ($this->config['uTorrentCombine']) ? 'buildDownloaderCombined(\'utorrent\');' : '$("#' . __FUNCTION__ . '").html(buildDownloader("utorrent"));';
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
' . $loadingBox . '
|
||||
<script>
|
||||
// homepageOrderuTorrent
|
||||
' . $builder . '
|
||||
homepageDownloader("utorrent", "' . $this->config['uTorrentRefresh'] . '");
|
||||
// End homepageOrderuTorrent
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function getuTorrentToken()
|
||||
{
|
||||
try {
|
||||
$tokenUrl = '/gui/token.html';
|
||||
$digest = $this->qualifyURL($this->config['uTorrentURL'], true);
|
||||
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $tokenUrl;
|
||||
$data = array('username' => $this->config['uTorrentUsername'], 'password' => $this->decrypt($this->config['uTorrentPassword']));
|
||||
$options = $this->requestOptions($url, null, $this->config['uTorrentDisableCertCheck'], $this->config['uTorrentUseCustomCertificate']);
|
||||
if ($this->config['uTorrentUsername'] !== '' && $this->decrypt($this->config['uTorrentPassword']) !== '') {
|
||||
$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['uTorrentUsername'], $this->decrypt($this->config['uTorrentPassword']))));
|
||||
$options = array_merge($options, $credentials);
|
||||
}
|
||||
$response = Requests::post($url, [], $data, $options);
|
||||
$dom = new PHPHtmlParser\Dom;
|
||||
$dom->loadStr($response->body);
|
||||
$id = $dom->getElementById('token')->text;
|
||||
$uTorrentConfig = array(
|
||||
"uTorrentToken" => $id,
|
||||
"uTorrentCookie" => "",
|
||||
);
|
||||
$reflection = new ReflectionClass($response->cookies);
|
||||
$cookie = $reflection->getProperty("cookies");
|
||||
$cookie->setAccessible(true);
|
||||
$cookie = $cookie->getValue($response->cookies);
|
||||
if ($cookie['GUID']) {
|
||||
$uTorrentConfig['uTorrentCookie'] = $cookie['GUID']->value;
|
||||
}
|
||||
if ($uTorrentConfig['uTorrentToken'] || $uTorrentConfig['uTorrentCookie']) {
|
||||
$this->updateConfigItems($uTorrentConfig);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('uTorrent')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function getuTorrentHomepageQueue()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->uTorrentHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if (!$this->config['uTorrentToken'] || !$this->config['uTorrentCookie']) {
|
||||
$this->getuTorrentToken();
|
||||
}
|
||||
$queryUrl = '/gui/?token=' . $this->config['uTorrentToken'] . '&list=1';
|
||||
$digest = $this->qualifyURL($this->config['uTorrentURL'], true);
|
||||
$url = $digest['scheme'] . '://' . $digest['host'] . $digest['port'] . $digest['path'] . $queryUrl;
|
||||
$options = $this->requestOptions($url, null, $this->config['uTorrentDisableCertCheck'], $this->config['uTorrentUseCustomCertificate']);
|
||||
if ($this->config['uTorrentUsername'] !== '' && $this->decrypt($this->config['uTorrentPassword']) !== '') {
|
||||
$credentials = array('auth' => new Requests_Auth_Basic(array($this->config['uTorrentUsername'], $this->decrypt($this->config['uTorrentPassword']))));
|
||||
$options = array_merge($options, $credentials);
|
||||
}
|
||||
$headers = array(
|
||||
'Cookie' => 'GUID=' . $this->config['uTorrentCookie']
|
||||
);
|
||||
$response = Requests::get($url, $headers, $options);
|
||||
$httpResponse = $response->status_code;
|
||||
if ($httpResponse == 400) {
|
||||
$this->setLoggerChannel('uTorrent')->warning('Token or Cookie Expired. Generating new session...');
|
||||
$this->getuTorrentToken();
|
||||
$response = Requests::get($url, $headers, $options);
|
||||
$httpResponse = $response->status_code;
|
||||
}
|
||||
if ($httpResponse == 200) {
|
||||
$responseData = json_decode($response->body);
|
||||
$keyArray = (array)$responseData->torrents;
|
||||
//Populate values
|
||||
$valueArray = array();
|
||||
foreach ($keyArray as $keyArr) {
|
||||
preg_match('/(?<Status>(\w+\s+)+)(?<Percentage>\d+.\d+.*)/', $keyArr[21], $matches);
|
||||
$Status = str_replace(' ', '', $matches['Status']);
|
||||
if ($this->config['uTorrentHideSeeding'] && $Status == "Seeding") {
|
||||
// Do Nothing
|
||||
} else if ($this->config['uTorrentHideCompleted'] && $Status == "Finished") {
|
||||
// Do Nothing
|
||||
} else {
|
||||
$value = array(
|
||||
'Hash' => $keyArr[0],
|
||||
'TorrentStatus' => $keyArr[1],
|
||||
'Name' => $keyArr[2],
|
||||
'Size' => $keyArr[3],
|
||||
'Progress' => $keyArr[4],
|
||||
'Downloaded' => $keyArr[5],
|
||||
'Uploaded' => $keyArr[6],
|
||||
'Ratio' => $keyArr[7],
|
||||
'upSpeed' => $keyArr[8],
|
||||
'downSpeed' => $keyArr[9],
|
||||
'eta' => $keyArr[10],
|
||||
'Labels' => $keyArr[11],
|
||||
'PeersConnected' => $keyArr[12],
|
||||
'PeersInSwarm' => $keyArr[13],
|
||||
'SeedsConnected' => $keyArr[14],
|
||||
'SeedsInSwarm' => $keyArr[15],
|
||||
'Availability' => $keyArr[16],
|
||||
'TorrentQueueOrder' => $keyArr[17],
|
||||
'Remaining' => $keyArr[18],
|
||||
'DownloadUrl' => $keyArr[19],
|
||||
'RssFeedUrl' => $keyArr[20],
|
||||
'Message' => $keyArr[21],
|
||||
'StreamId' => $keyArr[22],
|
||||
'DateAdded' => $keyArr[23],
|
||||
'DateCompleted' => $keyArr[24],
|
||||
'AppUpdateUrl' => $keyArr[25],
|
||||
'RootDownloadPath' => $keyArr[26],
|
||||
'Unknown27' => $keyArr[27],
|
||||
'Unknown28' => $keyArr[28],
|
||||
'Status' => $Status,
|
||||
'Percent' => str_replace(' ', '', $matches['Percentage']),
|
||||
);
|
||||
array_push($valueArray, $value);
|
||||
}
|
||||
}
|
||||
$api['content']['queueItems'] = $valueArray;
|
||||
$api['content'] = $api['content'] ?? false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('uTorrent')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
trait WeatherHomepageItem
|
||||
{
|
||||
public function weatherSettingsArray($infoOnly = false)
|
||||
{
|
||||
$homepageInformation = [
|
||||
'name' => 'Weather-Air',
|
||||
'enabled' => true,
|
||||
'image' => 'plugins/images/tabs/wind.png',
|
||||
'category' => 'Monitor',
|
||||
'settingsArray' => __FUNCTION__
|
||||
];
|
||||
if ($infoOnly) {
|
||||
return $homepageInformation;
|
||||
}
|
||||
$homepageSettings = [
|
||||
'debug' => true,
|
||||
'settings' => [
|
||||
'Enable' => [
|
||||
$this->settingsOption('enable', 'homepageWeatherAndAirEnabled'),
|
||||
$this->settingsOption('auth', 'homepageWeatherAndAirAuth'),
|
||||
],
|
||||
'Connection' => [
|
||||
$this->settingsOption('input', 'homepageWeatherAndAirLatitude', ['label' => 'Latitude', 'help' => 'Please enter full latitude including minus if needed']),
|
||||
$this->settingsOption('input', 'homepageWeatherAndAirLongitude', ['label' => 'Longitude', 'help' => 'Please enter full longitude including minus if needed']),
|
||||
$this->settingsOption('blank'),
|
||||
$this->settingsOption('button', null, ['type' => 'button', 'label' => '', 'icon' => 'fa fa-search', 'class' => 'pull-right', 'text' => 'Need Help With Coordinates?', 'attr' => 'onclick="showLookupCoordinatesModal()"']),
|
||||
],
|
||||
'Options' => [
|
||||
$this->settingsOption('title', 'homepageWeatherAndAirWeatherHeader'),
|
||||
$this->settingsOption('toggle-title', 'homepageWeatherAndAirWeatherHeaderToggle'),
|
||||
$this->settingsOption('enable', 'homepageWeatherAndAirWeatherEnabled', ['label' => 'Enable Weather', 'help' => 'Toggles the view module for Weather']),
|
||||
$this->settingsOption('enable', 'homepageWeatherAndAirAirQualityEnabled', ['label' => 'Enable Air Quality', 'help' => 'Toggles the view module for Air Quality']),
|
||||
$this->settingsOption('enable', 'homepageWeatherAndAirPollenEnabled', ['label' => 'Enable Pollen', 'help' => 'Toggles the view module for Pollen']),
|
||||
$this->settingsOption('select', 'homepageWeatherAndAirUnits', ['label' => 'Unit of Measurement', 'options' => [['name' => 'Imperial', 'value' => 'imperial'], ['name' => 'Metric', 'value' => 'metric']]]),
|
||||
$this->settingsOption('refresh', 'homepageWeatherAndAirRefresh'),
|
||||
],
|
||||
]
|
||||
];
|
||||
return array_merge($homepageInformation, $homepageSettings);
|
||||
}
|
||||
|
||||
public function weatherHomepagePermissions($key = null)
|
||||
{
|
||||
$permissions = [
|
||||
'main' => [
|
||||
'enabled' => [
|
||||
'homepageWeatherAndAirEnabled'
|
||||
],
|
||||
'auth' => [
|
||||
'homepageWeatherAndAirAuth'
|
||||
],
|
||||
'not_empty' => [
|
||||
'homepageWeatherAndAirLatitude',
|
||||
'homepageWeatherAndAirLongitude'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->homepageCheckKeyPermissions($key, $permissions);
|
||||
}
|
||||
|
||||
public function homepageOrderWeatherAndAir()
|
||||
{
|
||||
if ($this->homepageItemPermissions($this->weatherHomepagePermissions('main'))) {
|
||||
return '
|
||||
<div id="' . __FUNCTION__ . '">
|
||||
<div class="white-box homepage-loading-box"><h2 class="text-center" lang="en">Loading Weather...</h2></div>
|
||||
<script>
|
||||
// Weather And Air
|
||||
homepageWeatherAndAir("' . $this->config['homepageWeatherAndAirRefresh'] . '");
|
||||
// End Weather And Air
|
||||
</script>
|
||||
</div>
|
||||
';
|
||||
}
|
||||
}
|
||||
|
||||
public function searchCityForCoordinates($query)
|
||||
{
|
||||
try {
|
||||
$query = $query ?? false;
|
||||
if (!$query) {
|
||||
$this->setAPIResponse('error', 'Query was not supplied', 422);
|
||||
return false;
|
||||
}
|
||||
$url = $this->qualifyURL('https://api.mapbox.com/geocoding/v5/mapbox.places/' . urlencode($query) . '.json?access_token=pk.eyJ1IjoiY2F1c2VmeCIsImEiOiJjazhyeGxqeXgwMWd2M2ZydWQ4YmdjdGlzIn0.R50iYuMewh1CnUZ7sFPdHA&limit=5&fuzzyMatch=true');
|
||||
$response = Requests::get($url);
|
||||
if ($response->success) {
|
||||
$this->setAPIResponse('success', null, 200, json_decode($response->body));
|
||||
return json_decode($response->body);
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
public function getWeatherAndAirData()
|
||||
{
|
||||
if (!$this->homepageItemPermissions($this->weatherHomepagePermissions('main'), true)) {
|
||||
return false;
|
||||
}
|
||||
$api['content'] = array(
|
||||
'weather' => false,
|
||||
'air' => false,
|
||||
'pollen' => false
|
||||
);
|
||||
$apiURL = $this->qualifyURL('https://api.breezometer.com/');
|
||||
$info = '&lat=' . $this->config['homepageWeatherAndAirLatitude'] . '&lon=' . $this->config['homepageWeatherAndAirLongitude'] . '&units=' . $this->config['homepageWeatherAndAirUnits'] . '&key=' . $this->config['breezometerToken'];
|
||||
try {
|
||||
if ($this->config['homepageWeatherAndAirWeatherEnabled']) {
|
||||
$endpoint = '/weather/v1/forecast/hourly?hours=120&metadata=true';
|
||||
$options = $this->requestOptions($apiURL, $this->config['homepageWeatherAndAirRefresh']);
|
||||
$response = Requests::get($apiURL . $endpoint . $info, [], $options);
|
||||
if ($response->success) {
|
||||
$apiData = json_decode($response->body, true);
|
||||
$api['content']['weather'] = ($apiData['error'] === null) ? $apiData : false;
|
||||
unset($apiData);
|
||||
}
|
||||
}
|
||||
if ($this->config['homepageWeatherAndAirAirQualityEnabled']) {
|
||||
$endpoint = '/air-quality/v2/current-conditions?features=breezometer_aqi,local_aqi,health_recommendations,sources_and_effects,dominant_pollutant_concentrations,pollutants_concentrations,pollutants_aqi_information&metadata=true';
|
||||
$response = Requests::get($apiURL . $endpoint . $info);
|
||||
if ($response->success) {
|
||||
$apiData = json_decode($response->body, true);
|
||||
$api['content']['air'] = ($apiData['error'] === null) ? $apiData : false;
|
||||
unset($apiData);
|
||||
}
|
||||
}
|
||||
if ($this->config['homepageWeatherAndAirPollenEnabled']) {
|
||||
$endpoint = '/pollen/v2/forecast/daily?features=plants_information,types_information&days=1&metadata=true';
|
||||
$response = Requests::get($apiURL . $endpoint . $info);
|
||||
if ($response->success) {
|
||||
$apiData = json_decode($response->body, true);
|
||||
$api['content']['pollen'] = ($apiData['error'] === null) ? $apiData : false;
|
||||
unset($apiData);
|
||||
}
|
||||
}
|
||||
} catch (Requests_Exception $e) {
|
||||
$this->setLoggerChannel('Weather & Air')->error($e);
|
||||
$this->setResponse(500, $e->getMessage());
|
||||
return false;
|
||||
};
|
||||
$api['content'] = isset($api['content']) ? $api['content'] : false;
|
||||
$this->setAPIResponse('success', null, 200, $api);
|
||||
return $api;
|
||||
}
|
||||
}
|
||||
22
docker/test/tvtracker/config/www/organizr/api/index.php
Normal file
22
docker/test/tvtracker/config/www/organizr/api/index.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
reset($_GET);
|
||||
$function = (key($_GET) ? str_replace("/", "_", key($_GET)) : false);
|
||||
function validateData($data)
|
||||
{
|
||||
$data = trim($data);
|
||||
$data = stripslashes($data);
|
||||
return htmlspecialchars($data);
|
||||
}
|
||||
|
||||
switch ($function) {
|
||||
case 'v1_auth':
|
||||
$group = ($_GET['group']) ?? 0;
|
||||
header('Location: v2/auth?group=' . validateData($group));
|
||||
exit;
|
||||
default:
|
||||
// Forward everything to v2 api
|
||||
$result['status'] = "error";
|
||||
$result['statusText'] = "Please Use api/v2";
|
||||
break;
|
||||
}
|
||||
header('Location: v2/');
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
$GLOBALS['organizrPages'][] = 'dependencies';
|
||||
function get_page_dependencies($Organizr)
|
||||
{
|
||||
if (!$Organizr) {
|
||||
$Organizr = new Organizr();
|
||||
}
|
||||
return '
|
||||
<script>
|
||||
</script>
|
||||
<div class="container-fluid">
|
||||
<div class="row bg-title">
|
||||
<div class="col-lg-3 col-md-4 col-sm-4 col-xs-12">
|
||||
<h4 class="page-title" lang="en">Organizr Dependency Check</h4>
|
||||
</div>
|
||||
<!-- /.col-lg-12 -->
|
||||
</div>
|
||||
<!--.row-->
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="white-box">
|
||||
<div class="row row-in">
|
||||
<div class="col-lg-4 col-sm-6 row-in-br">
|
||||
<ul class="col-in">
|
||||
<li>
|
||||
<span class="circle circle-md bg-warning dependency-dependencies-check"><i class="fa fa-spin fa-spinner"></i></span>
|
||||
</li>
|
||||
<li class="col-last">
|
||||
<h3 class="counter text-right m-t-15" lang="en">Dependencies</h3>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6 row-in-br b-r-none">
|
||||
<ul class="col-in">
|
||||
<li>
|
||||
<span class="circle circle-md bg-warning dependency-phpversion-check"><i class="fa fa-spin fa-spinner"></i></span>
|
||||
</li>
|
||||
<li class="col-last">
|
||||
<h3 class="counter text-right m-t-15" lang="en">PHP Version</h3>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-sm-6 b-0">
|
||||
<ul class="col-in">
|
||||
<li>
|
||||
<span class="circle circle-md bg-warning dependency-permissions-check"><i class="fa fa-spin fa-spinner"></i></span>
|
||||
</li>
|
||||
<li class="col-last">
|
||||
<h3 class="counter text-right m-t-15" lang="en">Permissions</h3>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-4 col-sm-6">
|
||||
<div class="panel panel-danger dependency-dependencies-check-listing-header">
|
||||
<div class="panel-heading dependency-dependencies-check-listing"> <i class="ti-alert fa-fw"></i> <span lang="en">Dependencies Missing</span>
|
||||
<div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a></div>
|
||||
</div>
|
||||
<div class="panel-wrapper collapse in" aria-expanded="true">
|
||||
<div class="panel-body">
|
||||
<ul class="common-list" id="depenency-info"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">PHP Version Check</span>
|
||||
<div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a></div>
|
||||
</div>
|
||||
<div class="panel-wrapper collapse in" aria-expanded="true">
|
||||
<table class="table table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td id="php-version-check" lang="en">Loading...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="php-version-check-user" lang="en">Loading...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">Web Folder</span>
|
||||
<div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-minus"></i></a></div>
|
||||
</div>
|
||||
<div class="panel-wrapper collapse in" aria-expanded="true">
|
||||
<table class="table table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>' . dirname(__DIR__, 2) . '</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="web-folder" lang="en">Loading...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-12">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"> <i class="ti-alert fa-fw"></i> <span lang="en">Browser Information</span>
|
||||
<div class="pull-right"><a href="#" data-perform="panel-collapse"><i class="ti-plus"></i></a></div>
|
||||
</div>
|
||||
<div class="panel-wrapper collapse" id="browser-info" aria-expanded="false"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!--./row-->
|
||||
</div>
|
||||
<!-- /.container-fluid -->
|
||||
';
|
||||
}
|
||||
149
docker/test/tvtracker/config/www/organizr/api/pages/error.php
Normal file
149
docker/test/tvtracker/config/www/organizr/api/pages/error.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
$GLOBALS['organizrPages'][] = 'error';
|
||||
function get_page_error($Organizr)
|
||||
{
|
||||
if (!$Organizr) {
|
||||
$Organizr = new Organizr();
|
||||
}
|
||||
if ((!$Organizr->hasDB())) {
|
||||
return false;
|
||||
}
|
||||
$nonRoot = isset($_GET['organizr']);
|
||||
$nonRootPath = ($nonRoot) ? $Organizr->getRootPath() : '';
|
||||
$error = $_GET['vars']['var1'] ?? 404;
|
||||
$errorDetails = $Organizr->errorCodes($error);
|
||||
$redirect = $_GET['vars']['var2'] ?? null;
|
||||
if ($redirect) {
|
||||
$Organizr->logger->debug($redirect);
|
||||
}
|
||||
$GLOBALS['responseCode'] = 200;
|
||||
return '
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
||||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
||||
<meta content="' . $Organizr->config['description'] . '" name="description">
|
||||
<meta content="CauseFX" name="author">
|
||||
' . $Organizr->favIcons($nonRootPath) . '
|
||||
<title>Error ' . $Organizr->config['title'] . '</title>
|
||||
' . $Organizr->loadResources(
|
||||
[
|
||||
'bootstrap/dist/css/bootstrap.min.css',
|
||||
'css/animate.css',
|
||||
'plugins/bower_components/overlayScrollbars/OverlayScrollbars.min.css',
|
||||
'css/dark.min.css',
|
||||
'css/organizr.min.css',
|
||||
'js/jquery-2.2.4.min.js',
|
||||
'js/jquery-lang.min.js'
|
||||
], $nonRootPath
|
||||
) . '
|
||||
' . $Organizr->setTheme(null, $nonRootPath) . '
|
||||
<style id="user-appearance"></style>
|
||||
<style id="custom-theme-css"></style>
|
||||
<style id="custom-css"></style>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"
|
||||
integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"
|
||||
integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo"
|
||||
crossorigin="anonymous"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body class="fix-header">
|
||||
<!-- ============================================================== -->
|
||||
<!-- Preloader -->
|
||||
<!-- ==============================================================
|
||||
<div id="preloader" class="preloader">
|
||||
<svg class="circular" viewbox="25 25 50 50">
|
||||
<circle class="path" cx="50" cy="50" fill="none" r="20" stroke-miterlimit="10" stroke-width="10"></circle>
|
||||
</svg>
|
||||
</div>-->
|
||||
<!-- ============================================================== -->
|
||||
<!-- Wrapper -->
|
||||
<!-- ============================================================== -->
|
||||
<section id="wrapper">
|
||||
<div class="error-box">
|
||||
<div class="error-body text-center">
|
||||
<h1 class="text-danger">' . $error . '</h1>
|
||||
<h2 class="text-uppercase" lang="en">' . $errorDetails['type'] . '</h2>
|
||||
<h3 class="text-uppercase" lang="en">' . $errorDetails['description'] . '</h3>
|
||||
<p class="text-muted m-t-30 m-b-30">Hey there, ' . $Organizr->user['username'] . '. Looks like you tried accessing something that just ain\'t right! WTF right?! </p>
|
||||
<a href="' . $nonRootPath . '" class="btn btn-danger btn-rounded waves-effect waves-light m-b-40">Back Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
languageList = ' . $Organizr->languagePacks(true) . '
|
||||
var langStrings = { "token": {} };
|
||||
var lang = new Lang();
|
||||
loadLanguageList();
|
||||
lang.init({
|
||||
currentLang: (getCookie("organizrLanguage")) ? getCookie("organizrLanguage") : "en",
|
||||
cookie: {
|
||||
name: "organizrLanguage",
|
||||
expiry: 365,
|
||||
path: "/"
|
||||
},
|
||||
allowCookieOverride: true
|
||||
});
|
||||
|
||||
$.urlParam = function(name){
|
||||
let results = new RegExp("[\?&]" + name + "=([^&#]*)").exec(window.location.href);
|
||||
if (results == null) {
|
||||
return null;
|
||||
} else {
|
||||
return decodeURI(results[1]) || 0;
|
||||
}
|
||||
};
|
||||
if ($.urlParam("return") !== null && "' . $Organizr->user['groupID'] . '" === "999") {
|
||||
local("set", "uri", $.urlParam("return"));
|
||||
}
|
||||
function localStorageSupport() {
|
||||
return (("localStorage" in window) && window["localStorage"] !== null)
|
||||
}
|
||||
function local(type,key,value=null){
|
||||
if (localStorageSupport) {
|
||||
switch (type) {
|
||||
case "set":
|
||||
case "s":
|
||||
localStorage.setItem(key,value);
|
||||
break;
|
||||
case "get":
|
||||
case "g":
|
||||
return localStorage.getItem(key);
|
||||
break;
|
||||
case "remove":
|
||||
case "r":
|
||||
localStorage.removeItem(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
function loadLanguageList(){
|
||||
$.each(languageList, function(i,v) {
|
||||
lang.dynamic(v.code, "' . $nonRootPath . 'js/langpack/"+v.filename);
|
||||
});
|
||||
}
|
||||
function getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
var decodedCookie = decodeURIComponent(document.cookie);
|
||||
var ca = decodedCookie.split(";");
|
||||
for(var i = 0; i <ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == " ") {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
';
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user