Compare commits

..

17 Commits

Author SHA1 Message Date
Safihre
dbff203c62 Update text files for 4.2.3RC2 2024-02-26 22:30:38 +01:00
Safihre
f45eb891cd Correct translatable texts about connection threads 2024-02-26 22:23:51 +01:00
Safihre
77b58240cf Reduce Server test timeout to 10s
Unless otherwise specified
Related to #2802
2024-02-26 22:23:51 +01:00
Safihre
97ae1ff10e Refactor part of database.py
Use autocommit and skip unnecessary checks on every connect
2024-02-26 22:23:51 +01:00
Safihre
8734a4f24b Update text files for 4.2.3RC1 2024-02-19 22:19:43 +01:00
Shane Mc Cormack
480fce55a8 Handle NNTP error code 451 (#2808)
* Handle error code 451.

This is used by some servers to show that an article was intentionally removed.
Fix #2807

* Add a warning when an unknown status code is given for an article.

* Make warning message translatable.
2024-02-19 22:06:29 +01:00
thezoggy
d4136fadd2 Add missing tooltips (#2800)
Co-authored-by: Safihre <safihre@sabnzbd.org>
2024-02-18 13:42:48 +01:00
Safihre
308bc375bd Update log message about version check 2024-02-18 13:42:31 +01:00
Safihre
3bbcf6a41e Update standby command on macOS 2024-02-18 13:41:33 +01:00
Safihre
3d5d10a4c1 Remove parsing of Group command code
Since we never request it.
2024-02-18 13:41:05 +01:00
Safihre
0e979c14f0 Remove Send Group option
Closes #2715
2024-02-18 13:38:31 +01:00
Safihre
70f49114ac Wrong archive password is used for Retry
Closes #2790
2024-02-18 13:38:13 +01:00
Safihre
699d75bb9f Update text files for 4.2.2 2024-01-30 21:46:56 +01:00
Safihre
95822704c8 Update black formatting 2024-01-30 21:36:34 +01:00
Safihre
76e5f69e67 Only attempt Windows Toasts on Windows 10 and above 2024-01-29 16:34:03 +01:00
Safihre
abd31d0249 Update text files for 4.2.2RC2 2024-01-24 13:28:19 +01:00
Safihre
9ae7ee6e2d Add logging which notification will be sent 2024-01-22 15:15:44 +01:00
274 changed files with 18654 additions and 25160 deletions

View File

@@ -40,4 +40,3 @@ f06891926661986fff52d6eb4b4cb120c71972d1
9bcbcaefdfecc85aedfd8e2f8aaa1ca7f959404e
433dcab02b29f7bd3827e237434034deecc1b549
9f6a9f991222efccc87b45a701086c95629c67b6
f89114ca7e1b20bf8e645ecd0b52b707ec857aa9

View File

@@ -3,7 +3,7 @@ description: >
Did you discover a bug in SABnzbd? Report it here!
If you are not 100% certain this is a bug please go to our forums, Reddit or Discord server first.
labels:
- Support
- Bug
body:
- type: input
attributes:
@@ -21,7 +21,6 @@ body:
options:
- linuxserver
- hotio
- binhex
- Other
- type: textarea
attributes:

View File

@@ -4,7 +4,7 @@ contact_links:
url: https://forums.sabnzbd.org/
about: Support questions can be asked on our forums, Reddit or Discord server.
- name: Discord
url: https://discord.sabnzbd.org
url: https://discord.gg/KQzDe7fvNU
about: Support questions can be asked on our forums, Reddit or Discord server.
- name: Reddit - r/sabnzbd
url: https://www.reddit.com/r/sabnzbd

View File

@@ -1,7 +1,7 @@
name: Feature request
description: What new feature would you like to have added to SABnzbd?
labels:
- Support
- Feature request
body:
- type: textarea
attributes:

6
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

18
.github/renovate.json vendored
View File

@@ -7,31 +7,25 @@
"schedule": [
"before 8am on Monday"
],
"baseBranches": ["develop", "feature/uvicorn"],
"ignorePaths": [
".github/workflows/**"
],
"pip_requirements": {
"fileMatch": [
"requirements.txt",
"tests/requirements.txt",
"builder/requirements.txt",
"builder/release-requirements.txt"
"builder/release-requirements.txt",
"builder/osx/requirements.txt"
]
},
"ignorePaths": [],
"ignoreDeps": [
"jaraco.text",
"jaraco.context",
"jaraco.collections",
"sabctools",
"paho-mqtt",
"werkzeug",
"tavern"
"pyinstaller"
],
"packageRules": [
{
"matchManagers": ["github-actions"],
"matchPackageNames": ["windows", "macos"],
"enabled": false
},
{
"matchPackagePatterns": [
"*"

View File

@@ -8,131 +8,122 @@ env:
jobs:
build_windows:
name: Build Windows binary (${{ matrix.architecture }})
strategy:
fail-fast: false
matrix:
include:
- architecture: x64
runs-on: windows-2022
- architecture: arm64
runs-on: windows-11-arm
runs-on: ${{ matrix.runs-on }}
timeout-minutes: 15
name: Build Windows binary
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
- uses: actions/checkout@v3
- name: Set up Python 3.12 (64bit)
uses: actions/setup-python@v4
with:
python-version: "3.14"
architecture: ${{ matrix.architecture }}
cache: pip
cache-dependency-path: "**/requirements.txt"
- name: Install Python dependencies
python-version: "3.12"
architecture: "x64"
- name: Cache Python virtualenv (64bit)
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv-64bit
with:
custom_virtualenv_dir: "venv64"
custom_cache_key_element: "release"
requirement_files: "**/requirements.txt"
- name: Install Python dependencies (64bit)
if: steps.cache-virtualenv-64bit.outputs.cache-hit != 'true'
# Without dependencies to make sure everything is covered in the requirements.txt
run: |
python --version
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt --no-dependencies
pip install --upgrade -r builder/requirements.txt --no-dependencies
- name: Build Windows standalone binary
id: windows_binary
run: python builder/package.py binary
- name: Upload Windows standalone binary (unsigned)
uses: actions/upload-artifact@v6
id: upload-unsigned-binary
with:
path: "*-win*-bin.zip"
name: Windows standalone binary (${{ matrix.architecture }})
- name: Sign Windows standalone binary
uses: signpath/github-action-submit-signing-request@v2
if: contains(github.ref, 'refs/tags/')
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORG_ID }}
project-slug: "sabnzbd"
artifact-configuration-slug: "sabnzbd-binary"
signing-policy-slug: "release-signing"
github-artifact-id: ${{ steps.upload-unsigned-binary.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: "signed"
- name: Upload Windows standalone binary (signed)
uses: actions/upload-artifact@v6
if: contains(github.ref, 'refs/tags/')
with:
name: Windows standalone binary (${{ matrix.architecture }}, signed)
path: "signed"
- name: Build Windows installer
if: matrix.architecture == 'x64'
- name: Build Windows standalone binary and installer (64bit)
run: python builder/package.py installer
- name: Upload Windows installer
if: matrix.architecture == 'x64'
uses: actions/upload-artifact@v6
id: upload-unsigned-installer
- name: Upload Windows standalone binary (64bit)
uses: actions/upload-artifact@v3
with:
path: "*-win64-bin.zip"
name: Windows standalone binary (64bit)
- name: Upload Windows installer (64bit)
uses: actions/upload-artifact@v3
with:
path: "*-win-setup.exe"
name: Windows installer (${{ matrix.architecture }})
- name: Sign Windows installer
if: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
uses: signpath/github-action-submit-signing-request@v2
name: Windows installer
- name: Set up Python 3.8 (32bit and legacy)
uses: actions/setup-python@v4
with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
organization-id: ${{ secrets.SIGNPATH_ORG_ID }}
project-slug: "sabnzbd"
artifact-configuration-slug: "sabnzbd-installer"
signing-policy-slug: "release-signing"
github-artifact-id: ${{ steps.upload-unsigned-installer.outputs.artifact-id }}
wait-for-completion: true
output-artifact-directory: "signed"
- name: Upload Windows installer (signed)
if: matrix.architecture == 'x64' && contains(github.ref, 'refs/tags/')
uses: actions/upload-artifact@v6
python-version: "3.8"
architecture: "x86"
- name: Cache Python virtualenv (32bit and legacy)
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv-32bit
with:
name: Windows installer (${{ matrix.architecture }}, signed)
path: "signed/*-win-setup.exe"
custom_virtualenv_dir: "venv32"
custom_cache_key_element: "release"
requirement_files: "**/requirements.txt"
- name: Install Python dependencies (32bit and legacy)
if: steps.cache-virtualenv-32bit.outputs.cache-hit != 'true'
# We do not care about the extra dependencies for the legacy build
run: |
python --version
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt --no-dependencies
pip install --upgrade -r builder/requirements.txt --no-dependencies
- name: Build Windows standalone binary (32bit and legacy)
run: python builder/package.py binary
- name: Upload Windows standalone binary (32bit and legacy)
uses: actions/upload-artifact@v3
with:
path: "*-win32-bin.zip"
name: Windows standalone binary (32bit and legacy)
build_macos:
name: Build macOS binary
runs-on: macos-14
timeout-minutes: 15
runs-on: macos-11
timeout-minutes: 30
env:
# We need the official Python, because the GA ones only support newer macOS versions
# The deployment target is picked up by the Python build tools automatically
# If updated, make sure to also set LSMinimumSystemVersion in SABnzbd.spec
PYTHON_VERSION: "3.14.2"
MACOSX_DEPLOYMENT_TARGET: "10.15"
PYTHON_VERSION: "3.12.2"
MACOSX_DEPLOYMENT_TARGET: "10.9"
# We need to force compile for universal2 support
CFLAGS: -arch x86_64 -arch arm64
ARCHFLAGS: -arch x86_64 -arch arm64
steps:
- uses: actions/checkout@v6
- name: Set up Python
# Only use this for the caching of pip packages!
uses: actions/setup-python@v6
with:
python-version: "3.14"
cache: pip
cache-dependency-path: "**/requirements.txt"
- uses: actions/checkout@v3
- name: Cache Python download
id: cache-python-download
uses: actions/cache@v5
uses: actions/cache@v3
with:
path: ~/python.pkg
key: cache-macOS-Python-${{ env.PYTHON_VERSION }}
- name: Get Python from python.org
- name: Get Python
if: steps.cache-python-download.outputs.cache-hit != 'true'
run: curl https://www.python.org/ftp/python/${PYTHON_VERSION}/python-${PYTHON_VERSION}-macos11.pkg -o ~/python.pkg
- name: Install Python
run: sudo installer -pkg ~/python.pkg -target /
- name: Cache Python virtualenv
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv
with:
custom_cache_key_element: "release"
requirement_files: "**/requirements.txt"
- name: Install Python dependencies
# We have to manually compile some modules as they don't automatically fetch universal2 binaries
# We have to manually take a few steps:
# 1. Because building cryptography is hard, and we cannot force pip to fetch universal2 version we
# first install the x86 version (and it's dependencies) and then manually fetch the universal2 build
# https://github.com/pypa/pip/issues/5453
# 2. We need to build the PyInstaller bootloader:
# https://github.com/pyinstaller/pyinstaller/issues/6235
if: steps.cache-virtualenv.outputs.cache-hit != 'true'
run: |
python3 --version
pip3 install --upgrade pip wheel
pip3 install --upgrade -r requirements.txt --no-binary cffi,CT3,PyYAML,charset_normalizer --no-dependencies
pip3 install --upgrade -r builder/requirements.txt --no-dependencies
pip3 install --upgrade -r requirements.txt --no-binary cffi --no-dependencies
pip3 uninstall cryptography -y
pip3 download -r builder/osx/requirements.txt --platform macosx_10_12_universal2 --only-binary :all: --no-dependencies --dest .
pip3 install -r builder/osx/requirements.txt --no-cache-dir --no-index --find-links .
PYINSTALLER_COMPILE_BOOTLOADER=1 pip3 install --upgrade -r builder/requirements.txt --no-binary pyinstaller --no-dependencies
- name: Import macOS codesign certificates
# Taken from https://github.com/Apple-Actions/import-codesign-certs/pull/27 (comments)
env:
@@ -142,8 +133,8 @@ jobs:
if: env.CERTIFICATES_P12
run: |
echo $CERTIFICATES_P12 | base64 --decode > certificate.p12
security create-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security default-keychain -s build.keychain
security create-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$MACOS_KEYCHAIN_TEMP_PASSWORD" build.keychain
security set-keychain-settings -lut 21600 build.keychain
security import certificate.p12 -k build.keychain -P "$CERTIFICATES_P12_PASSWORD" -T /usr/bin/codesign -T /usr/bin/productsign -T /usr/bin/xcrun
@@ -152,7 +143,7 @@ jobs:
# Run this on macOS so the line endings are correct by default
run: python builder/package.py source
- name: Upload source distribution
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v3
with:
path: "*-src.tar.gz"
name: Source distribution
@@ -165,88 +156,28 @@ jobs:
python3 builder/package.py app
python3 builder/make_dmg.py
- name: Upload macOS binary
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v3
with:
path: "*-macos.dmg"
name: macOS binary
build-snap:
name: Build Snap Packages (${{ matrix.linux_arch }})
timeout-minutes: 30
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
linux_arch: x64
- os: ubuntu-24.04-arm
linux_arch: arm64
steps:
- uses: actions/checkout@v6
- name: Cache par2cmdline-turbo tarball
uses: actions/cache@v5
id: cache-par2cmdline
# Clearing the cache in case of new version requires manual clearing in GitHub!
with:
path: snap/par2cmdline.tar.gz
key: cache-par2cmdline
- name: Download par2cmdline-turbo tarball
if: steps.cache-par2cmdline.outputs.cache-hit != 'true'
run: |
PAR2_TARBALL=$(curl -sL https://api.github.com/repos/animetosho/par2cmdline-turbo/releases/latest | jq -r '.tarball_url')
curl -o snap/par2cmdline.tar.gz -L "$PAR2_TARBALL"
- uses: snapcore/action-build@v1
name: Build snap
id: snapcraft
- name: Test snap installation
run: |
sudo snap install --dangerous *.snap
sudo snap connect sabnzbd:removable-media
# Basic smoke test - check that the binary exists and can show help
timeout 10s snap run sabnzbd --help || true
sudo snap remove sabnzbd
- name: Upload snap
uses: actions/upload-artifact@v6
with:
name: Snap package (${{ matrix.linux_arch }})
path: ${{ steps.snapcraft.outputs.snap }}
- name: Publish snap
uses: snapcore/action-publish@v1
if: contains(github.ref, 'refs/tags/')
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_TOKEN }}
with:
store_login: ${{ secrets.SNAP_TOKEN }}
snap: ${{ steps.snapcraft.outputs.snap }}
release: stable
path: "*-osx.dmg"
name: macOS binary (not notarized)
release:
name: Prepare Release
name: Publish Release
runs-on: ubuntu-latest
needs: [build_windows, build_macos]
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v4
with:
python-version: "3.14"
cache: pip
cache-dependency-path: "builder/release-requirements.txt"
- name: Download Source distribution artifact
uses: actions/download-artifact@v7
python-version: "3.x"
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
name: Source distribution
- name: Download macOS artifact
uses: actions/download-artifact@v7
with:
name: macOS binary
- name: Download Windows artifacts
uses: actions/download-artifact@v7
with:
pattern: ${{ (contains(github.ref, 'refs/tags/')) && '*signed*' || '*Windows*' }}
merge-multiple: true
path: dist
- name: Move all artifacts to main folder
run: find dist -type f -exec mv {} . \;
- name: Prepare official release
env:
AUTOMATION_GITHUB_TOKEN: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
@@ -254,3 +185,9 @@ jobs:
run: |
pip3 install -r builder/release-requirements.txt
python3 builder/release.py
- name: Release latest available Snap
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_TOKEN }}
run: |
sudo snap install snapcraft --classic
python3 snap/local/release_snap.py

View File

@@ -7,7 +7,7 @@ jobs:
name: Black Code Formatter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
- name: Black Code Formatter
uses: lgeiger/black-action@master
with:
@@ -20,7 +20,7 @@ jobs:
builder/SABnzbd.spec
tests
--line-length=120
--target-version=py39
--target-version=py38
--check
--diff
@@ -31,33 +31,46 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-architecture: ["x64"]
name: ["Linux"]
os: [ubuntu-latest]
os: [ubuntu-20.04]
include:
- name: macOS
os: macos-latest
python-version: "3.14"
python-version: "3.12"
python-architecture: "x64"
- name: Windows
os: windows-2022
python-version: "3.14"
os: windows-latest
python-version: "3.12"
python-architecture: "x64"
- name: Windows (32bit)
os: windows-latest
python-version: "3.8"
python-architecture: "x86"
steps:
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} ${{ matrix.python-architecture }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: "**/requirements.txt"
architecture: ${{ matrix.python-architecture }}
- name: Install system dependencies
if: runner.os == 'Linux'
run: sudo apt-get install unrar 7zip par2
run: sudo apt-get install unrar p7zip-full par2
- name: Cache Python virtualenv
uses: syphar/restore-virtualenv@v1.3
id: cache-virtualenv
with:
custom_cache_key_element: ci-${{ matrix.python-architecture }}
requirement_files: "**/requirements.txt"
- name: Install Python dependencies
if: steps.cache-virtualenv.outputs.cache-hit != 'true'
run: |
python --version
python -m pip install --upgrade pip wheel
pip install --upgrade -r requirements.txt --no-dependencies
pip install --upgrade -r requirements.txt
pip install --upgrade -r tests/requirements.txt
- name: Test SABnzbd
run: pytest -s

View File

@@ -10,7 +10,7 @@ jobs:
if: github.repository_owner == 'sabnzbd'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- uses: actions/stale@v8
with:
days-before-stale: 21
days-before-close: 7
@@ -26,7 +26,7 @@ jobs:
if: github.repository_owner == 'sabnzbd'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v6
- uses: dessant/lock-threads@v4
with:
log-output: true
issue-inactive-days: 60

View File

@@ -12,7 +12,7 @@ jobs:
env:
TX_TOKEN: ${{ secrets.TX_TOKEN }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v3
with:
token: ${{ secrets.AUTOMATION_GITHUB_TOKEN }}
- name: Generate translatable texts
@@ -30,7 +30,7 @@ jobs:
run: |
python3 tools/make_mo.py
- name: Push translatable and translated texts back to repo
uses: stefanzweifel/git-auto-commit-action@v7.1.0
uses: stefanzweifel/git-auto-commit-action@v4.16.0
if: env.TX_TOKEN
with:
commit_message: |

View File

@@ -1,5 +1,5 @@
(c) Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
(c) Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
The SABnzbd-Team is:

View File

@@ -4,7 +4,7 @@
0) LICENSE
-------------------------------------------------------------------------------
(c) Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
(c) Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
@@ -52,7 +52,7 @@ Specific guides to install from source are available for Windows and macOS:
https://sabnzbd.org/wiki/installation/install-macos
https://sabnzbd.org/wiki/installation/install-from-source-windows
Only Python 3.9 and above is supported.
Only Python 3.8 and above is supported.
On Linux systems you need to install:
par2 unrar python3-setuptools python3-pip

View File

@@ -1,4 +1,4 @@
(c) Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
(c) Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License

View File

@@ -2,7 +2,7 @@ SABnzbd - The automated Usenet download tool
============================================
[![License](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
[![Join our Discord](https://img.shields.io/discord/976737547558461480?color=7289DA&label=Discord&logo=Discord&logoColor=white)](https://discord.sabnzbd.org)
[![Join our Discord](https://img.shields.io/discord/976737547558461480?color=7289DA&label=Discord&logo=Discord&logoColor=white)](https://discord.gg/KQzDe7fvNU)
SABnzbd is an Open Source Binary Newsreader written in Python.
@@ -16,7 +16,7 @@ If you want to know more you can head over to our website: https://sabnzbd.org.
SABnzbd has a few dependencies you'll need before you can get running. If you've previously run SABnzbd from one of the various Linux packages, then you likely already have all the needed dependencies. If not, here's what you're looking for:
- `python` (Python 3.9 and above, often called `python3`)
- `python` (Python 3.8 and above, often called `python3`)
- Python modules listed in `requirements.txt`. Install with `python3 -m pip install -r requirements.txt -U`
- `par2` (Multi-threaded par2 installation guide can be found [here](https://sabnzbd.org/wiki/installation/multicore-par2))
- `unrar` (make sure you get the "official" non-free version of unrar)
@@ -66,12 +66,3 @@ Conditions:
- Bugfixes created specifically for a release branch are done there (because they are specific, they're not cherry-picked to `develop`).
- Bugfixes done on `develop` may be cherry-picked to a release branch.
- We will not release a 1.0.2 if a 1.1.0 has already been released.
## Privacy Policy
This program will not transfer any information to other networked systems unless
specifically requested by the user or the person installing or operating it.
## Code Signing Policy
For our Windows release, free code signing is provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org).

View File

@@ -1,31 +1,78 @@
Release Notes - SABnzbd 4.6.0 Beta 2
Release Notes - SABnzbd 4.2.3 Release Candidate 2
=========================================================
This is the second beta release of version 4.6.
This is the third bug-fix release of SABnzbd 4.2.0.
## New features in 4.6.0
## Bug-fixes and changes since 4.2.2:
* Added support for NNTP Pipelining which eliminates idle waiting between
requests, significantly improving speeds on high-latency connections.
Read more here: https://sabnzbd.org/wiki/advanced/nntp-pipelining
* Dynamically increase Assembler limits on faster connections.
* Improved disk speed measurement in Status window.
* Enable `verify_xff_header` by default.
* Reduce delays between jobs during post-processing.
* If a download only has `.nzb` files inside, the new downloads
will include the name of the original download.
* Dropped support for Python 3.8.
* Windows: Added Windows ARM (portable) release.
* **Bug-fixes:**
* Handle new status code for missing articles, which could result in timeouts.
* Retry of failed job would not use the password provided.
* Optimize database handling in order to prevent locking errors.
* macOS: System standby after finishing the queue would not always work.
## Bug fixes since 4.5.0
* **Changes:**
* Remove `Send Group` option for Servers.
* `Check before download` could get stuck or fail to reject.
* No error was shown in case NZB upload failed.
* Correct mobile layout if `Full Width` is enabled.
* Aborted Direct Unpack could result in no files being unpacked.
* Windows: Tray icon disappears after Explorer restart.
* macOS: Slow to start on some network setups.
## Bug-fixes and changes since 4.2.1:
* **Bug-fixes:**
* RSS readout could result in a crash if Duplicate Detection was enabled.
* Passwords were not always correctly parsed.
* Warnings could show even if `helpful_warnings` was disabled.
* Duplicate Detection would trigger again on URLs if they were resumed.
* Windows: Fatal crash could occur if ran as Service or on older Windows versions.
* **Changes:**
* Parsing of filenames from the NZB was extended to allow more exotic formatting.
* Windows: Installer will automatically shutdown SABnzbd if it is running.
## Bug-fixes and changes since 4.2.0:
* **Bug-fixes:**
* New downloads did not appear in the History after the update to 4.2.0.
* **Changes:**
* The external IPv6-address is shown, instead of the internal address.
* Windows: Network drives as `Download Folder` are no longer blocked, only warned against.
## Key changes since 4.1.0
* **Duplicate detection workflow overhaul:**
* `Series Duplicate Detection` was replaced by `Smart Duplicate Detection`
that can also detect `Movie` and `Daily Show` duplicates.
* Additionally, duplicates will also be detected if they are still in the queue.
* More information: https://sabnzbd.org/wiki/duplicate-detection
* **Interface changes:**
* Added ability to filter the Queue and History by `status`.
* RSS-feed that provided the download is shown in History details.
* macOS/Windows 10 & 11: Added `Open Folder` button to `Job/Queue finished` notifications.
Clicking any type of notification will now open a browser with SABnzbd.
* **Performance and usability improvements:**
* Numerous smaller performance improvements were made.
* Server IP-address selection was optimized.
* The `Internet Bandwidth` test was made more reliable.
* macOS/Windows: Updated to Python 3.12 and par2cmdline-turbo v1.1.1.
* **Configuration changes:**
* The `On queue finish script` is now set in Switches.
* Reduced recursive unpacking to 2 levels, instead of 5.
* Duplicate detection related Pre-queue script input parameters were removed.
You will need to update your Pre-queue script.
More information: https://sabnzbd.org/wiki/configuration/4.2/scripts/pre-queue-scripts
* Stricter check if `Complete Folder` is inside `Download Folder`.
* Windows: Prevent use of network drive as `Download Folder`.
## Bug fixes since 4.1.0
* Fixed an issue where the multi-select option in the queue was not working for some users.
* Prevented a crash that would occur during the saving of configuration settings.
* Jobs with larger articles could stall the queue for several minutes.
* Ensured that server warnings are always displayed to users.
* If `weblogging` was enabled, output was also written to regular log.
* Fixed an issue where removing a failed download from the History could break active downloads.
## Upgrade notices
@@ -38,15 +85,10 @@ This is the second beta release of version 4.6.
* Read `ISSUES.txt` or https://sabnzbd.org/wiki/introduction/known-issues
## Code Signing Policy
Windows code signing is provided by SignPath.io using a SignPath Foundation certificate.
## About
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically, thanks to its web-based
user interface and advanced built-in post-processing options that automatically verify, repair,
extract and clean up posts downloaded from Usenet.
(c) Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
(c) Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 -OO
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,8 +19,8 @@ import sys
# Trick to show a better message on older Python
# releases that don't support walrus operator
if Python_39_is_required_to_run_SABnzbd := sys.hexversion < 0x03090000:
print("Sorry, requires Python 3.9 or above")
if Python_38_is_required_to_run_SABnzbd := sys.hexversion < 0x03080000:
print("Sorry, requires Python 3.8 or above")
print("You can read more at: https://sabnzbd.org/wiki/installation/install-off-modules")
sys.exit(1)
@@ -32,15 +32,14 @@ import traceback
import getopt
import signal
import socket
import platform
import subprocess
import multiprocessing
import ssl
import time
import re
import gc
import threading
import http.cookies
from typing import Any
from typing import List, Dict, Any
try:
import sabctools
@@ -48,7 +47,6 @@ try:
import feedparser
import configobj
import cherrypy
import cheroot.errors
import portend
import cryptography
import chardet
@@ -66,7 +64,7 @@ import sabnzbd
import sabnzbd.lang
import sabnzbd.interface
from sabnzbd.constants import (
DEF_NETWORKING_TIMEOUT,
DEF_TIMEOUT,
DEF_LOG_ERRFILE,
DEF_MAIN_TMPL,
DEF_STD_WEB_DIR,
@@ -94,6 +92,8 @@ from sabnzbd.misc import (
get_from_url,
upload_file_to_sabnzbd,
is_localhost,
is_lan_addr,
ip_in_subnet,
helpful_warning,
set_https_verification,
)
@@ -103,6 +103,8 @@ import sabnzbd.config as config
import sabnzbd.cfg
import sabnzbd.notifier as notifier
import sabnzbd.zconfig
from sabnzbd.getipaddress import local_ipv4
import sabnzbd.utils.ssdp as ssdp
try:
import win32api
@@ -119,7 +121,7 @@ try:
win32api.SetConsoleCtrlHandler(sabnzbd.sig_handler, True)
except ImportError:
if sabnzbd.WINDOWS:
if sabnzbd.WIN32:
print("Sorry, requires Python module PyWin32.")
sys.exit(1)
@@ -142,7 +144,7 @@ class GUIHandler(logging.Handler):
"""Initializes the handler"""
logging.Handler.__init__(self)
self._size: int = size
self.store: list[dict[str, Any]] = []
self.store: List[Dict[str, Any]] = []
def emit(self, record: logging.LogRecord):
"""Emit a record by adding it to our private queue"""
@@ -167,8 +169,7 @@ class GUIHandler(logging.Handler):
# This prevents endless looping if the notification service itself throws an error/warning
# We don't check based on message content, because if it includes a timestamp it's not unique
if not any(
stored_warning["origin"] == warning["origin"]
and stored_warning["time"] + DEF_NETWORKING_TIMEOUT > time.time()
stored_warning["origin"] == warning["origin"] and stored_warning["time"] + DEF_TIMEOUT > time.time()
for stored_warning in self.store
):
if record.levelno == logging.WARNING:
@@ -207,7 +208,7 @@ def print_help():
print(" -w --weblogging Enable cherrypy access logging")
print()
print(" -b --browser <0..1> Auto browser launch (0= off, 1= on) [*]")
if sabnzbd.WINDOWS:
if sabnzbd.WIN32:
print(" -d --daemon Use when run as a service")
else:
print(" -d --daemon Fork daemon process")
@@ -241,7 +242,7 @@ def print_version():
"""
%s-%s
(C) Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
(C) Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
SABnzbd comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. It is licensed under the
@@ -295,14 +296,14 @@ def daemonize():
os.dup2(f.fileno(), sys.stderr.fileno())
def abort_and_show_error(browserhost, web_port, err=""):
def abort_and_show_error(browserhost, cherryport, err=""):
"""Abort program because of CherryPy troubles"""
logging.error(T("Failed to start web-interface") + " : " + str(err))
if not sabnzbd.DAEMON:
if "49" in err:
panic_host(browserhost, web_port)
panic_host(browserhost, cherryport)
else:
panic_port(browserhost, web_port)
panic_port(browserhost, cherryport)
sabnzbd.halt()
exit_sab(2)
@@ -312,7 +313,7 @@ def identify_web_template(key, defweb, wdir):
if wdir is None:
try:
wdir = fix_webname(key())
except Exception:
except:
wdir = ""
if not wdir:
wdir = defweb
@@ -372,25 +373,25 @@ def get_user_profile_paths():
# just assume that everything defaults to the program dir
sabnzbd.DIR_LCLDATA = sabnzbd.DIR_PROG
sabnzbd.DIR_HOME = sabnzbd.DIR_PROG
if sabnzbd.WINDOWS:
if sabnzbd.WIN32:
# Ignore Win32 "logoff" signal
# This should work, but it doesn't
# Instead the signal_handler will ignore the "logoff" signal
# signal.signal(5, signal.SIG_IGN)
pass
return
elif sabnzbd.WINDOWS:
elif sabnzbd.WIN32:
try:
path = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, None, 0)
sabnzbd.DIR_LCLDATA = os.path.join(path, DEF_WORKDIR)
sabnzbd.DIR_HOME = os.environ["USERPROFILE"]
except Exception:
except:
try:
root = os.environ["AppData"]
user = os.environ["USERPROFILE"]
sabnzbd.DIR_LCLDATA = "%s\\%s" % (root.replace("\\Roaming", "\\Local"), DEF_WORKDIR)
sabnzbd.DIR_HOME = user
except Exception:
except:
pass
# Long-path everything
@@ -426,7 +427,10 @@ def print_modules():
# Check if we managed to link, warning for now
# It won't work on OpenSSL < 1.1.1 anyway, so we skip the check there
if not sabnzbd.decoder.SABCTOOLS_OPENSSL_LINKED and ssl.OPENSSL_VERSION_INFO >= (1, 1, 1):
helpful_warning(T("Unable to link to OpenSSL, optimized SSL connection functions will not be used."))
logging.warning(
"Could not link to OpenSSL library, please report here: "
"https://github.com/sabnzbd/sabnzbd/issues/2421"
)
else:
# Wrong SABCTools version, if it was fully missing it would fail to start due to check at the very top
logging.error(
@@ -440,6 +444,9 @@ def print_modules():
logging.info("Cryptography module (v%s)... found!", cryptography.__version__)
if sabnzbd.WIN32 and sabnzbd.newsunpack.MULTIPAR_COMMAND:
logging.info("MultiPar binary... found (%s)", sabnzbd.newsunpack.MULTIPAR_COMMAND)
if sabnzbd.newsunpack.PAR2_COMMAND:
logging.info("par2 binary... found (%s)", sabnzbd.newsunpack.PAR2_COMMAND)
else:
@@ -455,7 +462,7 @@ def print_modules():
have_str = "%.2f" % (float(sabnzbd.newsunpack.RAR_VERSION) / 100)
want_str = "%.2f" % (float(sabnzbd.constants.REC_RAR_VERSION) / 100)
helpful_warning(T("Your UNRAR version is %s, we recommend version %s or higher.<br />"), have_str, want_str)
elif not (sabnzbd.WINDOWS or sabnzbd.MACOS):
elif not (sabnzbd.WIN32 or sabnzbd.MACOS):
logging.info("UNRAR binary version %.2f", (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
else:
logging.error(T("unrar binary... NOT found"))
@@ -464,12 +471,12 @@ def print_modules():
if sabnzbd.newsunpack.SEVENZIP_COMMAND:
logging.info("7za binary... found (%s)", sabnzbd.newsunpack.SEVENZIP_COMMAND)
if not (sabnzbd.WINDOWS or sabnzbd.MACOS):
if not (sabnzbd.WIN32 or sabnzbd.MACOS):
logging.info("7za binary version %s", sabnzbd.newsunpack.SEVENZIP_VERSION)
else:
logging.warning(T("7za binary... NOT found!"))
if not sabnzbd.WINDOWS:
if not sabnzbd.WIN32:
if sabnzbd.newsunpack.NICE_COMMAND:
logging.info("nice binary... found (%s)", sabnzbd.newsunpack.NICE_COMMAND)
else:
@@ -522,37 +529,39 @@ def check_resolve(host):
return True
def get_webhost(web_host, web_port, https_port):
def get_webhost(cherryhost, cherryport, https_port):
"""Determine the webhost address and port,
return (host, port, browserhost)
"""
if web_host == "0.0.0.0" and not check_resolve("127.0.0.1"):
web_host = ""
elif web_host == "::" and not check_resolve("::1"):
web_host = ""
if cherryhost == "0.0.0.0" and not check_resolve("127.0.0.1"):
cherryhost = ""
elif cherryhost == "::" and not check_resolve("::1"):
cherryhost = ""
if web_host is None:
web_host = sabnzbd.cfg.web_host()
if cherryhost is None:
cherryhost = sabnzbd.cfg.cherryhost()
else:
sabnzbd.cfg.web_host.set(web_host)
sabnzbd.cfg.cherryhost.set(cherryhost)
# Get IP address, but discard APIPA/IPV6
# If only APIPA's or IPV6 are found, fall back to localhost
ipv4 = ipv6 = False
localhost = hostip = "localhost"
try:
# Valid user defined name?
info = socket.getaddrinfo(web_host, None)
info = socket.getaddrinfo(socket.gethostname(), None)
except socket.error:
if not is_localhost(web_host):
web_host = "0.0.0.0"
# Hostname does not resolve
try:
info = socket.getaddrinfo(localhost, None)
# Valid user defined name?
info = socket.getaddrinfo(cherryhost, None)
except socket.error:
info = socket.getaddrinfo("127.0.0.1", None)
localhost = "127.0.0.1"
if not is_localhost(cherryhost):
cherryhost = "0.0.0.0"
try:
info = socket.getaddrinfo(localhost, None)
except socket.error:
info = socket.getaddrinfo("127.0.0.1", None)
localhost = "127.0.0.1"
for item in info:
ip = str(item[4][0])
if ip.startswith("169.254."):
@@ -564,75 +573,75 @@ def get_webhost(web_host, web_port, https_port):
hostip = ip
# A blank host will use the local ip address
if web_host == "":
if cherryhost == "":
if ipv6 and ipv4:
# To protect Firefox users, use numeric IP
web_host = hostip
cherryhost = hostip
browserhost = hostip
else:
web_host = socket.gethostname()
browserhost = web_host
cherryhost = socket.gethostname()
browserhost = cherryhost
# 0.0.0.0 will listen on all ipv4 interfaces (no ipv6 addresses)
elif web_host == "0.0.0.0":
elif cherryhost == "0.0.0.0":
# Just take the gamble for this
web_host = "0.0.0.0"
cherryhost = "0.0.0.0"
browserhost = localhost
# :: will listen on all ipv6 interfaces (no ipv4 addresses)
elif web_host in ("::", "[::]"):
web_host = web_host.strip("[").strip("]")
elif cherryhost in ("::", "[::]"):
cherryhost = cherryhost.strip("[").strip("]")
# Assume '::1' == 'localhost'
browserhost = localhost
# IPV6 address
elif "[" in web_host or ":" in web_host:
browserhost = web_host
elif "[" in cherryhost or ":" in cherryhost:
browserhost = cherryhost
# IPV6 numeric address
elif web_host.replace(".", "").isdigit():
elif cherryhost.replace(".", "").isdigit():
# IPV4 numerical
browserhost = web_host
browserhost = cherryhost
elif web_host == localhost:
web_host = localhost
elif cherryhost == localhost:
cherryhost = localhost
browserhost = localhost
else:
# If on APIPA, use numerical IP, to help FireFoxers
if ipv6 and ipv4:
web_host = hostip
browserhost = web_host
cherryhost = hostip
browserhost = cherryhost
# Some systems don't like brackets in numerical ipv6
if sabnzbd.MACOS:
web_host = web_host.strip("[]")
cherryhost = cherryhost.strip("[]")
else:
try:
socket.getaddrinfo(web_host, None)
socket.getaddrinfo(cherryhost, None)
except socket.error:
web_host = web_host.strip("[]")
cherryhost = cherryhost.strip("[]")
if ipv6 and ipv4 and web_host == "" and sabnzbd.WINDOWS:
if ipv6 and ipv4 and cherryhost == "" and sabnzbd.WIN32:
helpful_warning(T("Please be aware the 0.0.0.0 hostname will need an IPv6 address for external access"))
if web_host == "localhost" and not sabnzbd.WINDOWS and not sabnzbd.MACOS:
if cherryhost == "localhost" and not sabnzbd.WIN32 and not sabnzbd.MACOS:
# On the Ubuntu family, localhost leads to problems for CherryPy
ips = ip_extract()
if "127.0.0.1" in ips and "::1" in ips:
web_host = "127.0.0.1"
cherryhost = "127.0.0.1"
if ips[0] != "127.0.0.1":
browserhost = "127.0.0.1"
# This is to please Chrome on macOS
if web_host == "localhost" and sabnzbd.MACOS:
web_host = "127.0.0.1"
if cherryhost == "localhost" and sabnzbd.MACOS:
cherryhost = "127.0.0.1"
browserhost = "localhost"
if web_port is None:
web_port = sabnzbd.cfg.web_port.get_int()
if cherryport is None:
cherryport = sabnzbd.cfg.cherryport.get_int()
else:
sabnzbd.cfg.web_port.set(str(web_port))
sabnzbd.cfg.cherryport.set(str(cherryport))
if https_port is None:
https_port = sabnzbd.cfg.https_port.get_int()
@@ -641,12 +650,12 @@ def get_webhost(web_host, web_port, https_port):
# if the https port was specified, assume they want HTTPS enabling also
sabnzbd.cfg.enable_https.set(True)
if web_port == https_port and sabnzbd.cfg.enable_https():
if cherryport == https_port and sabnzbd.cfg.enable_https():
sabnzbd.cfg.enable_https.set(False)
# Should have a translated message, but that's not available yet
logging.error(T("HTTP and HTTPS ports cannot be the same"))
return web_host, web_port, browserhost, https_port
return cherryhost, cherryport, browserhost, https_port
def attach_server(host, port, cert=None, key=None, chain=None):
@@ -671,7 +680,7 @@ def is_sabnzbd_running(url):
ver = get_from_url(url)
set_https_verification(prev)
return ver and (re.search(r"\d+\.\d+\.", ver) or ver.strip() == sabnzbd.__version__)
except Exception:
except:
return False
@@ -682,7 +691,7 @@ def find_free_port(host, currentport):
try:
portend.free(host, currentport, timeout=0.025)
return currentport
except Exception:
except:
currentport += 5
n += 1
return 0
@@ -831,8 +840,8 @@ def main():
fork = False
pause = False
inifile = None
web_host = None
web_port = None
cherryhost = None
cherryport = None
https_port = None
cherrypylogging = None
clean_up = False
@@ -855,7 +864,7 @@ def main():
if opt == "--servicecall":
sabnzbd.MY_FULLNAME = arg
elif opt in ("-d", "--daemon"):
if not sabnzbd.WINDOWS:
if not sabnzbd.WIN32:
fork = True
autobrowser = False
sabnzbd.DAEMON = True
@@ -870,11 +879,14 @@ def main():
elif opt in ("-t", "--templates"):
web_dir = arg
elif opt in ("-s", "--server"):
(web_host, web_port) = split_host(arg)
(cherryhost, cherryport) = split_host(arg)
elif opt in ("-n", "--nobrowser"):
autobrowser = False
elif opt in ("-b", "--browser"):
autobrowser = sabnzbd.misc.bool_conv(arg)
try:
autobrowser = bool(int(arg))
except ValueError:
autobrowser = True
elif opt == "--autorestarted":
autorestarted = True
elif opt in ("-c", "--clean"):
@@ -884,7 +896,7 @@ def main():
elif opt in ("-l", "--logging"):
try:
logging_level = int(arg)
except Exception:
except:
logging_level = -2
if logging_level < -1 or logging_level > 2:
print_help()
@@ -993,35 +1005,35 @@ def main():
sabnzbd.cfg.ipv6_hosting.set(ipv6_hosting)
# Determine web host address
web_host, web_port, browserhost, https_port = get_webhost(web_host, web_port, https_port)
cherryhost, cherryport, browserhost, https_port = get_webhost(cherryhost, cherryport, https_port)
enable_https = sabnzbd.cfg.enable_https()
# When this is a daemon, just check and bail out if port in use
if sabnzbd.DAEMON:
if enable_https and https_port:
try:
portend.free(web_host, https_port, timeout=0.05)
portend.free(cherryhost, https_port, timeout=0.05)
except IOError:
abort_and_show_error(browserhost, web_port)
except Exception:
abort_and_show_error(browserhost, web_port, "49")
abort_and_show_error(browserhost, cherryport)
except:
abort_and_show_error(browserhost, cherryport, "49")
try:
portend.free(web_host, web_port, timeout=0.05)
portend.free(cherryhost, cherryport, timeout=0.05)
except IOError:
abort_and_show_error(browserhost, web_port)
except Exception:
abort_and_show_error(browserhost, web_port, "49")
abort_and_show_error(browserhost, cherryport)
except:
abort_and_show_error(browserhost, cherryport, "49")
# Windows instance is reachable through registry
url = None
if sabnzbd.WINDOWS and not new_instance:
if sabnzbd.WIN32 and not new_instance:
url = get_connection_info()
if url and check_for_sabnzbd(url, upload_nzbs, autobrowser):
exit_sab(0)
# SSL
if enable_https:
port = https_port or web_port
port = https_port or cherryport
try:
portend.free(browserhost, port, timeout=0.05)
except IOError as error:
@@ -1033,7 +1045,7 @@ def main():
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
# Bail out if we have fixed our ports after first start-up
if sabnzbd.cfg.fixed_ports():
abort_and_show_error(browserhost, web_port)
abort_and_show_error(browserhost, cherryport)
# Find free port to bind
newport = find_free_port(browserhost, port)
if newport > 0:
@@ -1043,34 +1055,34 @@ def main():
sabnzbd.cfg.https_port.set(newport)
else:
# In case HTTPS == HTTP port
web_port = newport
sabnzbd.cfg.web_port.set(newport)
except Exception:
cherryport = newport
sabnzbd.cfg.cherryport.set(newport)
except:
# Something else wrong, probably badly specified host
abort_and_show_error(browserhost, web_port, "49")
abort_and_show_error(browserhost, cherryport, "49")
# NonSSL check if there's no HTTPS or we only use 1 port
if not (enable_https and not https_port):
try:
portend.free(browserhost, web_port, timeout=0.05)
portend.free(browserhost, cherryport, timeout=0.05)
except IOError as error:
if str(error) == "Port not bound.":
pass
else:
if not url:
url = "http://%s:%s%s/api?" % (browserhost, web_port, sabnzbd.cfg.url_base())
url = "http://%s:%s%s/api?" % (browserhost, cherryport, sabnzbd.cfg.url_base())
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
# Bail out if we have fixed our ports after first start-up
if sabnzbd.cfg.fixed_ports():
abort_and_show_error(browserhost, web_port)
abort_and_show_error(browserhost, cherryport)
# Find free port to bind
port = find_free_port(browserhost, web_port)
port = find_free_port(browserhost, cherryport)
if port > 0:
sabnzbd.cfg.web_port.set(port)
web_port = port
except Exception:
sabnzbd.cfg.cherryport.set(port)
cherryport = port
except:
# Something else wrong, probably badly specified host
abort_and_show_error(browserhost, web_port, "49")
abort_and_show_error(browserhost, cherryport, "49")
# We found a port, now we never check again
sabnzbd.cfg.fixed_ports.set(True)
@@ -1082,11 +1094,12 @@ def main():
sys.exit(1)
if clean_up:
for x in globber_full(logdir):
xlist = globber_full(logdir)
for x in xlist:
if RSS_FILE_NAME not in x:
try:
os.remove(x)
except Exception:
except:
pass
# Prevent the logger from raising exceptions
@@ -1098,13 +1111,12 @@ def main():
logging_level = sabnzbd.cfg.log_level()
else:
sabnzbd.cfg.log_level.set(logging_level)
sabnzbd.LOGFILE = os.path.join(logdir, DEF_LOG_FILE)
logformat = "%(asctime)s::%(levelname)s::[%(module)s:%(lineno)d] %(message)s"
logger.setLevel(LOGLEVELS[logging_level + 1])
try:
if not no_file_log:
sabnzbd.LOGFILE = os.path.join(logdir, DEF_LOG_FILE)
rollover_log = logging.handlers.RotatingFileHandler(
sabnzbd.LOGFILE, "a+", sabnzbd.cfg.log_size(), sabnzbd.cfg.log_backups()
)
@@ -1117,7 +1129,7 @@ def main():
exit_sab(2)
# Fork on non-Windows processes
if fork and not sabnzbd.WINDOWS:
if fork and not sabnzbd.WIN32:
daemonize()
else:
if console_logging:
@@ -1131,8 +1143,53 @@ def main():
# Start SABnzbd
logging.info("--------------------------------")
logging.info("%s-%s", sabnzbd.MY_NAME, sabnzbd.__version__)
# See if we can get version from git when running an unknown revision
if sabnzbd.__baseline__ == "unknown":
try:
sabnzbd.__baseline__ = sabnzbd.misc.run_command(
["git", "rev-parse", "--short", "HEAD"], cwd=sabnzbd.DIR_PROG
).strip()
except:
pass
logging.info("Commit = %s", sabnzbd.__baseline__)
logging.info("Full executable path = %s", sabnzbd.MY_FULLNAME)
logging.info("Arguments = %s", sabnzbd.CMDLINE)
logging.info("Python-version = %s", sys.version)
logging.info("Dockerized = %s", sabnzbd.DOCKER)
logging.info("CPU architecture = %s", platform.uname().machine)
try:
logging.info("Platform = %s - %s", os.name, platform.platform())
except:
# Can fail on special platforms (like Snapcraft or embedded)
pass
# Find encoding; relevant for external processing activities
logging.info("Preferred encoding = %s", sabnzbd.encoding.CODEPAGE)
# On Linux/FreeBSD/Unix "UTF-8" is strongly, strongly advised:
if not sabnzbd.WIN32 and not sabnzbd.MACOS and not ("utf-8" in sabnzbd.encoding.CODEPAGE.lower()):
helpful_warning(
T(
"SABnzbd was started with encoding %s, this should be UTF-8. Expect problems with Unicoded file and directory names in downloads."
),
sabnzbd.encoding.CODEPAGE,
)
# Verify umask, we need at least 700
if not sabnzbd.WIN32 and sabnzbd.ORG_UMASK > int("077", 8):
sabnzbd.misc.helpful_warning(
T("Current umask (%o) might deny SABnzbd access to the files and folders it creates."),
sabnzbd.ORG_UMASK,
)
# Log JSON module in case of problems
logging.debug("JSON-module = %s %s", sabnzbd.api.json.__name__, sabnzbd.api.json.__version__)
# SSL Information
logging.info("SSL version = %s", ssl.OPENSSL_VERSION)
# Load (extra) certificates if supplied by certifi
# This is optional and provided in the binaries
@@ -1143,11 +1200,15 @@ def main():
os.environ["SSL_CERT_FILE"] = certifi.where()
logging.info("Certifi version = %s", certifi.__version__)
logging.info("Loaded additional certificates from %s", os.environ["SSL_CERT_FILE"])
except Exception:
except:
# Sometimes the certificate file is blocked
logging.warning(T("Could not load additional certificates from certifi package"))
logging.info("Traceback: ", exc_info=True)
# List the number of certificates available (can take up to 1.5 seconds)
if sabnzbd.cfg.log_level() > 1:
logging.debug("Available certificates = %s", repr(ssl.create_default_context().cert_store_stats()))
logging.info("Using INI file %s", inifile)
# Store auto-browser setting from command line
@@ -1167,7 +1228,7 @@ def main():
# Handle the several tray icons
if sabnzbd.cfg.tray_icon() and not sabnzbd.DAEMON and not sabnzbd.WIN_SERVICE:
if sabnzbd.WINDOWS:
if sabnzbd.WIN32:
sabnzbd.WINTRAY = sabnzbd.sabtray.SABTrayThread()
elif sabnzbd.LINUX_POWER and os.environ.get("DISPLAY"):
try:
@@ -1178,7 +1239,7 @@ def main():
import sabnzbd.sabtraylinux
sabnzbd.sabtraylinux.StatusIcon()
except Exception:
except:
logging.info("python3-gi not found, no SysTray.")
# Find external programs
@@ -1207,7 +1268,7 @@ def main():
try:
trialcontext.load_cert_chain(https_cert, https_key)
logging.info("HTTPS keys are OK")
except Exception:
except:
logging.warning(T("Disabled HTTPS because of invalid CERT and KEY files"))
logging.info("Traceback: ", exc_info=True)
enable_https = False
@@ -1216,29 +1277,29 @@ def main():
# Starting of the webserver
# Determine if this system has multiple definitions for 'localhost'
hosts = all_localhosts()
multilocal = len(hosts) > 1 and web_host in ("localhost", "0.0.0.0")
multilocal = len(hosts) > 1 and cherryhost in ("localhost", "0.0.0.0")
# For 0.0.0.0 CherryPy will always pick IPv4, so make sure the secondary localhost is IPv6
if multilocal and web_host == "0.0.0.0" and hosts[1] == "127.0.0.1":
if multilocal and cherryhost == "0.0.0.0" and hosts[1] == "127.0.0.1":
hosts[1] = "::1"
# The Windows binary requires numeric localhost as primary address
if web_host == "localhost":
web_host = hosts[0]
if cherryhost == "localhost":
cherryhost = hosts[0]
if enable_https:
if https_port:
# Extra HTTP port for primary localhost
attach_server(web_host, web_port)
attach_server(cherryhost, cherryport)
if multilocal:
# Extra HTTP port for secondary localhost
attach_server(hosts[1], web_port)
attach_server(hosts[1], cherryport)
# Extra HTTPS port for secondary localhost
attach_server(hosts[1], https_port, https_cert, https_key, https_chain)
web_port = https_port
cherryport = https_port
elif multilocal:
# Extra HTTPS port for secondary localhost
attach_server(hosts[1], web_port, https_cert, https_key, https_chain)
attach_server(hosts[1], cherryport, https_cert, https_key, https_chain)
cherrypy.config.update(
{
@@ -1250,7 +1311,7 @@ def main():
)
elif multilocal:
# Extra HTTP port for secondary localhost
attach_server(hosts[1], web_port)
attach_server(hosts[1], cherryport)
if no_login:
sabnzbd.cfg.username.set("")
@@ -1273,8 +1334,8 @@ def main():
cherrypy.config.update(
{
"server.environment": "production",
"server.socket_host": web_host,
"server.socket_port": web_port,
"server.socket_host": cherryhost,
"server.socket_port": cherryport,
"server.shutdown_timeout": 0,
"engine.autoreload.on": False,
"tools.encode.on": True,
@@ -1286,16 +1347,6 @@ def main():
}
)
# Monkey-patch key validation to prevent cherrypy from stumbling over invalid cookies
http.cookies._is_legal_key = lambda _: True
# Catch shutdown errors that can break cherrypy/cheroot
# See https://github.com/cherrypy/cheroot/issues/710
try:
cheroot.errors.acceptable_sock_shutdown_exceptions += (OSError,)
except AttributeError:
pass
# Do we want CherryPy Logging? Cannot be done via the config
cherrypy.log.screen = False
cherrypy.log.access_log.propagate = False
@@ -1345,17 +1396,17 @@ def main():
# Set authentication for CherryPy
sabnzbd.interface.set_auth(cherrypy.config)
logging.info("Starting web-interface on %s:%s", web_host, web_port)
logging.info("Starting web-interface on %s:%s", cherryhost, cherryport)
sabnzbd.cfg.log_level.callback(guard_loglevel)
try:
cherrypy.engine.start()
except Exception:
except:
# Since the webserver is started by cherrypy in a separate thread, we can't really catch any
# start-up errors. This try/except only catches very few errors, the rest is only shown in the console.
logging.error(T("Failed to start web-interface: "), exc_info=True)
abort_and_show_error(browserhost, web_port)
abort_and_show_error(browserhost, cherryport)
# Create a record of the active cert/key/chain files, for use with config.create_config_backup()
if enable_https:
@@ -1363,18 +1414,24 @@ def main():
if full_path := getattr(sabnzbd.cfg, setting).get_path():
sabnzbd.CONFIG_BACKUP_HTTPS_OK.append(full_path)
# Set URL for browser
if enable_https:
sabnzbd.BROWSER_URL = "https://%s:%s%s" % (browserhost, web_port, sabnzbd.cfg.url_base())
else:
sabnzbd.BROWSER_URL = "http://%s:%s%s" % (browserhost, web_port, sabnzbd.cfg.url_base())
if sabnzbd.WIN32:
if enable_https:
mode = "s"
else:
mode = ""
api_url = "http%s://%s:%s%s/api?apikey=%s" % (
mode,
browserhost,
cherryport,
sabnzbd.cfg.url_base(),
sabnzbd.cfg.api_key(),
)
if sabnzbd.WINDOWS:
# Write URL for uploads and version check directly to registry
set_connection_info(f"{sabnzbd.BROWSER_URL}/api?apikey={sabnzbd.cfg.api_key()}")
# Write URL directly to registry
set_connection_info(api_url)
if pid_path or pid_file:
sabnzbd.pid_file(pid_path, pid_file, web_port)
sabnzbd.pid_file(pid_path, pid_file, cherryport)
# Stop here in case of fatal errors
if sabnzbd.NO_DOWNLOADING:
@@ -1387,7 +1444,7 @@ def main():
logging.info("Starting %s-%s", sabnzbd.MY_NAME, sabnzbd.__version__)
try:
sabnzbd.start()
except Exception:
except:
logging.exception("Failed to start %s-%s", sabnzbd.MY_NAME, sabnzbd.__version__)
sabnzbd.halt()
@@ -1396,13 +1453,56 @@ def main():
for upload_nzb in upload_nzbs:
sabnzbd.nzbparser.add_nzbfile(upload_nzb)
# Set URL for browser
if enable_https:
browser_url = "https://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
else:
browser_url = "http://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
sabnzbd.BROWSER_URL = browser_url
if not autorestarted:
launch_a_browser(sabnzbd.BROWSER_URL)
launch_a_browser(browser_url)
notifier.send_notification("SABnzbd", T("SABnzbd %s started") % sabnzbd.__version__, "startup")
autorestarted = False
# Do checks and miscellaneous logging in separate thread for performance
threading.Thread(target=sabnzbd.delayed_startup_actions).start()
# Start SSDP and Bonjour if SABnzbd isn't listening on localhost only
if sabnzbd.cfg.enable_broadcast() and not is_localhost(cherryhost):
# Try to find a LAN IP address for SSDP/Bonjour
if is_lan_addr(cherryhost):
# A specific listening address was configured, use that
external_host = cherryhost
else:
# Fall back to the IPv4 address of the LAN interface
external_host = local_ipv4()
logging.debug("Using %s as host address for Bonjour and SSDP", external_host)
# Only broadcast to local network addresses. If local ranges have been defined, further
# restrict broadcasts to those specific ranges in order to avoid broadcasting to the "wrong"
# private network when the system is connected to multiple such networks (e.g. a corporate
# VPN in addition to a standard household LAN).
if is_lan_addr(external_host) and (
(not sabnzbd.cfg.local_ranges()) or any(ip_in_subnet(external_host, r) for r in sabnzbd.cfg.local_ranges())
):
# Start Bonjour and SSDP
sabnzbd.zconfig.set_bonjour(external_host, cherryport)
# Set URL for browser for external hosts
ssdp_url = "%s://%s:%s%s" % (
("https" if enable_https else "http"),
external_host,
cherryport,
sabnzbd.cfg.url_base(),
)
ssdp.start_ssdp(
external_host,
"SABnzbd",
ssdp_url,
"SABnzbd %s" % sabnzbd.__version__,
"SABnzbd Team",
"https://sabnzbd.org/",
"SABnzbd %s" % sabnzbd.__version__,
ssdp_broadcast_interval=sabnzbd.cfg.ssdp_broadcast_interval(),
)
# Have to keep this running, otherwise logging will terminate
timer = 0
@@ -1471,7 +1571,7 @@ def main():
# Use external service handler to do the restart
# Wait 5 seconds to clean up
subprocess.Popen("timeout 5 & sc start SABnzbd", shell=True)
elif sabnzbd.WINDOWS:
elif sabnzbd.WIN32:
# Just a simple restart of the exe
os.execv(sys.executable, ['"%s"' % arg for arg in sys.argv])
else:
@@ -1493,7 +1593,7 @@ def main():
if hasattr(sys, "frozen") and sabnzbd.MACOS:
try:
AppHelper.stopEventLoop()
except Exception:
except:
# Failing AppHelper library!
os._exit(0)
elif sabnzbd.WIN_SERVICE:
@@ -1508,7 +1608,7 @@ def main():
##############################################################################
if sabnzbd.WINDOWS:
if sabnzbd.WIN32:
class SABnzbd(win32serviceutil.ServiceFramework):
"""Win32 Service Handler"""
@@ -1624,20 +1724,21 @@ if __name__ == "__main__":
signal.signal(signal.SIGINT, sabnzbd.sig_handler)
signal.signal(signal.SIGTERM, sabnzbd.sig_handler)
if sabnzbd.WINDOWS:
if sabnzbd.WIN32:
if not handle_windows_service():
main()
elif sabnzbd.MACOS and sabnzbd.FOUNDATION:
# macOS binary runner
from threading import Thread
from PyObjCTools import AppHelper
from AppKit import NSApplication
from sabnzbd.macosmenu import SABnzbdDelegate
from sabnzbd.osxmenu import SABnzbdDelegate
# Need to run the main application in separate thread because the eventLoop
# has to be in the main thread. The eventLoop is required for the menu.
# This code is made with trial-and-error, please feel free to improve!
class startApp(threading.Thread):
class startApp(Thread):
def run(self):
main()
AppHelper.stopEventLoop()

View File

@@ -1,5 +1,6 @@
# -*- mode: python -*-
import os
import re
import sys
from PyInstaller.building.api import EXE, COLLECT, PYZ
@@ -7,14 +8,13 @@ from PyInstaller.building.build_main import Analysis
from PyInstaller.building.osx import BUNDLE
from PyInstaller.utils.hooks import collect_data_files, collect_submodules
from builder.constants import EXTRA_FILES, EXTRA_FOLDERS, RELEASE_VERSION, RELEASE_VERSION_TUPLE
from builder.constants import EXTRA_FILES, EXTRA_FOLDERS, RELEASE_VERSION
# Add extra files in the PyInstaller-spec
extra_pyinstaller_files = []
# Add hidden imports
extra_hiddenimports = ["Cheetah.DummyTransaction", "cheroot.ssl.builtin", "certifi"]
extra_hiddenimports.extend(collect_submodules("apprise"))
extra_hiddenimports.extend(collect_submodules("babelfish.converters"))
extra_hiddenimports.extend(collect_submodules("guessit.data"))
@@ -22,9 +22,9 @@ extra_hiddenimports.extend(collect_submodules("guessit.data"))
if sys.platform == "darwin":
extra_hiddenimports.extend(["objc", "PyObjCTools"])
# macOS folders
EXTRA_FOLDERS += ["macos/par2/", "macos/unrar/", "macos/7zip/"]
EXTRA_FOLDERS += ["osx/par2/", "osx/unrar/", "osx/7zip/"]
# Add NZB-icon file
extra_pyinstaller_files.append(("builder/macos/image/nzbfile.icns", "."))
extra_pyinstaller_files.append(("builder/osx/image/nzbfile.icns", "."))
# Version information is set differently on macOS
version_info = None
else:
@@ -40,16 +40,20 @@ else:
)
# Windows
extra_hiddenimports.extend(["win32timezone", "winrt.windows.foundation.collections"])
EXTRA_FOLDERS += ["win/par2/", "win/unrar/", "win/7zip/"]
extra_hiddenimports.append("win32timezone")
EXTRA_FOLDERS += ["win/multipar/", "win/par2/", "win/unrar/", "win/7zip/"]
EXTRA_FILES += ["portable.cmd"]
# Parse the version info
version_regexed = re.search(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)", RELEASE_VERSION)
version_tuple = (int(version_regexed.group(1)), int(version_regexed.group(2)), int(version_regexed.group(3)), 0)
# Detailed instructions are in the PyInstaller documentation
# We don't include the alpha/beta/rc in the counters
version_info = VSVersionInfo(
ffi=FixedFileInfo(
filevers=RELEASE_VERSION_TUPLE,
prodvers=RELEASE_VERSION_TUPLE,
filevers=version_tuple,
prodvers=version_tuple,
mask=0x3F,
flags=0x0,
OS=0x40004,
@@ -87,15 +91,12 @@ for folder_item in EXTRA_FOLDERS:
# Add babelfish data files
extra_pyinstaller_files.extend(collect_data_files("babelfish"))
extra_pyinstaller_files.extend(collect_data_files("guessit"))
extra_pyinstaller_files.extend(collect_data_files("apprise"))
extra_pyinstaller_files.extend(collect_data_files("dateutil"))
pyi_analysis = Analysis(
["SABnzbd.py"],
datas=extra_pyinstaller_files,
hiddenimports=extra_hiddenimports,
excludes=["ujson", "FixTk", "tcl", "tk", "_tkinter", "tkinter", "Tkinter", "pydoc", "pydoc_data.topics"],
module_collection_mode={"apprise.plugins": "py"},
)
pyz = PYZ(pyi_analysis.pure, pyi_analysis.zipped_data)
@@ -118,7 +119,7 @@ exe = EXE(
contents_directory=".",
version=version_info,
target_arch="universal2",
entitlements_file="builder/macos/entitlements.plist",
entitlements_file="builder/osx/entitlements.plist",
codesign_identity=codesign_identity,
)
@@ -166,14 +167,14 @@ if sys.platform == "darwin":
"NSPersistentStoreTypeKey": "Binary",
}
],
"LSMinimumSystemVersion": "10.13",
"LSMinimumSystemVersion": "10.9",
"LSEnvironment": {"LANG": "en_US.UTF-8", "LC_ALL": "en_US.UTF-8"},
}
app = BUNDLE(
coll,
name="SABnzbd.app",
icon="builder/macos/image/sabnzbdplus.icns",
icon="builder/osx/image/sabnzbdplus.icns",
bundle_identifier="org.sabnzbd.sabnzbd",
info_plist=info_plist,
)

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 -OO
# Copyright 2008-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2008-2024 by The SABnzbd-Team (sabnzbd.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,8 +16,6 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
import platform
import re
# Constants
VERSION_FILE = "sabnzbd/version.py"
@@ -35,26 +33,16 @@ RELEASE_VERSION = __version__
# Pre-releases are longer than 6 characters (e.g. 3.1.0Beta1 vs 3.1.0, but also 3.0.11)
PRERELEASE = len(RELEASE_VERSION) > 5
# Parse the version info for Windows file properties information
version_regexed = re.search(r"(\d+)\.(\d+)\.(\d+)([a-zA-Z]*)(\d*)", RELEASE_VERSION)
RELEASE_VERSION_TUPLE = (int(version_regexed.group(1)), int(version_regexed.group(2)), int(version_regexed.group(3)), 0)
RELEASE_VERSION_BASE = f"{RELEASE_VERSION_TUPLE[0]}.{RELEASE_VERSION_TUPLE[1]}.{RELEASE_VERSION_TUPLE[2]}"
# Define release name
RELEASE_NAME = "SABnzbd-%s" % RELEASE_VERSION
RELEASE_TITLE = "SABnzbd %s" % RELEASE_VERSION
RELEASE_SRC = RELEASE_NAME + "-src.tar.gz"
RELEASE_WIN_BIN_X64 = RELEASE_NAME + "-win64-bin.zip"
RELEASE_WIN_BIN_ARM64 = RELEASE_NAME + "-win-arm64-bin.zip"
RELEASE_WIN_INSTALLER = RELEASE_NAME + "-win-setup.exe"
RELEASE_MACOS = RELEASE_NAME + "-macos.dmg"
RELEASE_BINARY_32 = RELEASE_NAME + "-win32-bin.zip"
RELEASE_BINARY_64 = RELEASE_NAME + "-win64-bin.zip"
RELEASE_INSTALLER = RELEASE_NAME + "-win-setup.exe"
RELEASE_MACOS = RELEASE_NAME + "-osx.dmg"
RELEASE_README = "README.mkd"
# Detect architecture
RELEASE_WIN_BIN = RELEASE_WIN_BIN_X64
if platform.machine() == "ARM64":
RELEASE_WIN_BIN = RELEASE_WIN_BIN_ARM64
# Used in package.py and SABnzbd.spec
EXTRA_FILES = [
RELEASE_README,

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 -OO
# Copyright 2008-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2008-2024 by The SABnzbd-Team (sabnzbd.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -24,7 +24,7 @@ if __name__ == "__main__":
# Check for DMGBuild
try:
import dmgbuild
except Exception:
except:
print("Requires dmgbuild-module, use pip install dmgbuild")
exit()
@@ -39,7 +39,7 @@ if __name__ == "__main__":
# Create sub-folder to upload later
release = RELEASE_VERSION
prod = "SABnzbd-" + release
fileDmg = prod + "-macos.dmg"
fileDmg = prod + "-osx.dmg"
# Path to app file
apppath = "dist/SABnzbd.app"
@@ -48,8 +48,8 @@ if __name__ == "__main__":
readmepath = os.path.join(apppath, "Contents/Resources/README.txt")
# Path to background and the icon
backgroundpath = "builder/macos/image/sabnzbd_new_bg.png"
iconpath = "builder/macos/image/sabnzbdplus.icns"
backgroundpath = "builder/osx/image/sabnzbd_new_bg.png"
iconpath = "builder/osx/image/sabnzbdplus.icns"
# Make DMG
print("Building DMG")

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,3 @@
# Special requirements for macOS universal2 binary release
# This way dependabot can auto-update them
cryptography==41.0.7

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 -OO
# Copyright 2008-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2008-2024 by The SABnzbd-Team (sabnzbd.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -16,6 +16,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import glob
import platform
import re
import sys
import os
@@ -27,16 +28,16 @@ import tarfile
import urllib.request
import urllib.error
import configobj
import packaging.version
from typing import List
from constants import (
RELEASE_VERSION,
RELEASE_VERSION_TUPLE,
VERSION_FILE,
RELEASE_README,
RELEASE_NAME,
RELEASE_WIN_BIN,
RELEASE_WIN_INSTALLER,
RELEASE_BINARY_32,
RELEASE_BINARY_64,
RELEASE_INSTALLER,
ON_GITHUB_ACTIONS,
RELEASE_THIS,
RELEASE_SRC,
@@ -69,9 +70,9 @@ def delete_files_glob(glob_pattern: str, allow_no_matches: bool = False):
raise FileNotFoundError(f"No files found that match '{glob_pattern}'")
def run_external_command(command: list[str], print_output: bool = True, **kwargs):
def run_external_command(command: List[str], print_output: bool = True):
"""Wrapper to ease the use of calling external programs"""
process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs)
process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output, _ = process.communicate()
ret = process.wait()
if (output and print_output) or ret != 0:
@@ -108,52 +109,6 @@ def patch_version_file(release_name):
ver.write(version_file)
def test_macos_min_version(binary_path: str):
# Skip check if nothing was set
if macos_min_version := os.environ.get("MACOSX_DEPLOYMENT_TARGET"):
# Skip any arm64 specific files
if "arm64" in binary_path:
print(f"Skipping arm64 binary {binary_path}")
return
# Check minimum macOS version is at least mac OS10.13
# We only check the x86_64 since for arm64 it's always macOS 11+
print(f"Checking if binary supports macOS {macos_min_version} and above: {binary_path}")
otool_output = run_external_command(
[
"otool",
"-arch",
"x86_64",
"-l",
binary_path,
],
print_output=False,
)
# Parse the output for LC_BUILD_VERSION minos
# The output is very large, so that's why we enumerate over it
req_version = packaging.version.parse(macos_min_version)
bin_version = None
lines = otool_output.split("\n")
for line_nr, line in enumerate(lines):
if "LC_VERSION_MIN_MACOSX" in line:
# Display the version in the next lines
bin_version = packaging.version.parse(lines[line_nr + 2].split()[1])
elif "minos" in line:
bin_version = packaging.version.parse(line.split()[1])
if bin_version and bin_version > req_version:
raise ValueError(f"{binary_path} requires {bin_version}, we want {req_version}")
else:
# We got the information we need
break
else:
print(lines)
raise RuntimeError(f"Could not determine minimum macOS version for {binary_path}")
else:
print(f"Skipping macOS version check, MACOSX_DEPLOYMENT_TARGET not set")
def test_sab_binary(binary_path: str):
"""Wrapper to have a simple start-up test for the binary"""
with tempfile.TemporaryDirectory() as config_dir:
@@ -170,7 +125,7 @@ def test_sab_binary(binary_path: str):
try:
urllib.request.urlopen(base_url, timeout=1).read()
break
except Exception:
except:
time.sleep(1)
else:
# Print console output and give some time to print
@@ -246,21 +201,30 @@ if __name__ == "__main__":
if not os.path.exists("locale"):
raise FileNotFoundError("Failed to compile language files")
if "binary" in sys.argv:
# Make sure we remove any existing build-folders
safe_remove("build")
safe_remove("dist")
safe_remove(RELEASE_NAME)
# Copy the specification
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
if "binary" in sys.argv or "installer" in sys.argv:
# Must be run on Windows
if sys.platform != "win32":
raise RuntimeError("Binary should be created on Windows")
# Make sure we remove any existing build-folders
safe_remove("build")
safe_remove("dist")
# Check what architecture we are on
RELEASE_BINARY = RELEASE_BINARY_32
BUILDING_64BIT = False
if platform.architecture()[0] == "64bit":
RELEASE_BINARY = RELEASE_BINARY_64
BUILDING_64BIT = True
# Remove any leftovers
safe_remove(RELEASE_NAME)
safe_remove(RELEASE_WIN_BIN)
safe_remove(RELEASE_BINARY)
# Run PyInstaller and check output
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
shutil.copytree("dist/SABnzbd-console", "dist/SABnzbd", dirs_exist_ok=True)
@@ -268,52 +232,47 @@ if __name__ == "__main__":
# Remove unwanted DLL's
shutil.rmtree("dist/SABnzbd/Pythonwin")
delete_files_glob("dist/SABnzbd/api-ms-win*.dll", allow_no_matches=True)
delete_files_glob("dist/SABnzbd/ucrtbase.dll", allow_no_matches=True)
if BUILDING_64BIT:
# These are only present on 64bit (Python 3.9+)
delete_files_glob("dist/SABnzbd/api-ms-win*.dll", allow_no_matches=True)
delete_files_glob("dist/SABnzbd/ucrtbase.dll", allow_no_matches=True)
# Test the release
test_sab_binary("dist/SABnzbd/SABnzbd.exe")
# Remove 32bit external executables
delete_files_glob("dist/SABnzbd/win/par2/par2.exe")
delete_files_glob("dist/SABnzbd/win/multipar/par2j.exe")
delete_files_glob("dist/SABnzbd/win/unrar/UnRAR.exe")
if "installer" in sys.argv:
# Needs to be run on 64 bit
if not BUILDING_64BIT:
raise RuntimeError("Installer should be created on 64bit Python")
# Compile NSIS translations
safe_remove("NSIS_Installer.nsi")
safe_remove("NSIS_Installer.nsi.tmp")
shutil.copyfile("builder/win/NSIS_Installer.nsi", "NSIS_Installer.nsi")
run_external_command([sys.executable, "tools/make_mo.py", "nsis"])
# Run NSIS to build installer
run_external_command(
[
"makensis.exe",
"/V3",
"/DSAB_PRODUCT=%s" % RELEASE_NAME,
"/DSAB_VERSION=%s" % RELEASE_VERSION,
"/DSAB_FILE=%s" % RELEASE_INSTALLER,
"NSIS_Installer.nsi.tmp",
]
)
# Rename the folder
shutil.copytree("dist/SABnzbd", RELEASE_NAME)
# Create the archive
run_external_command(["win/7zip/7za.exe", "a", RELEASE_WIN_BIN, "SABnzbd"], cwd="dist")
shutil.move(f"dist/{RELEASE_WIN_BIN}", RELEASE_WIN_BIN)
run_external_command(["win/7zip/7za.exe", "a", RELEASE_BINARY, RELEASE_NAME])
if "installer" in sys.argv:
# Check if we have the dist folder
if not os.path.exists("dist/SABnzbd/SABnzbd.exe"):
raise FileNotFoundError("SABnzbd executable not found, run binary creation first")
# Check if we have a signed version
if os.path.exists(f"signed/{RELEASE_WIN_BIN}"):
print("Using signed version of SABnzbd binaries")
safe_remove("dist/SABnzbd")
run_external_command(["win/7zip/7za.exe", "x", "-odist", f"signed/{RELEASE_WIN_BIN}"])
# Make sure it exists
if not os.path.exists("dist/SABnzbd/SABnzbd.exe"):
raise FileNotFoundError("SABnzbd executable not found, signed zip extraction failed")
elif RELEASE_THIS:
raise FileNotFoundError("Signed SABnzbd executable not found, required for release!")
else:
print("Using unsigned version of SABnzbd binaries")
# Compile NSIS translations
safe_remove("NSIS_Installer.nsi")
safe_remove("NSIS_Installer.nsi.tmp")
shutil.copyfile("builder/win/NSIS_Installer.nsi", "NSIS_Installer.nsi")
run_external_command([sys.executable, "tools/make_mo.py", "nsis"])
# Run NSIS to build installer
run_external_command(
[
"makensis.exe",
"/V3",
"/DSAB_VERSION=%s" % RELEASE_VERSION,
"/DSAB_VERSIONKEY=%s" % ".".join(map(str, RELEASE_VERSION_TUPLE)),
"/DSAB_FILE=%s" % RELEASE_WIN_INSTALLER,
"NSIS_Installer.nsi.tmp",
]
)
# Test the release, as the very last step to not mess with any release code
test_sab_binary("dist/SABnzbd/SABnzbd.exe")
if "app" in sys.argv:
# Must be run on macOS
@@ -329,17 +288,14 @@ if __name__ == "__main__":
# Otherwise the signature of the main application becomes invalid
if authority:
files_to_sign = [
"macos/par2/par2",
"macos/unrar/unrar",
"macos/unrar/arm64/unrar",
"macos/7zip/7zz",
"osx/par2/par2-turbo",
"osx/par2/arm64/par2-turbo",
"osx/unrar/unrar",
"osx/unrar/arm64/unrar",
"osx/7zip/7zz",
]
for file_to_sign in files_to_sign:
# Make sure it supports the macOS versions we want first
test_macos_min_version(file_to_sign)
# Then sign in
print("Signing %s with hardened runtime" % file_to_sign)
print("Signing %s with hardended runtime" % file_to_sign)
run_external_command(
[
"codesign",
@@ -349,7 +305,7 @@ if __name__ == "__main__":
"--options",
"runtime",
"--entitlements",
"builder/macos/entitlements.plist",
"builder/osx/entitlements.plist",
"-s",
authority,
file_to_sign,
@@ -359,21 +315,17 @@ if __name__ == "__main__":
print("Signed %s!" % file_to_sign)
# Run PyInstaller and check output
shutil.copyfile("builder/SABnzbd.spec", "SABnzbd.spec")
run_external_command([sys.executable, "-O", "-m", "PyInstaller", "SABnzbd.spec"])
# Make sure we created a fully universal2 release when releasing or during CI
if RELEASE_THIS or ON_GITHUB_ACTIONS:
for bin_to_check in glob.glob("dist/SABnzbd.app/**/*.so", recursive=True):
for bin_to_check in glob.glob("dist/SABnzbd.app/Contents/MacOS/**/*.so", recursive=True):
print("Checking if binary is universal2: %s" % bin_to_check)
file_output = run_external_command(["file", bin_to_check], print_output=False)
# Make sure we have both arm64 and x86
if not ("x86_64" in file_output and "arm64" in file_output):
raise RuntimeError("Non-universal2 binary found!")
# Make sure it supports the macOS versions we want
test_macos_min_version(bin_to_check)
# Only continue if we can sign
if authority:
# We use PyInstaller to sign the main SABnzbd executable and the SABnzbd.app
@@ -403,8 +355,8 @@ if __name__ == "__main__":
if authority not in sign_result or "adhoc" in sign_result or "invalid" in sign_result:
raise RuntimeError("Signature of %s seems invalid!" % file_to_check)
# Always notarize, as newer macOS versions don't allow any code without it
if notarization_user and notarization_pass:
# Only notarize for real builds that we want to deploy
if notarization_user and notarization_pass and RELEASE_THIS:
# Prepare zip to upload to notarization service
print("Creating zip to send to Apple notarization service")
# We need to use ditto, otherwise the signature gets lost!
@@ -438,6 +390,8 @@ if __name__ == "__main__":
# Staple the notarization!
print("Approved! Stapling the result to the app")
run_external_command(["xcrun", "stapler", "staple", "dist/SABnzbd.app"])
elif notarization_user and notarization_pass:
print("Notarization skipped, tag commit to trigger notarization!")
else:
print("Notarization skipped, NOTARIZATION_USER or NOTARIZATION_PASS missing.")
else:
@@ -495,7 +449,7 @@ if __name__ == "__main__":
tarinfo.uid = 0
tarinfo.gid = 0
if _file in ("SABnzbd.py", "Sample-PostProc.sh", "make_mo.py", "msgfmt.py"):
# Force Linux/macOS scripts as executable
# Force Linux/OSX scripts as executable
tarinfo.mode = 0o755
else:
tarinfo.mode = 0o644

View File

@@ -1,2 +1,2 @@
PyGithub==2.8.1
praw==7.8.1
PyGithub==2.1.1
praw==7.7.1

View File

@@ -1,5 +1,5 @@
#!/usr/bin/python3 -OO
# Copyright 2008-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2008-2024 by The SABnzbd-Team (sabnzbd.org)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -26,26 +26,24 @@ import praw
from constants import (
RELEASE_VERSION,
RELEASE_VERSION_BASE,
PRERELEASE,
RELEASE_SRC,
RELEASE_WIN_BIN_X64,
RELEASE_WIN_BIN_ARM64,
RELEASE_WIN_INSTALLER,
RELEASE_BINARY_32,
RELEASE_BINARY_64,
RELEASE_INSTALLER,
RELEASE_MACOS,
RELEASE_README,
RELEASE_THIS,
RELEASE_TITLE,
APPDATA_FILE,
ON_GITHUB_ACTIONS,
)
# Verify we have all assets
files_to_check = (
RELEASE_SRC,
RELEASE_WIN_BIN_X64,
RELEASE_WIN_BIN_ARM64,
RELEASE_WIN_INSTALLER,
RELEASE_BINARY_32,
RELEASE_BINARY_64,
RELEASE_INSTALLER,
RELEASE_MACOS,
RELEASE_README,
)
@@ -55,14 +53,9 @@ for file_to_check in files_to_check:
print("All release files are present")
# Verify that appdata file is updated
if not isinstance(ET.parse(APPDATA_FILE).find(f"./releases/release[@version='{RELEASE_VERSION_BASE}']"), ET.Element):
release_missing = f"Could not find {RELEASE_VERSION_BASE} in {APPDATA_FILE}"
if RELEASE_THIS:
raise RuntimeError(release_missing)
elif ON_GITHUB_ACTIONS:
print(f"::warning file={APPDATA_FILE},title=Missing release::{release_missing}")
else:
print(release_missing)
if not PRERELEASE:
if not isinstance(ET.parse(APPDATA_FILE).find(f"./releases/release[@version='{RELEASE_VERSION}']"), ET.Element):
raise RuntimeError(f"Could not find {RELEASE_VERSION} in {APPDATA_FILE}")
# Calculate hashes for Synology release
with open(RELEASE_SRC, "rb") as inp_file:
@@ -77,7 +70,7 @@ print("----")
# Check if tagged as release and check for token
gh_token = os.environ.get("AUTOMATION_GITHUB_TOKEN", "")
if RELEASE_THIS and gh_token:
gh_obj = github.Github(auth=github.Auth.Token(gh_token))
gh_obj = github.Github(gh_token)
gh_repo = gh_obj.get_repo("sabnzbd/sabnzbd")
# Read the release notes
@@ -88,7 +81,7 @@ if RELEASE_THIS and gh_token:
for release in gh_repo.get_releases():
if release.tag_name == RELEASE_VERSION:
gh_release = release
print("Found existing release %s" % gh_release.name)
print("Found existing release %s" % gh_release.title)
break
else:
# Did not find it, so create the release, use the GitHub tag we got as input
@@ -114,7 +107,7 @@ if RELEASE_THIS and gh_token:
print("Removing existing asset %s " % gh_asset.name)
gh_asset.delete_asset()
# Upload the new one
print("Uploading %s to release %s" % (file_to_check, gh_release.name))
print("Uploading %s to release %s" % (file_to_check, gh_release.title))
gh_release.upload_asset(file_to_check)
# Check if we now have all files
@@ -240,7 +233,7 @@ if RELEASE_THIS and gh_token:
readme_lines = readme_file.readlines()
# Put the download link after the title
readme_lines[2] = "## https://sabnzbd.org/downloads\n\n"
readme_lines[2] = "## https://sabnzbd.org/downloads\n"
# Use the header in the readme as title
title = readme_lines[0]

View File

@@ -1,19 +1,28 @@
# Basic build requirements
# Note that not all sub-dependencies are listed, but only ones we know could cause trouble
pyinstaller==6.17.0
packaging==25.0
pyinstaller-hooks-contrib==2025.10
altgraph==0.17.5
wrapt==2.0.1
setuptools==80.9.0
pyinstaller==5.13.2
packaging==23.2
pyinstaller-hooks-contrib==2024.0
altgraph==0.17.4
wrapt==1.16.0
setuptools==69.0.3
certifi
# Required on 32bit Windows, exclude it based on Python-version
importlib_metadata==7.0.1; python_version < '3.10'
importlib_resources==6.1.1; python_version < '3.10'
zipp==3.17.0; python_version < '3.10'
# orjson does not support 32bit Windows, also exclude based on Python-version
orjson==3.9.12; python_version > '3.8'
# For the Windows build
pefile==2024.8.26; sys_platform == 'win32'
pywin32-ctypes==0.2.3; sys_platform == 'win32'
pefile==2023.2.7; sys_platform == 'win32'
pywin32-ctypes==0.2.2; sys_platform == 'win32'
# For the macOS build
dmgbuild==1.6.6; sys_platform == 'darwin'
mac-alias==2.2.3; sys_platform == 'darwin'
macholib==1.16.4; sys_platform == 'darwin'
ds-store==1.3.2; sys_platform == 'darwin'
PyNaCl==1.6.1; sys_platform == 'darwin'
dmgbuild==1.6.1; sys_platform == 'darwin'
mac-alias==2.2.2; sys_platform == 'darwin'
macholib==1.16.3; sys_platform == 'darwin'
ds-store==1.3.1; sys_platform == 'darwin'
PyNaCl==1.5.0; sys_platform == 'darwin'

View File

@@ -29,7 +29,6 @@ Unicode true
!include "nsProcess.nsh"
!include "x64.nsh"
!include "servicelib.nsh"
!include "StdUtils.nsh"
;------------------------------------------------------------------
;
@@ -43,47 +42,13 @@ Unicode true
RMDir /r "${idir}"
!macroend
!define RemovePrevShortcuts "!insertmacro RemovePrevShortcuts"
!macro RemovePrevShortcuts
; Remove shortcuts, starting with current user ones (from old installs)
SetShellVarContext current
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - SafeMode.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - Documentation.url"
RMDir "$SMPROGRAMS\$MUI_TEMP"
Delete "$SMPROGRAMS\Startup\SABnzbd.lnk"
Delete "$DESKTOP\SABnzbd.lnk"
SetShellVarContext all
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - SafeMode.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - Documentation.url"
RMDir "$SMPROGRAMS\$MUI_TEMP"
Delete "$SMPROGRAMS\Startup\SABnzbd.lnk"
Delete "$DESKTOP\SABnzbd.lnk"
!macroend
;------------------------------------------------------------------
; Define names of the product
Name "SABnzbd ${SAB_VERSION}"
VIProductVersion "${SAB_VERSIONKEY}"
VIFileVersion "${SAB_VERSIONKEY}"
VIAddVersionKey "Comments" "SABnzbd ${SAB_VERSION}"
VIAddVersionKey "CompanyName" "The SABnzbd-Team"
VIAddVersionKey "FileDescription" "SABnzbd ${SAB_VERSION}"
VIAddVersionKey "FileVersion" "${SAB_VERSION}"
VIAddVersionKey "LegalCopyright" "The SABnzbd-Team"
VIAddVersionKey "ProductName" "SABnzbd ${SAB_VERSION}"
VIAddVersionKey "ProductVersion" "${SAB_VERSION}"
Name "${SAB_PRODUCT}"
OutFile "${SAB_FILE}"
InstallDir "$PROGRAMFILES\SABnzbd"
;------------------------------------------------------------------
; Some default compiler settings (uncomment and change at will):
SetCompress auto ; (can be off or force)
@@ -140,9 +105,9 @@ Unicode true
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
!define MUI_FINISHPAGE_RUN_TEXT $(MsgRunSAB)
; !define MUI_FINISHPAGE_RUN
; !define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
; !define MUI_FINISHPAGE_RUN_TEXT $(MsgRunSAB)
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
!define MUI_FINISHPAGE_SHOWREADME_TEXT $(MsgShowRelNote)
!define MUI_FINISHPAGE_LINK $(MsgSupportUs)
@@ -155,21 +120,12 @@ Unicode true
!insertmacro MUI_UNPAGE_COMPONENTS
!insertmacro MUI_UNPAGE_INSTFILES
;------------------------------------------------------------------
; Run as user-level at end of install
Function PageFinishRun
; Check if SABnzbd service is installed
!insertmacro SERVICE "installed" "SABnzbd" ""
Pop $0 ;response
${If} $0 == true
; Service is installed, start the service
!insertmacro SERVICE "start" "SABnzbd" ""
${Else}
; Service not installed, run executable as user
${StdUtils.ExecShellAsUser} $0 "$INSTDIR\SABnzbd.exe" "" ""
${EndIf}
FunctionEnd
; DOES NOT WORK
; Function PageFinishRun
; !insertmacro UAC_AsUser_ExecShell "" "$INSTDIR\SABnzbd.exe" "" "" ""
; FunctionEnd
;------------------------------------------------------------------
@@ -185,19 +141,18 @@ FunctionEnd
!insertmacro MUI_LANGUAGE "Polish"
!insertmacro MUI_LANGUAGE "Swedish"
!insertmacro MUI_LANGUAGE "Danish"
!insertmacro MUI_LANGUAGE "Italian"
!insertmacro MUI_LANGUAGE "Norwegian"
!insertmacro MUI_LANGUAGE "Romanian"
!insertmacro MUI_LANGUAGE "Spanish"
!insertmacro MUI_LANGUAGE "PortugueseBR"
!insertmacro MUI_LANGUAGE "Serbian"
!insertmacro MUI_LANGUAGE "Turkish"
!insertmacro MUI_LANGUAGE "Hebrew"
!insertmacro MUI_LANGUAGE "Russian"
!insertmacro MUI_LANGUAGE "Czech"
!insertmacro MUI_LANGUAGE "SimpChinese"
;------------------------------------------------------------------
;Reserve Files
;If you are using solid compression, files that are required before
@@ -303,13 +258,15 @@ Function .onInit
${If} ${RunningX64}
StrCpy $INSTDIR "$PROGRAMFILES64\SABnzbd"
${Else}
MessageBox MB_OK|MB_ICONSTOP $(MsgOnly64bit)
MessageBox MB_OK $(MsgOnly64bit)
ExecShell "open" "https://sabnzbd.org/downloads"
Abort
${EndIf}
; Python 3.9 no longer supports Windows 7
${If} ${AtMostWin8}
MessageBox MB_OK|MB_ICONSTOP $(MsgNoWin7)
MessageBox MB_OK $(MsgNoWin7)
ExecShell "open" "https://sabnzbd.org/downloads"
Abort
${EndIf}
@@ -327,9 +284,8 @@ Function .onInit
;------------------------------------------------------------------
; Check what the user has currently set for install options
SetShellVarContext current
IfFileExists "$SMPROGRAMS\Startup\SABnzbd.lnk" 0 endCheckStartupCurrent
IfFileExists "$SMPROGRAMS\Startup\SABnzbd.lnk" 0 endCheckStartup
SectionSetFlags ${startup} 1
endCheckStartupCurrent:
SetShellVarContext all
IfFileExists "$SMPROGRAMS\Startup\SABnzbd.lnk" 0 endCheckStartup
SectionSetFlags ${startup} 1
@@ -337,12 +293,11 @@ Function .onInit
SetShellVarContext current
IfFileExists "$DESKTOP\SABnzbd.lnk" endCheckDesktop 0
; If not present for current user, first check all user folder
SetShellVarContext all
IfFileExists "$DESKTOP\SABnzbd.lnk" endCheckDesktop 0
SectionSetFlags ${desktop} 0 ; SAB is installed but desktop-icon not, so uncheck it
endCheckDesktop:
SectionSetFlags ${desktop} 0 ; SAB is installed but desktop-icon not, so uncheck it
SetShellVarContext all
IfFileExists "$DESKTOP\SABnzbd.lnk" endCheckDesktop 0
SectionSetFlags ${desktop} 0 ; SAB is installed but desktop-icon not, so uncheck it
endCheckDesktop:
Push $1
ReadRegStr $1 HKCR ".nzb" "" ; read current file association
@@ -370,6 +325,14 @@ Function .onInit
FunctionEnd
;------------------------------------------------------------------
; Show the shortcuts at end of install so user can start SABnzbd
; This is instead of us trying to run SAB from the installer
;
Function .onInstSuccess
ExecShell "open" "$SMPROGRAMS\$STARTMENU_FOLDER"
FunctionEnd
;--------------------------------
; begin uninstall settings/section
UninstallText $(MsgUninstall)
@@ -388,12 +351,39 @@ Section "un.$(MsgDelProgram)" Uninstall
DeleteRegKey HKEY_CURRENT_USER "Software\SABnzbd"
${RemovePrev} "$INSTDIR"
${RemovePrevShortcuts}
; Remove firewall entries
liteFirewallW::RemoveRule "$INSTDIR\SABnzbd.exe" "SABnzbd"
liteFirewallW::RemoveRule "$INSTDIR\SABnzbd-console.exe" "SABnzbd-console"
SetShellVarContext all
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - SafeMode.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - Documentation.url"
RMDir "$SMPROGRAMS\$MUI_TEMP"
Delete "$SMPROGRAMS\Startup\SABnzbd.lnk"
Delete "$DESKTOP\SABnzbd.lnk"
SetShellVarContext current
!insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - SafeMode.lnk"
Delete "$SMPROGRAMS\$MUI_TEMP\SABnzbd - Documentation.url"
RMDir "$SMPROGRAMS\$MUI_TEMP"
Delete "$SMPROGRAMS\Startup\SABnzbd.lnk"
Delete "$DESKTOP\SABnzbd.lnk"
${unregisterExtension} ".nzb" "NZB File"
${RefreshShellIcons}
@@ -411,15 +401,13 @@ SectionEnd
;Language strings
LangString MsgShowRelNote ${LANG_ENGLISH} "Show Release Notes"
LangString MsgRunSAB ${LANG_ENGLISH} "Run SABnzbd"
LangString MsgSupportUs ${LANG_ENGLISH} "Support the project, Donate!"
LangString MsgServChange ${LANG_ENGLISH} "The SABnzbd Windows Service changed in SABnzbd 3.0.0. $\nYou will need to reinstall the SABnzbd service. $\n$\nClick `OK` to remove the existing services or `Cancel` to cancel this upgrade."
LangString MsgOnly64bit ${LANG_ENGLISH} "SABnzbd only supports 64-bit Windows."
LangString MsgOnly64bit ${LANG_ENGLISH} "The installer only supports 64-bit Windows, use the standalone version to run on 32-bit Windows."
LangString MsgNoWin7 ${LANG_ENGLISH} "SABnzbd only supports Windows 8.1 and above."
LangString MsgNoWin7 ${LANG_ENGLISH} "The installer only supports Windows 8.1 and above, use the standalone legacy version to run on older Windows version."
LangString MsgShutting ${LANG_ENGLISH} "Shutting down SABnzbd"
@@ -435,6 +423,10 @@ SectionEnd
LangString MsgDelSettings ${LANG_ENGLISH} "Delete Settings"
LangString MsgRemoveOld ${LANG_ENGLISH} "You cannot overwrite an existing installation. $\n$\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade."
LangString MsgRemoveOld2 ${LANG_ENGLISH} "Your settings and data will be preserved."
LangString MsgLangCode ${LANG_ENGLISH} "en"
Function un.onInit

View File

@@ -1,501 +0,0 @@
#################################################################################
# StdUtils plug-in for NSIS
# Copyright (C) 2004-2018 LoRd_MuldeR <MuldeR2@GMX.de>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# http://www.gnu.org/licenses/lgpl-2.1.txt
#################################################################################
# DEVELOPER NOTES:
# - Please see "https://github.com/lordmulder/stdutils/" for news and updates!
# - Please see "Docs\StdUtils\StdUtils.html" for detailed function descriptions!
# - Please see "Examples\StdUtils\StdUtilsTest.nsi" for usage examples!
#################################################################################
# FUNCTION DECLARTIONS
#################################################################################
!ifndef ___STDUTILS__NSH___
!define ___STDUTILS__NSH___
!define StdUtils.Time '!insertmacro _StdU_Time' #time(), as in C standard library
!define StdUtils.GetMinutes '!insertmacro _StdU_GetMinutes' #GetSystemTimeAsFileTime(), returns the number of minutes
!define StdUtils.GetHours '!insertmacro _StdU_GetHours' #GetSystemTimeAsFileTime(), returns the number of hours
!define StdUtils.GetDays '!insertmacro _StdU_GetDays' #GetSystemTimeAsFileTime(), returns the number of days
!define StdUtils.Rand '!insertmacro _StdU_Rand' #rand(), as in C standard library
!define StdUtils.RandMax '!insertmacro _StdU_RandMax' #rand(), as in C standard library, with maximum value
!define StdUtils.RandMinMax '!insertmacro _StdU_RandMinMax' #rand(), as in C standard library, with minimum/maximum value
!define StdUtils.RandList '!insertmacro _StdU_RandList' #rand(), as in C standard library, with list support
!define StdUtils.RandBytes '!insertmacro _StdU_RandBytes' #Generates random bytes, returned as Base64-encoded string
!define StdUtils.FormatStr '!insertmacro _StdU_FormatStr' #sprintf(), as in C standard library, one '%d' placeholder
!define StdUtils.FormatStr2 '!insertmacro _StdU_FormatStr2' #sprintf(), as in C standard library, two '%d' placeholders
!define StdUtils.FormatStr3 '!insertmacro _StdU_FormatStr3' #sprintf(), as in C standard library, three '%d' placeholders
!define StdUtils.ScanStr '!insertmacro _StdU_ScanStr' #sscanf(), as in C standard library, one '%d' placeholder
!define StdUtils.ScanStr2 '!insertmacro _StdU_ScanStr2' #sscanf(), as in C standard library, two '%d' placeholders
!define StdUtils.ScanStr3 '!insertmacro _StdU_ScanStr3' #sscanf(), as in C standard library, three '%d' placeholders
!define StdUtils.TrimStr '!insertmacro _StdU_TrimStr' #Remove whitspaces from string, left and right
!define StdUtils.TrimStrLeft '!insertmacro _StdU_TrimStrLeft' #Remove whitspaces from string, left side only
!define StdUtils.TrimStrRight '!insertmacro _StdU_TrimStrRight' #Remove whitspaces from string, right side only
!define StdUtils.RevStr '!insertmacro _StdU_RevStr' #Reverse a string, e.g. "reverse me" <-> "em esrever"
!define StdUtils.ValidFileName '!insertmacro _StdU_ValidFileName' #Test whether string is a valid file name - no paths allowed
!define StdUtils.ValidPathSpec '!insertmacro _StdU_ValidPathSpec' #Test whether string is a valid full(!) path specification
!define StdUtils.ValidDomainName '!insertmacro _StdU_ValidDomain' #Test whether string is a valid host name or domain name
!define StdUtils.StrToUtf8 '!insertmacro _StdU_StrToUtf8' #Convert string from Unicode (UTF-16) or ANSI to UTF-8 bytes
!define StdUtils.StrFromUtf8 '!insertmacro _StdU_StrFromUtf8' #Convert string from UTF-8 bytes to Unicode (UTF-16) or ANSI
!define StdUtils.SHFileMove '!insertmacro _StdU_SHFileMove' #SHFileOperation(), using the FO_MOVE operation
!define StdUtils.SHFileCopy '!insertmacro _StdU_SHFileCopy' #SHFileOperation(), using the FO_COPY operation
!define StdUtils.AppendToFile '!insertmacro _StdU_AppendToFile' #Append contents of an existing file to another file
!define StdUtils.ExecShellAsUser '!insertmacro _StdU_ExecShlUser' #ShellExecute() as NON-elevated user from elevated installer
!define StdUtils.InvokeShellVerb '!insertmacro _StdU_InvkeShlVrb' #Invokes a "shell verb", e.g. for pinning items to the taskbar
!define StdUtils.ExecShellWaitEx '!insertmacro _StdU_ExecShlWaitEx' #ShellExecuteEx(), returns the handle of the new process
!define StdUtils.WaitForProcEx '!insertmacro _StdU_WaitForProcEx' #WaitForSingleObject(), e.g. to wait for a running process
!define StdUtils.GetParameter '!insertmacro _StdU_GetParameter' #Get the value of a specific command-line option
!define StdUtils.TestParameter '!insertmacro _StdU_TestParameter' #Test whether a specific command-line option has been set
!define StdUtils.ParameterCnt '!insertmacro _StdU_ParameterCnt' #Get number of command-line tokens, similar to argc in main()
!define StdUtils.ParameterStr '!insertmacro _StdU_ParameterStr' #Get the n-th command-line token, similar to argv[i] in main()
!define StdUtils.GetAllParameters '!insertmacro _StdU_GetAllParams' #Get complete command-line, but without executable name
!define StdUtils.GetRealOSVersion '!insertmacro _StdU_GetRealOSVer' #Get the *real* Windows version number, even on Windows 8.1+
!define StdUtils.GetRealOSBuildNo '!insertmacro _StdU_GetRealOSBld' #Get the *real* Windows build number, even on Windows 8.1+
!define StdUtils.GetRealOSName '!insertmacro _StdU_GetRealOSStr' #Get the *real* Windows version, as a "friendly" name
!define StdUtils.GetOSEdition '!insertmacro _StdU_GetOSEdition' #Get the Windows edition, i.e. "workstation" or "server"
!define StdUtils.GetOSReleaseId '!insertmacro _StdU_GetOSRelIdNo' #Get the Windows release identifier (on Windows 10)
!define StdUtils.GetOSReleaseName '!insertmacro _StdU_GetOSRelIdStr' #Get the Windows release (on Windows 10), as a "friendly" name
!define StdUtils.VerifyOSVersion '!insertmacro _StdU_VrfyRealOSVer' #Compare *real* operating system to an expected version number
!define StdUtils.VerifyOSBuildNo '!insertmacro _StdU_VrfyRealOSBld' #Compare *real* operating system to an expected build number
!define StdUtils.HashText '!insertmacro _StdU_HashText' #Compute hash from text string (CRC32, MD5, SHA1/2/3, BLAKE2)
!define StdUtils.HashFile '!insertmacro _StdU_HashFile' #Compute hash from file (CRC32, MD5, SHA1/2/3, BLAKE2)
!define StdUtils.NormalizePath '!insertmacro _StdU_NormalizePath' #Simplifies the path to produce a direct, well-formed path
!define StdUtils.GetParentPath '!insertmacro _StdU_GetParentPath' #Get parent path by removing the last component from the path
!define StdUtils.SplitPath '!insertmacro _StdU_SplitPath' #Split the components of the given path
!define StdUtils.GetDrivePart '!insertmacro _StdU_GetDrivePart' #Get drive component of path
!define StdUtils.GetDirectoryPart '!insertmacro _StdU_GetDirPart' #Get directory component of path
!define StdUtils.GetFileNamePart '!insertmacro _StdU_GetFNamePart' #Get file name component of path
!define StdUtils.GetExtensionPart '!insertmacro _StdU_GetExtnPart' #Get file extension component of path
!define StdUtils.TimerCreate '!insertmacro _StdU_TimerCreate' #Create a new event-timer that will be triggered periodically
!define StdUtils.TimerDestroy '!insertmacro _StdU_TimerDestroy' #Destroy a running timer created with TimerCreate()
!define StdUtils.ProtectStr '!insertmacro _StdU_PrtctStr' #Protect a given String using Windows' DPAPI
!define StdUtils.UnprotectStr '!insertmacro _StdU_UnprtctStr' #Unprotect a string that was protected via ProtectStr()
!define StdUtils.GetLibVersion '!insertmacro _StdU_GetLibVersion' #Get the current StdUtils library version (for debugging)
!define StdUtils.SetVerbose '!insertmacro _StdU_SetVerbose' #Enable or disable "verbose" mode (for debugging)
#################################################################################
# MACRO DEFINITIONS
#################################################################################
!macro _StdU_Time out
StdUtils::Time /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetMinutes out
StdUtils::GetMinutes /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetHours out
StdUtils::GetHours /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetDays out
StdUtils::GetDays /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_Rand out
StdUtils::Rand /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandMax out max
push ${max}
StdUtils::RandMax /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandMinMax out min max
push ${min}
push ${max}
StdUtils::RandMinMax /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_RandList count max
push ${max}
push ${count}
StdUtils::RandList /NOUNLOAD
!macroend
!macro _StdU_RandBytes out count
push ${count}
StdUtils::RandBytes /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr out format val
push `${format}`
push ${val}
StdUtils::FormatStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr2 out format val1 val2
push `${format}`
push ${val1}
push ${val2}
StdUtils::FormatStr2 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_FormatStr3 out format val1 val2 val3
push `${format}`
push ${val1}
push ${val2}
push ${val3}
StdUtils::FormatStr3 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ScanStr out format input default
push `${format}`
push `${input}`
push ${default}
StdUtils::ScanStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ScanStr2 out1 out2 format input default1 default2
push `${format}`
push `${input}`
push ${default1}
push ${default2}
StdUtils::ScanStr2 /NOUNLOAD
pop ${out1}
pop ${out2}
!macroend
!macro _StdU_ScanStr3 out1 out2 out3 format input default1 default2 default3
push `${format}`
push `${input}`
push ${default1}
push ${default2}
push ${default3}
StdUtils::ScanStr3 /NOUNLOAD
pop ${out1}
pop ${out2}
pop ${out3}
!macroend
!macro _StdU_TrimStr var
push ${var}
StdUtils::TrimStr /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_TrimStrLeft var
push ${var}
StdUtils::TrimStrLeft /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_TrimStrRight var
push ${var}
StdUtils::TrimStrRight /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_RevStr var
push ${var}
StdUtils::RevStr /NOUNLOAD
pop ${var}
!macroend
!macro _StdU_ValidFileName out test
push `${test}`
StdUtils::ValidFileName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ValidPathSpec out test
push `${test}`
StdUtils::ValidPathSpec /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ValidDomain out test
push `${test}`
StdUtils::ValidDomainName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_StrToUtf8 out str
push `${str}`
StdUtils::StrToUtf8 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_StrFromUtf8 out trnc str
push ${trnc}
push `${str}`
StdUtils::StrFromUtf8 /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SHFileMove out from to hwnd
push `${from}`
push `${to}`
push ${hwnd}
StdUtils::SHFileMove /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SHFileCopy out from to hwnd
push `${from}`
push `${to}`
push ${hwnd}
StdUtils::SHFileCopy /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_AppendToFile out from dest offset maxlen
push `${from}`
push `${dest}`
push ${offset}
push ${maxlen}
StdUtils::AppendToFile /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ExecShlUser out file verb args
push `${file}`
push `${verb}`
push `${args}`
StdUtils::ExecShellAsUser /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_InvkeShlVrb out path file verb_id
push "${path}"
push "${file}"
push ${verb_id}
StdUtils::InvokeShellVerb /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ExecShlWaitEx out_res out_val file verb args
push `${file}`
push `${verb}`
push `${args}`
StdUtils::ExecShellWaitEx /NOUNLOAD
pop ${out_res}
pop ${out_val}
!macroend
!macro _StdU_WaitForProcEx out handle
push `${handle}`
StdUtils::WaitForProcEx /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetParameter out name default
push `${name}`
push `${default}`
StdUtils::GetParameter /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TestParameter out name
push `${name}`
StdUtils::TestParameter /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ParameterCnt out
StdUtils::ParameterCnt /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_ParameterStr out index
push ${index}
StdUtils::ParameterStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetAllParams out truncate
push `${truncate}`
StdUtils::GetAllParameters /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetRealOSVer out_major out_minor out_spack
StdUtils::GetRealOsVersion /NOUNLOAD
pop ${out_major}
pop ${out_minor}
pop ${out_spack}
!macroend
!macro _StdU_GetRealOSBld out
StdUtils::GetRealOsBuildNo /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetRealOSStr out
StdUtils::GetRealOsName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_VrfyRealOSVer out major minor spack
push `${major}`
push `${minor}`
push `${spack}`
StdUtils::VerifyRealOsVersion /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_VrfyRealOSBld out build
push `${build}`
StdUtils::VerifyRealOsBuildNo /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSEdition out
StdUtils::GetOsEdition /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSRelIdNo out
StdUtils::GetOsReleaseId /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetOSRelIdStr out
StdUtils::GetOsReleaseName /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_HashText out type text
push `${type}`
push `${text}`
StdUtils::HashText /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_HashFile out type file
push `${type}`
push `${file}`
StdUtils::HashFile /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_NormalizePath out path
push `${path}`
StdUtils::NormalizePath /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetParentPath out path
push `${path}`
StdUtils::GetParentPath /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_SplitPath out_drive out_dir out_fname out_ext path
push `${path}`
StdUtils::SplitPath /NOUNLOAD
pop ${out_drive}
pop ${out_dir}
pop ${out_fname}
pop ${out_ext}
!macroend
!macro _StdU_GetDrivePart out path
push `${path}`
StdUtils::GetDrivePart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetDirPart out path
push `${path}`
StdUtils::GetDirectoryPart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetFNamePart out path
push `${path}`
StdUtils::GetFileNamePart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetExtnPart out path
push `${path}`
StdUtils::GetExtensionPart /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TimerCreate out callback interval
GetFunctionAddress ${out} ${callback}
push ${out}
push ${interval}
StdUtils::TimerCreate /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_TimerDestroy out timer_id
push ${timer_id}
StdUtils::TimerDestroy /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_PrtctStr out dpsc salt text
push `${dpsc}`
push `${salt}`
push `${text}`
StdUtils::ProtectStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_UnprtctStr out trnc salt data
push `${trnc}`
push `${salt}`
push `${data}`
StdUtils::UnprotectStr /NOUNLOAD
pop ${out}
!macroend
!macro _StdU_GetLibVersion out_ver out_tst
StdUtils::GetLibVersion /NOUNLOAD
pop ${out_ver}
pop ${out_tst}
!macroend
!macro _StdU_SetVerbose enable
Push ${enable}
StdUtils::SetVerboseMode /NOUNLOAD
!macroend
#################################################################################
# MAGIC NUMBERS
#################################################################################
!define StdUtils.Const.ShellVerb.PinToTaskbar 0
!define StdUtils.Const.ShellVerb.UnpinFromTaskbar 1
!define StdUtils.Const.ShellVerb.PinToStart 2
!define StdUtils.Const.ShellVerb.UnpinFromStart 3
!endif # !___STDUTILS__NSH___

View File

Binary file not shown.

View File

@@ -1,7 +1,7 @@
##
## Bad URL Fetch Email template for SABnzbd
## This a Cheetah template
## Documentation: https://sabnzbd.org/wiki/extra/email-templates
## Documentation: http://sabnzbd.wikidot.com/email-templates
##
## Newlines and whitespace are significant!
##

View File

@@ -1,7 +1,7 @@
##
## Default Email template for SABnzbd
## This a Cheetah template
## Documentation: https://sabnzbd.org/wiki/extra/email-templates
## Documentation: http://sabnzbd.wikidot.com/email-templates
##
## Newlines and whitespace are significant!
##

View File

@@ -1,7 +1,7 @@
##
## RSS Email template for SABnzbd
## This a Cheetah template
## Documentation: https://sabnzbd.org/wiki/extra/email-templates
## Documentation: http://sabnzbd.wikidot.com/email-templates
##
## Newlines and whitespace are significant!
##

View File

@@ -12,13 +12,13 @@
<div class="modal-body">
</div>
<div class="modal-footer">
<!--#if not $windows#-->
<div class="checkbox">
<label>
<input type="checkbox" id="show_hidden_folders"> <span>$T('hiddenFolders')</span>
</label>
</div>
<!--#end if#-->
<button type="button" class="btn btn-danger" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> $T('cancel')</button>
<button type="button" class="btn btn-default" id="filebrowser_modal_accept"><span class="glyphicon glyphicon-ok"></span> $T('rss-accept')</button>
</div>

View File

@@ -70,7 +70,27 @@
<script type="text/javascript" src="${root}staticcfg/js/script.js?v=$version"></script>
<script type="text/javascript">
// Set default functions for the autocomplete everywhere
jQuery.extend(jQuery.fn.typeahead.defaults, {
source: function (query, process) {
// If there's no separator, it must be a relative path
if(query.split(folderSeperator).length < 2 && this.\$element.data('initialdir')) {
query = this.\$element.data('initialdir') + folderSeperator + query;
}
// Get info from the API
return jQuery.get(folderBrowseUrl + '&compact=1&term=' + query, function (data) {
return process(data);
});
},
updater: function(item) {
// Is it a relative path?
if(item.indexOf(this.\$element.data('initialdir')) === 0) {
// Remove start
return item.replace(this.\$element.data('initialdir')+folderSeperator, '');
}
// Full path
return item
}
})
// to top right away
if(window.location.hash) {
@@ -92,7 +112,7 @@
<span class="icon-bar"></span>
</button>
<a class="navbar-logo navbar-logo-small" href="${root}" title="$T('Home')" data-placement="bottom">
<a class="navbar-logo navbar-logo-small" href="${root}" title="$T('Home')">
#include $webdir + "/staticcfg/images/logo-small.svg"#
</a>
</div>

View File

@@ -96,7 +96,7 @@
<div class="colmask">
<div class="padding">
<h5 class="copyright">Copyright &copy; 2007-2025 by The SABnzbd-Team (<a href="https://sabnzbd.org/" target="_blank">sabnzbd.org</a>)</h5>
<h5 class="copyright">Copyright &copy; 2007-2024 by The SABnzbd-Team (<a href="https://sabnzbd.org/" target="_blank">sabnzbd.org</a>)</h5>
<p class="copyright"><small>$T('yourRights')</small></p>
</div>

View File

@@ -77,7 +77,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="password_file">$T('opt-password_file')</label>
<input type="text" name="password_file" id="password_file" value="$password_file" class="fileBrowserField" data-initialdir="$my_home" data-files="1" />
<input type="text" name="password_file" id="password_file" value="$password_file" />
<span class="desc">$T('explain-password_file')</span>
</div>
<div class="field-pair">
@@ -133,7 +133,7 @@
<script type="text/javascript">
jQuery(document).ready(function() {
// Add autocomplete and file-browser
jQuery('.col1 input[name$="_dir"], #password_file').typeahead().fileBrowser();
jQuery('.col1 input[name$="_dir"]').typeahead().fileBrowser();
jQuery('#purge_log_files').click(function () {
if ( confirm("$T('confirm')") ) {

View File

@@ -26,7 +26,7 @@
</div>
<div class="field-pair">
<label class="config" for="port">$T('opt-port')</label>
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" min="0" max="65535" />
<input type="number" name="port" id="port" value="$port" size="8" data-original="$port" />
<span class="desc">$T('explain-port')</span>
</div>
<div class="field-pair">
@@ -69,12 +69,12 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="https_port">$T('opt-https_port')</label>
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" min="0" max="65535" />
<input type="number" name="https_port" id="https_port" value="$https_port" size="8" data-original="$https_port" />
<span class="desc">$T('explain-https_port')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="https_cert">$T('opt-https_cert')</label>
<input type="text" name="https_cert" id="https_cert" value="$https_cert" class="fileBrowserField" data-initialdir="$my_lcldata" data-files="1" />
<input type="text" name="https_cert" id="https_cert" value="$https_cert" />
<button class="btn btn-default generate_cert" title="$T('explain-new-cert')">
<span class="glyphicon glyphicon-repeat"></span>
</button>
@@ -82,7 +82,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="https_key">$T('opt-https_key')</label>
<input type="text" name="https_key" id="https_key" value="$https_key" class="fileBrowserField" data-initialdir="$my_lcldata" data-files="1" />
<input type="text" name="https_key" id="https_key" value="$https_key" />
<button class="btn btn-default generate_cert" title="$T('explain-new-cert')">
<span class="glyphicon glyphicon-repeat"></span>
</button>
@@ -90,7 +90,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="https_chain">$T('opt-https_chain')</label>
<input type="text" name="https_chain" id="https_chain" value="$https_chain" class="fileBrowserField" data-initialdir="$my_lcldata" data-files="1" />
<input type="text" name="https_chain" id="https_chain" value="$https_chain" />
<span class="desc">$T('explain-https_chain')</span>
</div>
<div class="field-pair">
@@ -136,14 +136,14 @@
</div>
<div class="field-pair">
<label class="config" for="apikey_display">$T('opt-apikey')</label>
<input type="text" id="apikey_display" value="$apikey" readonly />
<input type="text" id="apikey_display" class="fileBrowserField" value="$apikey" readonly />
<button class="btn btn-default show_qrcode" title="$T('explain-qr-code')" rel="$apikey" ><span class="glyphicon glyphicon-qrcode"></span></button>
<button class="btn btn-default generate_key" id="generate_new_apikey" title="$T('button-apikey')"><span class="glyphicon glyphicon-repeat"></span></button>
<span class="desc">$T('explain-apikey')</span>
</div>
<div class="field-pair">
<label class="config" for="nzbkey">$T('opt-nzbkey')</label>
<input type="text" id="nzbkey" value="$nzb_key" readonly />
<input type="text" id="nzbkey" class="fileBrowserField" value="$nzb_key" readonly />
<button class="btn btn-default show_qrcode" title="$T('explain-qr-code')" rel="$nzb_key" ><span class="glyphicon glyphicon-qrcode"></span></button>
<button class="btn btn-default generate_key" id="generate_new_nzbkey" title="$T('button-apikey')"><span class="glyphicon glyphicon-repeat"></span></button>
<span class="desc">$T('explain-nzbkey')</span>
@@ -341,9 +341,6 @@ jQuery(document).ready(function(){
}
});
// Add autocomplete and file-browser
jQuery('.fileBrowserField').typeahead().fileBrowser();
jQuery('.show_qrcode').click(function (e) {
// Show in modal
jQuery('#modal_qr .modal-dialog').width(330)

View File

@@ -2,21 +2,19 @@
<!--#set global $help_uri = $confighelpuri + "notifications"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#import apprise#-->
<!--#def show_notify_checkboxes($section_label)#-->
<!--#for $type in $notify_types#-->
<div class="field-pair">
<label class="config wide" for="${section_label}_prio_$type">
$T($notify_types[$type]).replace('/', ' / ')
</label>
<input type="checkbox" name="${section_label}_prio_$type" id="${section_label}_prio_$type" value="1" <!--#if $getVar($section_label + '_prio_' + $type) then 'checked="checked"' else ""#--> />
<input type="checkbox" name="${section_label}_prio_$type" id="${section_label}_prio_$type" value="1" <!--#if int($getVar($section_label + '_prio_' + $type)) > 0 then 'checked="checked"' else ""#--> />
</div>
<!--#end for#-->
<!--#end def#-->
<!--#def show_cat_box($section_label)#-->
<div class="col2-cats" <!--#if $getVar($section_label + '_enable') then '' else 'style="display:none"'#-->>
<div class="col2-cats" <!--#if int($getVar($section_label + '_enable')) > 0 then '' else 'style="display:none"'#-->>
<hr>
<b>$T('affectedCat')</b><br/>
<select name="${section_label}_cats" multiple="multiple" class="multiple_cats" size="$len($categories)">
@@ -58,12 +56,12 @@
</div>
<div class="field-pair">
<label class="config" for="email_full">$T('opt-email_full')</label>
<input type="checkbox" name="email_full" id="email_full" value="1" <!--#if $email_full then 'checked="checked"' else ""#--> />
<input type="checkbox" name="email_full" id="email_full" value="1" <!--#if int($email_full) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-email_full')</span>
</div>
<div class="field-pair">
<label class="config" for="email_rss">$T('opt-email_rss')</label>
<input type="checkbox" name="email_rss" id="email_rss" value="1" <!--#if $email_rss then 'checked="checked"' else ""#--> />
<input type="checkbox" name="email_rss" id="email_rss" value="1" <!--#if int($email_rss) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-email_rss')</span>
</div>
<div class="field-pair">
@@ -107,12 +105,12 @@
<h3>$T('section-NC')</h3>
<table>
<tr>
<td><input type="checkbox" name="ncenter_enable" id="ncenter_enable" value="1" <!--#if $ncenter_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="ncenter_enable" id="ncenter_enable" value="1" <!--#if int($ncenter_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="ncenter_enable"> $T('opt-ncenter_enable')</label></td>
</tr>
</table>
</div>
<div class="col1" <!--#if $ncenter_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($ncenter_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('ncenter')
<div class="field-pair no-field-pair-bg">
@@ -132,13 +130,13 @@
<h3>$T('section-AC')</h3>
<table>
<tr>
<td><input type="checkbox" name="acenter_enable" id="acenter_enable" value="1" <!--#if $acenter_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="acenter_enable" id="acenter_enable" value="1" <!--#if int($acenter_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="acenter_enable"> $T('opt-acenter_enable')</label></td>
</tr>
</table>
$show_cat_box('acenter')
</div>
<div class="col1" <!--#if $acenter_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($acenter_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('acenter')
<div class="field-pair no-field-pair-bg">
@@ -158,13 +156,13 @@
<h3>$T('section-OSD') <a href="$help_uri#toc4" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<table>
<tr>
<td><input type="checkbox" name="ntfosd_enable" id="ntfosd_enable" value="1" <!--#if $ntfosd_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="ntfosd_enable" id="ntfosd_enable" value="1" <!--#if int($ntfosd_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="ntfosd_enable"> $T('opt-ntfosd_enable')</label></td>
</tr>
</table>
$show_cat_box('ntfosd')
</div>
<div class="col1" <!--#if $ntfosd_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($ntfosd_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('ntfosd')
<div class="field-pair no-field-pair-bg">
@@ -178,64 +176,19 @@
</div>
</div>
<!--#end if#-->
<div class="section" id="apprise">
<div class="col2">
<h3>Apprise <a href="$help_uri#apprise" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<table>
<tr>
<td><input type="checkbox" name="apprise_enable" id="apprise_enable" value="1" <!--#if $apprise_enable then 'checked="checked"' else ""#--> /></td>
<td><label for="apprise_enable"> $T('opt-apprise_enable')</label></td>
</tr>
</table>
<p>$T('explain-apprise_enable')</p>
<p>$T('version'): ${apprise.__version__}</p>
$show_cat_box('apprise')
</div>
<div class="col1" <!--#if $apprise_enable then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="apprise_urls">$T('opt-apprise_urls')</label>
<input type="text" name="apprise_urls" id="apprise_urls" value="$apprise_urls" />
<span class="desc">$T('explain-apprise_urls')</span>
</div>
<div class="field-pair">
<span class="desc">$T('explain-apprise_extra_urls')</span>
</div>
<!--#set $section_label = 'apprise'#-->
<!--#for $type in $notify_types#-->
<div class="field-pair">
<label class="config" for="${section_label}_target_${type}">
$T($notify_types[$type]).replace('/', ' / ')
</label>
<input type="checkbox" name="${section_label}_target_${type}_enable" id="${section_label}_target_${type}_enable" value="1" <!--#if $getVar($section_label + '_target_' + $type + '_enable') then 'checked="checked"' else ""#--> />
<input type="text" name="${section_label}_target_${type}" id="${section_label}_target_${type}" value="$getVar($section_label + '_target_' + $type)" placeholder="$T('opt-apprise_urls')" />
</div>
<!--#end for#-->
<div class="field-pair no-field-pair-bg">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default" type="button" id="test_apprise"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
</div>
<div class="section" id="nscript">
<div class="col2">
<h3>$T('section-NScript') <a href="$help_uri#nscript" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
<table>
<tr>
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if $nscript_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
</tr>
</table>
<em>$T('explain-nscript_enable')</em><br><a href="$help_uri#nscript" target="_blank">$T('readwiki')</a>
$show_cat_box('nscript')
</div>
<div class="col1" <!--#if $nscript_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
@@ -267,14 +220,14 @@
<h3>$T('section-Prowl')</h3>
<table>
<tr>
<td><input type="checkbox" name="prowl_enable" id="prowl_enable" value="1" <!--#if $prowl_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="prowl_enable" id="prowl_enable" value="1" <!--#if int($prowl_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="prowl_enable"> $T('opt-prowl_enable')</label></td>
</tr>
</table>
<em>$T('explain-prowl_enable')</em>
$show_cat_box('prowl')
</div>
<div class="col1" <!--#if $prowl_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($prowl_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="prowl_apikey">$T('opt-prowl_apikey')</label>
@@ -288,12 +241,12 @@
$T($notify_types[$type]).replace('/', ' / ')
</label>
<select name="${section_label}_prio_$type" id="${section_label}_prio_$type">
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == -3 then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == -1 then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == 1 then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == "-3" then 'selected="selected"' else ""#--> >$T('prowl-off')</option>
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == "-2" then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == "-1" then 'selected="selected"' else ""#--> >$T('prowl-moderate')</option>
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == "0" then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == "1" then 'selected="selected"' else ""#--> >$T('prowl-high')</option>
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == "2" then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
</select>
</div>
<!--#end for#-->
@@ -313,14 +266,14 @@
<h3>$T('section-Pushover')</h3>
<table>
<tr>
<td><input type="checkbox" name="pushover_enable" id="pushover_enable" value="1" <!--#if $pushover_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="pushover_enable" id="pushover_enable" value="1" <!--#if int($pushover_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="pushover_enable"> $T('opt-pushover_enable')</label></td>
</tr>
</table>
<em>$T('explain-pushover_enable')</em>
$show_cat_box('pushover')
</div>
<div class="col1" <!--#if $pushover_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($pushover_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="pushover_token">$T('opt-pushover_token')</label>
@@ -354,12 +307,12 @@
$T($notify_types[$type]).replace('/', ' / ')
</label>
<select name="${section_label}_prio_$type" id="${section_label}_prio_$type">
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == -3 then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == -2 then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == -1 then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == 0 then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == 1 then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == 2 then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
<option value="-3" <!--#if $getVar($section_label + '_prio_' + $type) == "-3" then 'selected="selected"' else ""#--> >$T('pushover-off')</option>
<option value="-2" <!--#if $getVar($section_label + '_prio_' + $type) == "-2" then 'selected="selected"' else ""#--> >$T('prowl-very-low')</option>
<option value="-1" <!--#if $getVar($section_label + '_prio_' + $type) == "-1" then 'selected="selected"' else ""#--> >$T('pushover-low')</option>
<option value="0" <!--#if $getVar($section_label + '_prio_' + $type) == "0" then 'selected="selected"' else ""#--> >$T('prowl-normal')</option>
<option value="1" <!--#if $getVar($section_label + '_prio_' + $type) == "1" then 'selected="selected"' else ""#--> >$T('pushover-high')</option>
<option value="2" <!--#if $getVar($section_label + '_prio_' + $type) == "2" then 'selected="selected"' else ""#--> >$T('prowl-emergency')</option>
</select>
</div>
<!--#end for#-->
@@ -378,14 +331,14 @@
<h3>$T('section-Pushbullet')</h3>
<table>
<tr>
<td><input type="checkbox" name="pushbullet_enable" id="pushbullet_enable" value="1" <!--#if $pushbullet_enable then 'checked="checked"' else ""#--> /></td>
<td><input type="checkbox" name="pushbullet_enable" id="pushbullet_enable" value="1" <!--#if int($pushbullet_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="pushbullet_enable"> $T('opt-pushbullet_enable')</label></td>
</tr>
</table>
<em>$T('explain-pushbullet_enable')</em>
$show_cat_box('pushbullet')
</div>
<div class="col1" <!--#if $pushbullet_enable then '' else 'style="display:none"'#-->>
<div class="col1" <!--#if int($pushbullet_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="pushbullet_apikey">$T('opt-pushbullet_apikey')</label>
@@ -473,7 +426,7 @@ jQuery(document).ready(function(){
}
})
}
jQuery('#test_email, #test_notif, #test_windows, #test_apprise, #test_pushbullet, #test_pushover, #test_prowl, #test_osd, #test_nscript').click(function () {
jQuery('#test_email, #test_notif, #test_windows, #test_pushbullet, #test_pushover, #test_prowl, #test_osd, #test_nscript').click(function () {
testNotification(this)
})
});

View File

@@ -28,7 +28,7 @@
</label>
<div class="advanced-buttonSeperator"></div>
<div class="chart-selector-container" title="$T('selectedDates')" data-placement="bottom">
<div class="chart-selector-container" title="$T('selectedDates')">
<span class="glyphicon glyphicon-signal"></span>
<!--#set today = datetime.date.today()#-->
<input type="date" name="chart-start" id="chart-start" value="<!--#echo (today-datetime.timedelta(days=30)).strftime('%Y-%m-%d')#-->"> -
@@ -59,7 +59,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="port">$T('srv-port')</label>
<input type="number" name="port" id="port" size="8" value="563" min="0" max="65535" />
<input type="number" name="port" id="port" size="8" value="563" min="0" />
</div>
<div class="field-pair">
<label class="config" for="ssl">$T('srv-ssl')</label>
@@ -94,12 +94,11 @@
<div class="field-pair advanced-settings">
<label class="config" for="ssl_verify">$T('opt-ssl_verify')</label>
<select name="ssl_verify" id="ssl_verify">
<option value="3" selected>$T('ssl_verify-strict')</option>
<option value="2">$T('ssl_verify-medium')</option>
<option value="1">$T('ssl_verify-minimal')</option>
<option value="2" selected>$T('ssl_verify-strict')</option>
<option value="1">$T('ssl_verify-normal')</option>
<option value="0">$T('ssl_verify-disabled')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('-', '<br/>-')</span>
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="ssl_ciphers">$T('opt-ssl_ciphers')</label>
@@ -117,12 +116,6 @@
<input type="checkbox" name="optional" id="optional" value="1" />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="pipelining_requests">$T('srv-pipelining_requests')</label>
<input type="number" name="pipelining_requests" id="pipelining_requests" min="1" max="20" value="1" />
<span class="desc">$T('explain-pipelining_requests')<br>$T('readwiki')
<a href="https://sabnzbd.org/wiki/advanced/nntp-pipelining" target="_blank">https://sabnzbd.org/wiki/advanced/nntp-pipelining</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="expire_date">$T('srv-expire_date')</label>
<input type="date" name="expire_date" id="expire_date" />
@@ -138,7 +131,7 @@
<textarea name="notes" id="notes" rows="3" cols="50"></textarea>
</div>
<div class="field-pair no-field-pair-bg">
<button class="btn btn-default addNewServer" disabled data-toggle="tooltip" data-placement="top" title="$T('wizard-test-server-required')"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<button class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> $T('button-addServer')</button>
<button class="btn btn-default testServer" type="button"><span class="glyphicon glyphicon-sort"></span> $T('button-testServer')</button>
</div>
<div class="field-pair result-box">
@@ -149,7 +142,7 @@
</div>
</div>
<!--#set $prio_colors = ["#59cc33", "#26a69a", "#3366cc", "#7f33cc", "#cc33a6", "#f39c12", "#cc3333", "#8d6e63"] #-->
<!--#set $prio_colors = ["#59cc33", "#3366cc","#7f33cc", "#cc33a6", "#cc3333"] #-->
<!--#set $cur_prio_color = -1 #-->
<!--#set $last_prio = -1 #-->
<!--#for $cur, $server in enumerate($servers) #-->
@@ -178,9 +171,6 @@
</table>
<button type="button" class="btn btn-default showserver"><span class="glyphicon glyphicon-pencil"></span> $T('showDetails')</button>
<button type="button" class="btn btn-default clrServer"><span class="glyphicon glyphicon-remove"></span> $T('button-clrServer')</button>
<!--#if $server['notes'] #-->
<p>$server['notes'].replace('\n', '<br>')</p>
<!--#end if#-->
</div>
<div class="col1" style="display:none;">
<input type="hidden" name="enable" id="enable$cur" value="$int($server['enable'])" />
@@ -195,7 +185,7 @@
</div>
<div class="field-pair advanced-settings">
<label class="config" for="port$cur">$T('srv-port')</label>
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" min="0" max="65535" required />
<input type="number" name="port" id="port$cur" value="$server['port']" size="8" min="0" required />
</div>
<div class="field-pair">
<label class="config" for="ssl$cur">$T('srv-ssl')</label>
@@ -231,12 +221,11 @@
<div class="field-pair advanced-settings">
<label class="config" for="ssl_verify$cur">$T('opt-ssl_verify')</label>
<select name="ssl_verify" id="ssl_verify$cur">
<option value="3" <!--#if $server['ssl_verify'] == 3 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-medium')</option>
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-minimal')</option>
<option value="2" <!--#if $server['ssl_verify'] == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="1" <!--#if $server['ssl_verify'] == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
<option value="0" <!--#if $server['ssl_verify'] == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('-', '<br/>-')</span>
<span class="desc">$T('explain-ssl_verify').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="ssl_ciphers$cur">$T('opt-ssl_ciphers')</label>
@@ -254,12 +243,6 @@
<input type="checkbox" name="optional" id="optional$cur" value="1" <!--#if int($server['optional']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="pipelining_requests$cur">$T('srv-pipelining_requests')</label>
<input type="number" name="pipelining_requests" id="pipelining_requests$cur" value="$server['pipelining_requests']" min="1" max="20" required />
<span class="desc">$T('explain-pipelining_requests')<br>$T('readwiki')
<a href="https://sabnzbd.org/wiki/advanced/nntp-pipelining" target="_blank">https://sabnzbd.org/wiki/advanced/nntp-pipelining</a></span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="expire_date$cur">$T('srv-expire_date')</label>
<input type="date" name="expire_date" id="expire_date$cur" value="$server['expire_date']" />
@@ -305,7 +288,7 @@
<p><b>$T('srv-expire_date'):</b> $(server['expire_date'])</p>
<!--#end if#-->
<!--#if $server['quota']#-->
<p><b>$T('quota-left'):</b> $(server['quota_left'])</p>
<p><b>$T('quota-left'):</b> $(server['quota_left'])B</p>
<!--#end if#-->
</div>
<div class="server-chart" data-serverid="${cur}">
@@ -476,14 +459,14 @@
When finished loading
**/
jQuery(document).ready(function(){
// Initialize tooltips
jQuery('[data-toggle="tooltip"]').tooltip()
// Reload form in case we change items that make the servers appear different
jQuery('input[name="priority"], input[name="displayname"], textarea[name="notes"]').on('change', function(event) {
var parentForm = jQuery(event.target).parents("form")
parentForm.unbind("submit")
parentForm.find('input[name="ajax"]').val('')
// Exception when change of priority, reload
jQuery('input[name="priority"], input[name="displayname"]').on('change', function() {
jQuery('.fullform').submit(function() {
// No ajax this time
jQuery('input[name="ajax"]').val('')
// Skip the fancy stuff, just submit
this.submit()
})
})
/**
@@ -571,26 +554,9 @@
if(data.value.result) {
resultBox.addClass('alert-success')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
// Allow adding the new server if we are in the new-server section
if(theButton.parents("form[action='addServer']").length) {
jQuery(".addNewServer").removeAttr("disabled")
jQuery(".addNewServer").removeAttr("data-toggle")
jQuery(".addNewServer").removeAttr("title")
jQuery(".addNewServer").tooltip("destroy")
}
} else {
resultBox.addClass('alert-danger')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
// Disable the adding of new server, just to be sure
if(theButton.parents("form[action='addServer']").length) {
jQuery(".addNewServer").attr("disabled", "disabled")
jQuery(".addNewServer").attr("data-toggle", "tooltip")
jQuery(".addNewServer").attr("data-placement", "top")
jQuery(".addNewServer").attr("title", "$T('wizard-test-server-required')")
jQuery(".addNewServer").tooltip()
}
}
});
});
@@ -647,7 +613,7 @@
})
}
if(event.data === 'hide_server') {
// Hide and don't load anymore until the next release
// Hide and don't load anymore untill the next release
jQuery('.Servers .server-frame').hide()
localStorage.setItem("server-frame-hide-$version", "hide")
}

View File

@@ -71,7 +71,7 @@
<div class="field-pair">
<label class="config" for="field_sort_string_$cur">$T('sortString')</label>
<input type="text" name="sort_string" id="field_sort_string_$cur" value="$slot.sort_string" required="required" />
<button type="button" class="btn btn-default patternKey" onclick="jQuery('#pattern_explainer_$cur').toggle(); window.scrollBy(0, 500);">
<button type="button" title="$T('sort-legenda')" class="btn btn-default patternKey" onclick="jQuery('#pattern_explainer_$cur').toggle(); window.scrollBy(0, 500);">
<span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> $T('sort-legenda')
</button>
</div>

View File

@@ -69,7 +69,7 @@
</div>
<div class="field-pair">
<label class="config" for="propagation_delay">$T('opt-propagation_delay')</label>
<input type="number" name="propagation_delay" id="propagation_delay" value="$propagation_delay" min="0" /> <i>$T('minutes')</i>
<input type="number" name="propagation_delay" id="propagation_delay" value="$propagation_delay" /> <i>$T('minutes')</i>
<span class="desc">$T('explain-propagation_delay')</span>
</div>
<div class="field-pair advanced-settings">
@@ -93,7 +93,7 @@
<option value="0" <!--#if int($no_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('fail-to-history')</option>
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
</select>
<span class="desc">
@@ -107,7 +107,7 @@
<option value="0" <!--#if int($no_smart_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_smart_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_smart_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_smart_dupes) == 3 then 'selected="selected"' else ""#--> >$T('fail-to-history')</option>
<option value="3" <!--#if int($no_smart_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_smart_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
</select>
<span class="desc">
@@ -125,7 +125,7 @@
<select name="pause_on_pwrar" id="pause_on_pwrar">
<option value="0" <!--#if int($pause_on_pwrar) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="1" <!--#if int($pause_on_pwrar) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="2" <!--#if int($pause_on_pwrar) == 2 then 'selected="selected"' else ""#--> >$T('fail-to-history')</option>
<option value="2" <!--#if int($pause_on_pwrar) == 2 then 'selected="selected"' else ""#--> >$T('abort')</option>
</select>
<span class="desc">$T('explain-pause_on_pwrar')</span>
</div>
@@ -143,7 +143,7 @@
<select name="action_on_unwanted_extensions" id="action_on_unwanted_extensions">
<option value="0" <!--#if int($action_on_unwanted_extensions) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="1" <!--#if int($action_on_unwanted_extensions) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="2" <!--#if int($action_on_unwanted_extensions) == 2 then 'selected="selected"' else ""#--> >$T('fail-to-history')</option>
<option value="2" <!--#if int($action_on_unwanted_extensions) == 2 then 'selected="selected"' else ""#--> >$T('abort')</option>
</select>
<span class="desc">$T('explain-action_on_unwanted_extensions')</span>
</div>
@@ -259,17 +259,16 @@
<span class="desc">$T('explain-cleanup_list')</span>
</div>
<div class="field-pair">
<label class="config" for="history_retention_option">$T('opt-history_retention')</label>
<select name="history_retention_option" id="history_retention_option">
<option value="all" <!--#if $auto_sort == "all" then 'selected="selected"' else ""#-->>$T('history_retention-all')</option>
<option value="number-archive" <!--#if $history_retention_option == "number-archive" then 'selected="selected"' else ""#-->>$T('history_retention-number-archive')</option>
<option value="number-delete" <!--#if $history_retention_option == "number-delete" then 'selected="selected"' else ""#-->>$T('history_retention-number-delete')</option>
<option value="days-archive" <!--#if $history_retention_option == "days-archive" then 'selected="selected"' else ""#-->>$T('history_retention-days-archive')</option>
<option value="days-delete" <!--#if $history_retention_option == "days-delete" then 'selected="selected"' else ""#-->>$T('history_retention-days-delete')</option>
<option value="all-archive" <!--#if $history_retention_option == "all-archive" then 'selected="selected"' else ""#-->>$T('history_retention-archive')</option>
<option value="all-delete" <!--#if $history_retention_option == "all-delete" then 'selected="selected"' else ""#-->>$T('history_retention-none')</option>
<label class="config" for="history_retention_select">$T('opt-history_retention')</label>
<input type="hidden" name="history_retention" id="history_retention" value="$history_retention">
<select name="history_retention_select" id="history_retention_select">
<option value="0">$T('history_retention-all')</option>
<option value="n">$T('history_retention-number')</option>
<option value="d">$T('history_retention-days')</option>
<option value="-1">$T('history_retention-none')</option>
</select>
<input type="number" id="history_retention_number" name="history_retention_number" min="1" value="$history_retention_number">
<input type="number" id="history_retention_number" name="history_retention_number" min="1">
<span class="desc">$T('explain-history_retention').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
@@ -360,24 +359,52 @@
<script type="text/javascript">
jQuery(document).ready(function() {
jQuery('#history_retention_option').on('change', updateHistoryRetention)
jQuery('#history_retention_select, #history_retention_number').on('change', updateHistoryRetention)
function updateHistoryRetention() {
var retention_option = jQuery('#history_retention_option').val()
var retention_setting = jQuery('#history_retention')
var retention_select = jQuery('#history_retention_select').val()
var retention_number = jQuery('#history_retention_number')
if(retention_option === "number-archive" || retention_option === "number-delete") {
retention_number.show()
retention_number.attr('placeholder', '$T('history_retention-limit')')
} else if(retention_option === "days-archive" || retention_option === "days-delete") {
retention_number.show()
retention_number.attr('placeholder', '$T('days').capitalize()')
} else {
// Keep all or keep none
if(retention_select === "0" || retention_select === "-1") {
retention_number.hide()
retention_number.val('')
retention_number.attr('placeholder', '')
retention_setting.val(retention_select)
} else {
retention_number.show()
// Days or number?
if(retention_select.indexOf("d") !== -1) {
retention_number.attr('placeholder', '$T('days').capitalize()')
if(retention_number.val()) {
retention_setting.val(retention_number.val() + 'd')
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
} else {
retention_number.attr('placeholder', '$T('history_retention-limit')')
if(retention_number.val()) {
retention_setting.val(retention_number.val())
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
}
}
}
updateHistoryRetention()
// Set the history-retention settig
var retention_setting_value = jQuery('#history_retention').val()
if(parseInt(retention_setting_value) > 0) {
// Days or number?
if(retention_setting_value.indexOf("d") !== -1) {
jQuery('#history_retention_select').val("d")
} else {
jQuery('#history_retention_select').val("n")
}
jQuery('#history_retention_number').val(parseInt(retention_setting_value))
} else {
// Keep all or keep none
jQuery('#history_retention_select').val(retention_setting_value)
jQuery('#history_retention_number').hide()
}
jQuery('.restoreDefaults').click(function(e) {
// Get section name

View File

@@ -1,4 +1,3 @@
<!DOCTYPE HTML>
<html lang="$active_lang">
<head>
<title>SABnzbd - $T('login')</title>
@@ -37,8 +36,8 @@
<div class="alert alert-danger" role="alert">$error</div>
<!--#end if#-->
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" autocomplete="username" required autofocus>
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" autocomplete="current-password" required>
<input type="text" class="form-control" name="username" placeholder="$T('srv-username')" required autofocus>
<input type="password" class="form-control" name="password" placeholder="$T('srv-password')" required>
<button class="btn btn-default"><span class="glyphicon glyphicon-circle-arrow-right"></span> $T('login') </button>
@@ -63,4 +62,4 @@
} catch(err) { }
</script>
</body>
</html>
</html>

View File

@@ -50,6 +50,7 @@ textarea,
.navbar-default .navbar-nav>li>a:focus,
.navbar-logo:hover,
.quoteBlock,
.selected,
.server-disabled,
#serverResponse,
.table>tbody>tr:nth-child(odd),
@@ -61,10 +62,30 @@ select:hover {
color: #EBEBEB !important;
}
.failed {
.correct {
border: 2px solid #00cc22 !important;
}
.failed,
.required-star,
.error-text {
color: #ff3333 !important;
}
.unselected,
.selected {
border: 1px solid #EBEBEB !important;
color: #EBEBEB !important;
}
.incorrect {
border: 2px solid #ff3333 !important;
}
.disabled-text {
color: #777 !important;
}
#rightGreyText,
small {
color: #c7c7c7 !important;
@@ -187,7 +208,7 @@ ul.tabs a,
#subscriptions,
.RSS form[action="add_rss_feed"] tr:nth-child(even),
.Config .table {
border: 1px solid #555555;
border: 1px solid #555555 !important;
}
.Categories form:first-of-type tr:last-of-type,
@@ -267,9 +288,18 @@ col2 h3 a,
fill: #555555;
}
::placeholder {
/* Placeholders - Will not work if grouped! */
::-webkit-input-placeholder {
color: #EBEBEB !important;
}
::-moz-placeholder {
color: #EBEBEB !important;
opacity: 1 !important;
}
:-ms-input-placeholder {
color: #EBEBEB !important;
opacity: 0.5;
}
.tooltip-inner {
@@ -285,14 +315,6 @@ col2 h3 a,
border-top-color: #E4E4E4 !important;
}
.tooltip.left .tooltip-arrow {
border-left-color: #E4E4E4 !important;
}
.tooltip.right .tooltip-arrow {
border-right-color: #E4E4E4 !important;
}
.Special .glyphicon-asterisk {
color: #E4E4E4 !important;
}

View File

@@ -19,7 +19,6 @@ body {
float: left;
overflow: visible;
border: 1px solid #dfdede;
border-bottom: none !important;
background-color: #FFF;
width: 100%
}
@@ -165,9 +164,6 @@ input[type="checkbox"]+.desc {
color: #666;
margin: 1em 0;
}
.col2 p {
margin-left: 3px;
}
.field-pair {
padding: 6px;
clear: both;
@@ -1226,6 +1222,7 @@ input[type="checkbox"] {
}
.value-and-select select {
min-width: 30px;
margin-top: 1px;
}
.dotOne, .dotTwo, .dotThree {

View File

@@ -60,7 +60,6 @@
// Initialize
this.element = $(element);
this.initialDir = null;
this.showFiles = false;
this.currentBrowserPath = null;
this.currentRequest = null;
this.fileBrowserDialog = $('#filebrowser_modal .modal-body');
@@ -100,11 +99,6 @@
this.initialDir = this.element.data('initialdir') + folderSeperator + this.element.val();
}
// Are we selecting files or folders
if(this.element.data('files')) {
this.showFiles = true
}
// Browse
this.browse(this.initialDir , folderBrowseUrl);
@@ -150,20 +144,12 @@
// Still loading
if (this.currentRequest) this.currentRequest.abort();
// Show hidden folders
var params = { name: path}
if($('#show_hidden_folders').is(':checked')) {
params['show_hidden_folders'] = "1"
}
// Show files?
if(this.showFiles) {
params['show_files'] = "1"
}
// Show hidden folders on Linux?
var extraHidden = $('#show_hidden_folders').is(':checked') ? '&show_hidden_folders=1' : '';
// Get current folders
this.currentBrowserPath = path;
this.currentRequest = $.getJSON(endpoint, params, function (data) {
this.currentRequest = $.getJSON(endpoint + extraHidden, { name: path }, function (data) {
// Clean
self.fileBrowserDialog.empty();
@@ -177,21 +163,11 @@
}
// Regular link
link = $('<a class="list-group-item" href="javascript:void(0)" />').click(function () {
// Are we looking for files and did we select a file?
if(self.showFiles && !entry.dir) {
// Trigger selection
self.currentBrowserPath = entry.path
$('#filebrowser_modal_accept').click()
} else {
self.browse(entry.path, endpoint);
}
}).text(entry.name);
self.browse(entry.path, endpoint); }
).text(entry.name);
// Back image
if(entry.name === '..') {
$('<span class="glyphicon glyphicon-arrow-left"></span> ').prependTo(link);
} else if(!entry.dir) {
$('<span class="glyphicon glyphicon-file"></span> ').prependTo(link);
} else {
$('<span class="glyphicon glyphicon-folder-open"></span> ').prependTo(link);
}
@@ -262,10 +238,9 @@ function do_restart() {
// Show overlay
$('.main-restarting').show()
// Check if we need redirect
// Uses == on purpose, because val() returns string and data() returns int!
// What template
var switchedHTTPS = ($('#enable_https').is(':checked') === ($('#enable_https').data('original') === undefined))
var portsUnchanged = ($('#port').val() == $('#port').data('original')) && ($('#https_port').val() == $('#https_port').data('original'))
var portsUnchanged = ($('#port').val() === $('#port').data('original')) && ($('#https_port').val() === $('#https_port').data('original'))
// Are we on settings page or did nothing change?
if(!$('body').hasClass('General') || (!switchedHTTPS && portsUnchanged)) {
@@ -332,7 +307,7 @@ function do_restart() {
});
}
// Remove obfuscation
// Remove obfusication
function removeObfuscation() {
$('input[data-hide]').each(function(index, objInput) {
$(objInput).attr('name', $(objInput).data('hide'))
@@ -348,36 +323,6 @@ function addRowColor() {
})
}
// Set default functions for the autocomplete everywhere
jQuery.extend(jQuery.fn.typeahead.defaults, {
source: function (query, process) {
// If there's no separator, it must be a relative path
if(query.split(folderSeperator).length < 2 && this.$element.data('initialdir')) {
query = this.$element.data('initialdir') + folderSeperator + query;
}
var params = { compact: "1", name: query }
if($('#show_hidden_folders').is(':checked')) {
params['show_hidden_folders'] = "1"
}
if(this.$element.data('files')) {
params['show_files'] = "1"
}
// Get info from the API
return jQuery.get(folderBrowseUrl, params, function (data) {
return process(data["paths"]);
});
},
updater: function(item) {
// Is it a relative path?
if(item.indexOf(this.$element.data('initialdir')) === 0) {
// Remove start
return item.replace(this.$element.data('initialdir') + folderSeperator, '');
}
// Full path
return item
}
})
$(document).ready(function () {
/**
Restart function
@@ -490,9 +435,6 @@ $(document).ready(function () {
addRowColor()
}
addRowColor()
// Add tooltips
jQuery('[title]').tooltip()
});
/*

View File

@@ -1,7 +1,7 @@
<div class="history" id="history-tab">
<div class="history" id="history-tab" data-bind="visible: hasHistory() || displayTabbed()" style="display: none">
<div class="history-header">
<h2>$T('menu-history') <small data-bind="visible: history.showArchive()">($T('archive'))</small></h2>
<a href="#" data-bind="click: history.showMultiEdit, visible: hasHistory()">
<h2>$T('menu-history')</h2>
<a href="#" data-bind="click: history.showMultiEdit">
<span class="glyphicon glyphicon-tasks" data-tooltip="true" data-placement="left" title="$T('Glitter-multiOperations')"></span>
</a>
</div>
@@ -18,16 +18,7 @@
<th style="width: 60px;"></th>
</tr>
</thead>
<!-- ko if: !hasHistory() -->
<tbody class="no-downloads">
<tr>
<td colspan="6" data-bind="attr: { 'colspan': 5 + extraHistoryColumns().length }">
<span>$T('empty')</span>
</td>
</tr>
</tbody>
<!-- /ko -->
<tbody data-bind="foreach: history.historyItems, visible: hasHistory()" style="display: none;">
<tbody data-bind="foreach: history.historyItems">
<tr class="history-item" data-bind="css: {'history-failed-download':failed()}">
<td>
<div data-bind="visible: processingWaiting()">
@@ -83,20 +74,13 @@
<div class="col-sm-2">$T('name')</div>
<div class="col-sm-10" data-bind="text: historyStatus.name"></div>
</div>
<div class="row" data-bind="visible: historyStatus.time_added">
<div class="col-sm-2">$T('rss-added')</div>
<div class="col-sm-10" data-bind="text: timeAdded(), attr: { 'data-timestamp': historyStatus.time_added }"></div>
</div>
<div class="row">
<div class="col-sm-2">$T('post-Completed')</div>
<div class="col-sm-10" data-bind="text: completedOn, attr: { 'data-timestamp': completed }"></div>
</div>
<div class="row">
<div class="col-sm-2">$T('status')</div>
<div class="col-sm-10">
<span data-bind="text: glitterTranslate.status[historyStatus.status()] ? glitterTranslate.status[historyStatus.status()] : statusText()"></span>
<a href="#" class="mark-completed-link" data-bind="visible: failed(), click: markAsCompleted" title="$T('button-mark-completed')"><span class="glyphicon glyphicon-ok"></span> $T('post-Completed')</a>
</div>
<div class="col-sm-10" data-bind="text: glitterTranslate.status[historyStatus.status()] ? glitterTranslate.status[historyStatus.status()] : statusText()"></div>
</div>
<div class="row">
<div class="col-sm-2">$T('size')</div>
@@ -124,7 +108,7 @@
</div>
<!-- /ko -->
</div>
<a href="#" data-bind="click: parent.triggerRemoveDownload">
<a href="#" data-bind="click: deleteSlot">
<span class="hover-button glyphicon glyphicon-trash" data-bind="css: { 'glyphicon-stop' : processingDownload() == 2, disabled : processingDownload() == 1 }, attr: { title: processingDownload() == 2 ? '$T('abort')' : '$T('nzo-delete')' }"></span>
</a>
</td>
@@ -146,22 +130,20 @@
</ul>
<div class="multioperations-selector" id="history-options">
<a href="#" class="hover-button history-archive" title="$T('showArchive') / $T('showAllHis')" data-tooltip="true" data-placement="top" data-bind="click: history.toggleShowArchive, css: { 'history-options-show-failed': history.showArchive }"><svg viewBox="6 6 36 36" height="14" width="14" class="archive-icon"><path d="M41.09 10.45l-2.77-3.36c-.56-.66-1.39-1.09-2.32-1.09h-24c-.93 0-1.76.43-2.31 1.09l-2.77 3.36c-.58.7-.92 1.58-.92 2.55v25c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4v-25c0-.97-.34-1.85-.91-2.55zm-17.09 24.55l-11-11h7v-4h8v4h7l-11 11zm-13.75-25l1.63-2h24l1.87 2h-27.5z"/></svg></a>
<a href="#" class="hover-button" title="$T('showFailedHis') / $T('showAllHis')" data-tooltip="true" data-placement="top" data-bind="click: history.toggleShowFailed, css: { 'history-options-show-failed': history.showFailed }"><span class="glyphicon glyphicon-exclamation-sign"></span></a>
<a href="#" class="hover-button" title="$T('link-retryAll')" data-tooltip="true" data-placement="top" data-bind="click: history.retryAllFailed"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="hover-button" title="$T('button-mark-completed')" data-bind="visible: (history.isMultiEditing() && hasHistory()), click: history.doMultiMarkCompleted" data-tooltip="true" data-placement="top"><span class="glyphicon glyphicon-ok"></span></a>
<a href="#" class="hover-button" title="$T('link-retryAll')" data-tooltip="true" data-placement="left" data-bind="click: history.retryAllFailed"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="hover-button" title="$T('showAllHis') / $T('showFailedHis')" data-tooltip="true" data-placement="left" data-bind="click: history.toggleShowFailed, css: { 'history-options-show-failed': history.showFailed }"><span class="glyphicon glyphicon-exclamation-sign"></span></a>
<div data-bind="visible: (history.isMultiEditing() && hasHistory())">
<div data-bind="visible: history.isMultiEditing()">
<span class="label label-default" data-bind="text: history.multiEditItems().length">0</span>
<label for="multiedit-checkall-history">
<input type="checkbox" name="multieditCheckAll" id="multiedit-checkall-history" title="$T('Glitter-checkAll')" data-bind="click: history.checkAllJobs" data-tooltip="true" data-placement="top" />
</label>
</div>
<a href="#" class="hover-button" title="$T('nzo-delete')" data-bind="visible: (history.isMultiEditing() && hasHistory()), click: history.doMultiDelete" data-tooltip="true" data-placement="top">
<a href="#" class="hover-button" title="$T('nzo-delete')" data-bind="visible: history.isMultiEditing(), click: history.doMultiDelete">
<span class="glyphicon glyphicon-trash"></span>
</a>
<a href="#modal-purge-history" class="hover-button" title="$T('purgeHist')" data-bind="visible: !(history.isMultiEditing() && hasHistory())" data-toggle="modal" data-tooltip="true" data-placement="top">
<a href="#modal-purge-history" class="hover-button" title="$T('purgeHist')" data-bind="visible: !history.isMultiEditing()" data-toggle="modal" data-tooltip="true" data-placement="left">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
@@ -173,3 +155,9 @@
<span data-bind="text: history.downloadedTotal"></span>B $T('Glitter-total')
</div>
</div>
<div class="info-container history-info" data-bind="visible: !hasHistory() && !displayTabbed()" style="display: none">
<span class="glyphicon glyphicon-save"></span>
<span data-bind="text: history.downloadedToday"></span>B $T('Glitter-today')
<span data-bind="text: history.downloadedMonth"></span>B $T('Glitter-thisMonth')
<span data-bind="text: history.downloadedTotal"></span>B $T('Glitter-total')
</div>

View File

@@ -6,12 +6,8 @@
<span class="glyphicon glyphicon-open"></span> $T('Glitter-notification-uploading') <span class="main-notification-box-file-count"></span>
</div>
<div class="main-notification-box-uploading-failed">
<span class="glyphicon glyphicon-exclamation-sign"></span> $T('Glitter-notification-upload-failed').replace('%s', '') <span class="main-notification-box-file-count"></span>
</div>
<div class="main-notification-box-queue-repair">
<span class="glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
<span class="glyphicon glyphicon glyphicon-wrench"></span> $T('Glitter-repairQueue')
</div>
<div class="main-notification-box-disconnect">
@@ -170,12 +166,6 @@
</div>
<div class="col-sm-6 col-loading" data-bind="visible: !hasPerformanceInfo()">$T('Glitter-loading')<span class="loader-dot-one">.</span><span class="loader-dot-two">.</span><span class="loader-dot-three">.</span></div>
</div>
<div class="row">
<div class="col-sm-6">$T('platform') &nbsp; </div>
<div class="col-sm-6">
$platform
</div>
</div>
<div class="row test-download">
<div class="col-sm-6">$T('dashboard-testDownload') &nbsp; </div>
<div class="col-sm-6">
@@ -538,7 +528,7 @@
<div class="form-group">
<label class="col-sm-4 control-label">$T('category')</label>
<div class="col-sm-6">
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText', optionsCaption: ''"></select>
<select name="Category" class="form-control" data-bind="options: queue.categoriesList, optionsValue: 'catValue', optionsText: 'catText'"></select>
<span class="glyphicon glyphicon-tag"></span>
</div>
</div>
@@ -547,7 +537,7 @@
<div class="col-sm-6">
<!-- This list is different from the one during download! -->
<select name="Priority" class="form-control">
<option value=""></option>
<option value="-100">$T('default')</option>
<option value="2">$T('pr-force')</option>
<option value="1">$T('pr-high')</option>
<option value="0">$T('pr-normal')</option>
@@ -560,14 +550,14 @@
<div class="form-group">
<label class="col-sm-4 control-label">$T('swtag-pp')</label>
<div class="col-sm-6">
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: ''"></select>
<select name="Processing" class="form-control" data-bind="options: queue.processingOptions, optionsValue: 'value', optionsText: 'name', optionsCaption: '$T('default')'"></select>
<span class="glyphicon glyphicon-check"></span>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">$T('eoq-scripts')</label>
<div class="col-sm-6">
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '', optionsValue: 'scriptValue', optionsText: 'scriptText', enable: (queue.scriptsList().length > 1)"></select>
<select name="Post-processing" class="form-control" data-bind="options: queue.scriptsList, optionsCaption: '$T('default')', optionsValue: 'scriptValue', optionsText: 'scriptText', enable: (queue.scriptsList().length > 1)"></select>
<span class="glyphicon glyphicon-flash"></span>
</div>
</div>
@@ -645,59 +635,6 @@
</div>
</div>
<div id="modal-delete-queue-job" class="modal modal-delete-job fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title row-wrap-text">$T('removeNZB-Files')</h4>
</div>
<form data-bind="submit: queue.removeDownloads">
<div class="modal-body">
$T('confirm-delete')
<ul data-bind="foreach: queue.deleteItems">
<li data-bind="text: name"></li>
</ul>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">$T('cancel')</button>
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> $T('nzo-delete')</button>
</div>
</form>
</div>
</div>
</div>
<div id="modal-delete-history-job" class="modal modal-delete-job fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title row-wrap-text">$T('nzo-delete')</h4>
</div>
<form data-bind="submit: history.removeDownloads">
<div class="modal-body">
$T('confirm-delete')
<ul data-bind="foreach: history.deleteItems">
<li data-bind="text: historyStatus.name"></li>
</ul>
</div>
<div class="modal-footer">
<div class="checkbox" data-bind="visible: !history.showArchive()">
<label>
<input type="checkbox" data-bind="checked: history.permanentlyDelete"> <span>$T('permanently-delete')</span>
</label>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">$T('cancel')</button>
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> $T('nzo-delete')</button>
</div>
</form>
</div>
</div>
</div>
<div id="modal-retry-job" class="modal modal-small fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
@@ -784,7 +721,7 @@
</tbody>
</table>
<hr/>
<p><small>Copyright &copy; 2007-2025 by The SABnzbd-Team (<a href="https://sabnzbd.org/" target="_blank">sabnzbd.org</a>)<br/>$T('yourRights') </small></p>
<p><small>Copyright &copy; 2007-2024 by The SABnzbd-Team (<a href="https://sabnzbd.org/" target="_blank">sabnzbd.org</a>)<br/>$T('yourRights') </small></p>
</div>
</div>
</div>
@@ -803,18 +740,11 @@
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-completed"><span class="glyphicon glyphicon-floppy-saved"></span> $T('purgeCompl')</button><hr />
<button type="button" class="btn btn-danger" data-bind="click: history.emptyHistory" data-action="history-purge-page"><span class="glyphicon glyphicon-check"></span> $T('purgePage') <span class="label label-default" data-bind="text: history.historyItems().length"></span></button>
</div>
<div class="modal-footer">
<div class="checkbox">
<label>
<input type="checkbox" data-bind="checked: history.permanentlyDelete"> <span>$T('permanently-delete')</span>
</label>
</div>
</div>
</div>
</div>
</div>
<div id="modal-custom-pause" class="modal modal-small fade" tabindex="-1">
<div id="modal_custom_pause" class="modal modal-small fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">

View File

@@ -15,10 +15,10 @@
<span data-bind="text: queueDataLeft"></span> $T('Glitter-left')
</div>
<!-- /ko -->
<!-- ko if: (parseInt(quotaLimit()) != 0) -->
<!-- ko if: (quotaLimit() != 0) -->
<div class="info-container-box">
<span data-bind="css: { 'queue-error-info' : (parseInt(quotaLimitLeft())<=0) }">
<span class="glyphicon glyphicon-log-in"></span> <span data-bind="text: quotaLimitLeft"></span> / <span data-bind="text: quotaLimit"></span> $T('quota-left')
<span class="glyphicon glyphicon-log-in"></span> <span data-bind="text: quotaLimitLeft"></span>B / <span data-bind="text: quotaLimit"></span>B $T('quota-left')
</span>
</div>
<!-- /ko -->
@@ -163,7 +163,7 @@
<!-- /ko -->
</div>
<!-- /ko -->
<a href="#" class="hover-button" title="$T('removeNZB-Files')" data-bind="click: parent.triggerRemoveDownload"><span class="glyphicon glyphicon-trash"></span></a>
<a href="#" class="hover-button" title="$T('removeNZB-Files')" data-bind="click: removeDownload"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</tbody>
@@ -174,25 +174,19 @@
<label for="multiedit-checkall-queue">
<input type="checkbox" name="multieditCheckAll" id="multiedit-checkall-queue" title="$T('Glitter-checkAll')" data-bind="click: queue.checkAllJobs" data-tooltip="true" data-placement="top" />
</label>
<a href="#" class="hover-button" title="$T('removeNZB-Files')" data-bind="click: queue.doMultiDelete" data-tooltip="true" data-placement="top">
<a href="#" class="hover-button" title="$T('removeNZB-Files')" data-bind="click: queue.doMultiDelete">
<span class="glyphicon glyphicon-trash"></span>
</a>
</div>
<div class="add-nzb-inputbox add-nzb-inputbox-small">
<label for="multiedit-play" data-bind="event: { mousedown: queue.handleMultiEditStatusMouseDown }">
<label for="multiedit-play">
<input type="radio" name="multiedit-status" value="resume" id="multiedit-play" data-bind="event: { change: queue.doMultiEditUpdate }" />
<span class="glyphicon glyphicon-play" title="$T('link-resume')" data-tooltip="true" data-placement="top"></span>
</label>
<label for="multiedit-pause" data-bind="event: { mousedown: queue.handleMultiEditStatusMouseDown }">
<label for="multiedit-pause">
<input type="radio" name="multiedit-status" value="pause" id="multiedit-pause" data-bind="event: { change: queue.doMultiEditUpdate }" />
<span class="glyphicon glyphicon-pause" title="$T('link-pause')" data-tooltip="true" data-placement="top"></span>
</label>
<a href="#" class="hover-button" title="$T('Glitter-top')" data-bind="click: queue.doMultiMoveToTop" data-tooltip="true" data-placement="top">
<span class="glyphicon glyphicon-chevron-up"></span>
</a>
<a href="#" class="hover-button" title="$T('Glitter-bottom')" data-bind="click: queue.doMultiMoveToBottom" data-tooltip="true" data-placement="top">
<span class="glyphicon glyphicon-chevron-down"></span>
</a>
<span class="label label-default" data-bind="text: queue.multiEditItems().length">0</span>
</div>
<div class="add-nzb-inputbox-clear"></div>
@@ -227,4 +221,4 @@
<span data-bind="text: page"></span>
</li>
</ul>
</div>
</div>

View File

@@ -52,15 +52,16 @@
var glitterTranslate = new Object();
glitterTranslate.paused = "$T('post-Paused')";
glitterTranslate.left = "$T('Glitter-left')";
glitterTranslate.clearOrphanWarning = "$T('Glitter-clearOrphanWarning')";
glitterTranslate.clearWarn = "$T('confirm')";
glitterTranslate.pausePromptFail = "$T('Glitter-pausePromptFail')"
glitterTranslate.pauseFor = "$T('pauseFor')"
glitterTranslate.minutes = "$T('mins')"
glitterTranslate.shutdown = "$T('shutdownOK?')";
glitterTranslate.restart = "$T('explain-Restart') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n").replace(/&quot;/g,'"');
glitterTranslate.confirm = "$T('confirm')";
glitterTranslate.markComplete = "$T('button-mark-completed')";
glitterTranslate.deleteMsg = "$T('nzo-delete')";
glitterTranslate.removeDown = "$T('confirm')";
glitterTranslate.removeDow1 = "$T('confirm')";
glitterTranslate.renameAbort = "$T('Glitter-confirmAbortDirectUnpack')\n$T('confirm')";
glitterTranslate.retryAll = "$T('link-retryAll')?";
glitterTranslate.fetch = "$T('Glitter-fetch')";

View File

@@ -9,13 +9,10 @@ function HistoryListModel(parent) {
self.lastUpdate = 0;
self.historyItems = ko.observableArray([])
self.showFailed = ko.observable(false).extend({ persist: 'historyShowFailed' });
self.showArchive = ko.observable(false).extend({ persist: 'historyShowArchive' });
self.permanentlyDelete = ko.observable(false).extend({ persist: 'permanentlyDelete' });
self.isLoading = ko.observable(false).extend({ rateLimit: 100 });
self.searchTerm = ko.observable('').extend({ rateLimit: { timeout: 400, method: "notifyWhenChangesStop" } });
self.paginationLimit = ko.observable(10).extend({ persist: 'historyPaginationLimit' });
self.totalItems = ko.observable(0);
self.deleteItems = ko.observableArray([]);
self.ppItems = ko.observable(0);
self.pagination = new paginationModel(self);
self.isMultiEditing = ko.observable(false).extend({ persist: 'historyIsMultiEditing' });
@@ -121,27 +118,6 @@ function HistoryListModel(parent) {
self.parent.refresh(true)
});
self.triggerRemoveDownload = function(items) {
// Show and fill modal
self.deleteItems.removeAll()
// Single or multiple items?
if(items.length) {
ko.utils.arrayPushAll(self.deleteItems, items)
} else {
self.deleteItems.push(items)
}
// Show modal or delete right away
if(self.parent.confirmDeleteHistory()) {
// Open modal if desired
$('#modal-delete-history-job').modal("show")
} else {
// Otherwise just submit right away
$('#modal-delete-history-job form').submit()
}
}
// Retry a job
self.retryJob = function(form) {
// Adding a extra retry file happens through this special function
@@ -200,17 +176,10 @@ function HistoryListModel(parent) {
// Toggle showing failed
self.toggleShowFailed = function(data, event) {
// Set the loader so it doesn't flicker and then switch
self.isLoading(true)
self.showFailed(!self.showFailed())
// Force hide tooltip so it doesn't linger
$('#history-options a').tooltip('hide')
// Force refresh
self.parent.refresh(true)
}
// Toggle showing archive
self.toggleShowArchive = function(data, event) {
self.showArchive(!self.showArchive())
// Force hide tooltip so it doesn't linger
// Forde hide tooltip so it doesn't linger
$('#history-options a').tooltip('hide')
// Force refresh
self.parent.refresh(true)
@@ -232,9 +201,11 @@ function HistoryListModel(parent) {
// Empty history options
self.emptyHistory = function(data, event) {
// Make sure no flickering
self.isLoading(true)
// What event?
var whatToRemove = $(event.target).data('action');
var skipArchive = $('#modal-purge-history input[type="checkbox"]').prop("checked")
var del_files, value;
// Purge failed
@@ -267,7 +238,6 @@ function HistoryListModel(parent) {
mode: 'history',
name: 'delete',
del_files: 1,
archive: (!skipArchive) * 1,
value: strIDs
}).then(function() {
// Clear search, refresh and hide
@@ -282,9 +252,8 @@ function HistoryListModel(parent) {
callAPI({
mode: 'history',
name: 'delete',
del_files: del_files,
archive: (!skipArchive) * 1,
value: value
value: value,
del_files: del_files
}).then(function() {
self.parent.refresh();
$("#modal-purge-history").modal('hide');
@@ -359,108 +328,43 @@ function HistoryListModel(parent) {
return true;
}
// Remove downloads from history
self.removeDownloads = function(form) {
// Hide modal and show notification
$('#modal-delete-history-job').modal("hide")
showNotification('.main-notification-box-removing')
var strIDsPP = '';
var strIDsHistory = '';
$.each(self.deleteItems(), function(index) {
// Split in jobs that need post-processing aborted, and jobs that need to be deleted
if(this.processingDownload() === 2) {
strIDsPP = strIDsPP + this.id + ',';
// These items should not be listed in the deletedItems later on
// as active post-processing aren't removed from the history output
self.deleteItems.remove(this)
} else {
strIDsHistory = strIDsHistory + this.id + ',';
}
})
// Trigger post-processing aborting
if(strIDsPP !== "") {
callAPI({
mode: 'cancel_pp',
value: strIDsPP
}).then(function(response) {
// Only hide and refresh
self.parent.refresh();
hideNotification()
});
}
if(strIDsHistory !== "") {
var skipArchive = $('#modal-delete-history-job input[type="checkbox"]').prop("checked")
// Permanently delete if we are on the Archive page
if(self.showArchive()) skipArchive = true
callAPI({
mode: 'history',
name: 'delete',
del_files: 1,
archive: (!skipArchive) * 1,
value: strIDsHistory
}).then(function(response) {
self.historyItems.removeAll(self.deleteItems());
self.multiEditItems.removeAll(self.deleteItems())
self.parent.refresh();
hideNotification()
});
}
};
// Delete all selected
self.doMultiDelete = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Trigger modal
self.triggerRemoveDownload(self.multiEditItems())
}
// Mark jobs as completed
self.markAsCompleted = function(items) {
// Confirm
if(!confirm(glitterTranslate.markComplete)) {
return
}
// Single or multiple items?
var strIDs = '';
if(items.length) {
$.each(items, function(index) {
// Need confirm
if(!self.parent.confirmDeleteHistory() || confirm(glitterTranslate.removeDown)) {
// List all the ID's
var strIDs = '';
$.each(self.multiEditItems(), function(index) {
strIDs = strIDs + this.id + ',';
})
} else {
strIDs = items.id
// Show notification
showNotification('.main-notification-box-removing-multiple', 0, self.multiEditItems().length)
// Remove
callAPI({
mode: 'history',
name: 'delete',
del_files: 1,
value: strIDs
}).then(function(response) {
if(response.status) {
// Make sure the queue doesnt flicker and then fade-out
// Make sure no flickering (if there are more items left) and then remove
self.isLoading(self.totalItems() > 1)
self.parent.refresh();
// Empty it
self.multiEditItems.removeAll();
// Hide notification
hideNotification()
}
})
}
// Send the API call
callAPI({
mode: 'history',
name: 'mark_as_completed',
value: strIDs
}).then(function(response) {
// Force refresh to update the UI
self.parent.refresh(true);
});
}
// Mark all selected as completed
self.doMultiMarkCompleted = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Mark them
self.markAsCompleted(self.multiEditItems());
}
// Focus on the confirm button
$('#modal-delete-history-job').on("shown.bs.modal", function() {
$('#modal-delete-history-job .btn[type="submit"]').focus()
})
// On change of page we need to check all those that were in the list!
self.historyItems.subscribe(function() {
// We need to wait until the unit is actually finished rendering
@@ -587,11 +491,6 @@ function HistoryModel(parent, data) {
return displayDateTime(self.completed(), parent.parent.dateFormat(), 'X')
});
// Format time added
self.timeAdded = ko.pureComputed(function() {
return displayDateTime(self.historyStatus.time_added(), parent.parent.dateFormat(), 'X')
});
// Subscribe to retryEvent so we can load the password
self.canRetry.subscribe(function() {
self.updateAllHistory = true;
@@ -607,11 +506,6 @@ function HistoryModel(parent, data) {
$('#modal-retry-job').modal("show")
};
// Mark as completed button
self.markAsCompleted = function() {
parent.markAsCompleted(self);
};
// Update information only on click
self.updateAllHistoryInfo = function(data, event) {
// Show
@@ -648,4 +542,36 @@ function HistoryModel(parent, data) {
return false;
})
}
// Delete button
self.deleteSlot = function(item, event) {
// Confirm?
if(!self.parent.parent.confirmDeleteHistory() || confirm(glitterTranslate.deleteMsg + ":\n" + item.historyStatus.name() + "\n\n" + glitterTranslate.removeDow1)) {
// Are we still processing and it can be stopped?
if(item.processingDownload() === 2) {
callAPI({
mode: 'cancel_pp',
value: self.id
})
// All we can do is wait
} else {
// Delete the item
callAPI({
mode: 'history',
name: 'delete',
del_files: 1,
value: self.id
}).then(function(response) {
if(response.status) {
// Make sure no flickering (if there are more items left) and then remove
self.parent.isLoading(self.parent.totalItems() > 1)
self.parent.historyItems.remove(self);
self.parent.multiEditItems.remove(function(inList) { return inList.id === self.id; })
self.parent.parent.refresh();
}
});
}
}
};
}

View File

@@ -13,7 +13,7 @@ function ViewModel() {
self.useGlobalOptions = ko.observable(true).extend({ persist: 'useGlobalOptions' });
self.refreshRate = ko.observable(1).extend({ persist: 'pageRefreshRate' });
self.dateFormat = ko.observable('fromNow').extend({ persist: 'pageDateFormat' });
self.displayTabbed = ko.observable(false).extend({ persist: 'displayTabbed' });
self.displayTabbed = ko.observable().extend({ persist: 'displayTabbed' });
self.displayCompact = ko.observable(false).extend({ persist: 'displayCompact' });
self.displayFullWidth = ko.observable(false).extend({ persist: 'displayFullWidth' });
self.confirmDeleteQueue = ko.observable(true).extend({ persist: 'confirmDeleteQueue' });
@@ -120,7 +120,8 @@ function ViewModel() {
// Dynamic history length check
self.hasHistory = ko.pureComputed(function() {
return (self.history.historyItems().length > 0 || self.history.searchTerm() || self.history.isLoading())
// We also 'have history' if we can't find any results of the search or there are no failed ones
return (self.history.historyItems().length > 0 || self.history.searchTerm() || self.history.showFailed() || self.history.isLoading())
})
self.hasWarnings = ko.pureComputed(function() {
@@ -363,7 +364,6 @@ function ViewModel() {
failed_only: self.history.showFailed() * 1,
start: self.history.pagination.currentStart(),
limit: parseInt(self.history.paginationLimit()),
archive: self.history.showArchive() * 1,
last_history_update: self.history.lastUpdate
}
if (self.history.searchTerm()) {
@@ -431,16 +431,18 @@ function ViewModel() {
return;
}
// Show modal
$('#modal-custom-pause').modal('show')
}
$('#modal_custom_pause').modal('show')
// Focus on the input field
$('#modal_custom_pause').on('shown.bs.modal', function() {
$('#customPauseInput').focus()
})
$('#modal-custom-pause').on('shown.bs.modal', function() {
// Focus on the input field when opening the modal
$('#customPauseInput').focus()
}).on('hide.bs.modal', function() {
// Reset on modal close
self.pauseCustom('');
})
$('#modal_custom_pause').on('hide.bs.modal', function() {
self.pauseCustom('');
})
}
// Update on changes
self.pauseCustom.subscribe(function(newValue) {
@@ -453,7 +455,7 @@ function ViewModel() {
// At least 3 charaters
if (newValue.length < 3) {
$('#customPauseOutput').text('').data('time', 0)
$('#modal-custom-pause .btn-default').addClass('disabled')
$('#modal_custom_pause .btn-default').addClass('disabled')
return;
}
@@ -478,11 +480,11 @@ function ViewModel() {
var pauseDuration = Math.round((pauseParsed - Date.parse('now')) / 1000 / 60);
$('#customPauseOutput').html('<span class="glyphicon glyphicon-pause"></span> ' + glitterTranslate.pauseFor + ' ' + pauseDuration + ' ' + glitterTranslate.minutes)
$('#customPauseOutput').data('time', pauseDuration)
$('#modal-custom-pause .btn-default').removeClass('disabled')
$('#modal_custom_pause .btn-default').removeClass('disabled')
} else if (newValue) {
// No..
$('#customPauseOutput').text(glitterTranslate.pausePromptFail)
$('#modal-custom-pause .btn-default').addClass('disabled')
$('#modal_custom_pause .btn-default').addClass('disabled')
}
})
@@ -501,7 +503,7 @@ function ViewModel() {
// Refresh and close the modal
self.refresh()
self.downloadsPaused(true);
$('#modal-custom-pause').modal('hide')
$('#modal_custom_pause').modal('hide')
});
}
}
@@ -726,9 +728,6 @@ function ViewModel() {
$('#nzbname').val('')
$('.btn-file em').html(glitterTranslate.chooseFile + '&hellip;')
}
}).fail(function(xhr, status, error) {
// Update the uploading notification text to show error
showNotification('.main-notification-box-uploading-failed', 0, error)
});
}
@@ -898,7 +897,7 @@ function ViewModel() {
// Orphaned folder deletion of all
self.removeAllOrphaned = function() {
if (confirm(glitterTranslate.clearOrphanWarning)) {
if (!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) {
// Show notification
showNotification('.main-notification-box-removing-multiple', 0, self.statusInfo.folders().length)
// Delete them all
@@ -915,7 +914,7 @@ function ViewModel() {
// Orphaned folder adding of all
self.addAllOrphaned = function() {
if (confirm(glitterTranslate.confirm)) {
if (!self.confirmDeleteHistory() || confirm(glitterTranslate.clearWarn)) {
// Show notification
showNotification('.main-notification-box-sendback')
// Delete them all

View File

@@ -32,7 +32,6 @@ function QueueListModel(parent) {
// External var's
self.queueItems = ko.observableArray([]);
self.totalItems = ko.observable(0);
self.deleteItems = ko.observableArray([]);
self.isMultiEditing = ko.observable(false).extend({ persist: 'queueIsMultiEditing' });
self.isLoading = ko.observable(false).extend({ rateLimit: 100 });
self.multiEditItems = ko.observableArray([]);
@@ -148,27 +147,6 @@ function QueueListModel(parent) {
}
self.triggerRemoveDownload = function(items) {
// Show and fill modal
self.deleteItems.removeAll()
// Single or multiple items?
if(items.length) {
ko.utils.arrayPushAll(self.deleteItems, items)
} else {
self.deleteItems.push(items)
}
// Show modal or delete right away
if(self.parent.confirmDeleteQueue()) {
// Open modal if desired
$('#modal-delete-queue-job').modal("show")
} else {
// Otherwise just submit right away
$('#modal-delete-queue-job form').submit()
}
}
// Save pagination state
self.paginationLimit.subscribe(function(newValue) {
// Save in config if global
@@ -423,103 +401,42 @@ function QueueListModel(parent) {
}
// Handle mousedown to capture state before change
self.handleMultiEditStatusMouseDown = function(item, event) {
var clickedValue = $(event.currentTarget).find("input").val();
// If this radio was already selected (same value as previous), clear it
if ($('.multioperations-selector input[name="multiedit-status"]:checked').val() === clickedValue) {
// Clear all radio buttons in this group after the click finished
// Hacky, but it works
setTimeout(function () {
$('.multioperations-selector input[name="multiedit-status"]').prop('checked', false);
}, 200)
}
return true;
}
// Remove downloads from queue
self.removeDownloads = function(form) {
// Hide modal and show notification
$('#modal-delete-queue-job').modal("hide")
showNotification('.main-notification-box-removing')
var strIDs = '';
$.each(self.deleteItems(), function(index) {
strIDs = strIDs + this.id + ',';
})
callAPI({
mode: 'queue',
name: 'delete',
del_files: 1,
value: strIDs
}).then(function(response) {
self.queueItems.removeAll(self.deleteItems());
self.multiEditItems.removeAll(self.deleteItems())
self.parent.refresh();
hideNotification()
});
};
// Delete all selected
self.doMultiDelete = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Trigger modal
self.triggerRemoveDownload(self.multiEditItems())
}
// Need confirm
if(!self.parent.confirmDeleteQueue() || confirm(glitterTranslate.removeDown)) {
// List all the ID's
var strIDs = '';
$.each(self.multiEditItems(), function(index) {
strIDs = strIDs + this.id + ',';
})
// Move all selected to top
self.doMultiMoveToTop = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Show notification
showNotification('.main-notification-box-removing-multiple', 0, self.multiEditItems().length)
// Move each item to the top, starting from the last one in the sorted list
var arrayList = self.multiEditItems()
var movePromises = [];
for(var i = arrayList.length - 1; i >= 0; i--) {
movePromises.push(callAPI({
mode: "switch",
value: arrayList[i].id,
value2: 0
}));
// Remove
callAPI({
mode: 'queue',
name: 'delete',
del_files: 1,
value: strIDs
}).then(function(response) {
if(response.status) {
// Make sure the queue doesnt flicker and then fade-out
self.isLoading(true)
self.parent.refresh()
// Empty it
self.multiEditItems.removeAll();
// Hide notification
hideNotification()
}
})
}
// Wait for all moves to complete then refresh
Promise.all(movePromises).then(function() {
self.parent.refresh();
});
}
// Move all selected to bottom
self.doMultiMoveToBottom = function() {
// Anything selected?
if(self.multiEditItems().length < 1) return;
// Move each item to the bottom, starting from the first one in the sorted list
var arrayList = self.multiEditItems()
var movePromises = [];
for(var i = 0; i < arrayList.length; i++) {
movePromises.push(callAPI({
mode: "switch",
value: arrayList[i].id,
value2: self.totalItems() - 1
}));
}
// Wait for all moves to complete then refresh
Promise.all(movePromises).then(function() {
self.parent.refresh();
});
}
// Focus on the confirm button
$('#modal-delete-queue-job').on("shown.bs.modal", function() {
$('#modal-delete-queue-job .btn[type="submit"]').focus()
})
// On change of page we need to check all those that were in the list!
self.queueItems.subscribe(function() {
// We need to wait until the unit is actually finished rendering
@@ -710,37 +627,9 @@ function QueueModel(parent, data) {
self.editingName(true)
self.nameForEdit(self.name())
// Select the input
const $input = $(event.target).parents('.name').find('input');
$input.select();
// Add Tab/Shift+Tab navigation
$input.off('keydown.tabnav').on('keydown.tabnav', function (e) {
if (e.key === 'Tab') {
e.preventDefault();
// Find all rename inputs that are currently visible
const inputs = $('.queue-table input[type="text"]');
const currentIndex = inputs.index(this);
let nextIndex = e.shiftKey ? currentIndex - 1 : currentIndex + 1;
// Wrap around
if (nextIndex >= inputs.length) nextIndex = 0;
if (nextIndex < 0) nextIndex = inputs.length - 1;
// Simulate clicking Rename on the next row
const $nextRow = inputs.eq(nextIndex).closest('tr');
$nextRow.find('.hover-button[title="Rename"]').click();
// Delay focusing to wait for new input to appear
setTimeout(() => {
const $nextInput = $('.queue-table input[type="text"]').eq(nextIndex);
$nextInput.focus().select();
}, 50);
}
});
};
// Select
$(event.target).parents('.name').find('input').select()
}
// Catch the submit action
self.editingNameSubmit = function() {
@@ -822,5 +711,29 @@ function QueueModel(parent, data) {
})
}
// Remove 1 download from queue
self.removeDownload = function(item, event) {
// Confirm and remove
if(!self.parent.parent.confirmDeleteQueue() || confirm(glitterTranslate.deleteMsg + ":\n" + item.name() + "\n\n" + glitterTranslate.removeDow1)) {
var itemToDelete = this;
// Show notification
showNotification('.main-notification-box-removing')
callAPI({
mode: 'queue',
name: 'delete',
del_files: 1,
value: item.id
}).then(function(response) {
// Make sure no flickering (if there are more items left) and then remove
self.parent.isLoading(self.parent.totalItems() > 1)
parent.queueItems.remove(itemToDelete);
parent.multiEditItems.remove(function(inList) { return inList.id === itemToDelete.id; })
self.parent.parent.refresh();
// Hide notifcation
hideNotification()
});
}
};
}

View File

@@ -69,10 +69,6 @@ legend,
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.main-notification-box-uploading-failed {
color: #F95151;
}
.container,
.modal-body,
.modal-footer {
@@ -210,8 +206,7 @@ tbody .caret {
.info-container,
#modal-options .options-status-box small,
#modal-options #options-status small,
#modal-options .tab-content h4,
h2 small {
#modal-options .tab-content h4 {
color: #D6D6D6;
}
@@ -277,11 +272,16 @@ button:focus {
outline: initial;
}
.archive-icon {
fill: #EBEBEB;
/* Placeholders - Will not work if grouped! */
::-webkit-input-placeholder {
color: #EBEBEB !important;
}
::placeholder {
color: #EBEBEB !important;
opacity: 0.5 !important;
::-moz-placeholder {
color: #EBEBEB !important;
opacity: 1 !important;
}
:-ms-input-placeholder {
color: #EBEBEB !important;
}

View File

@@ -690,10 +690,6 @@ tbody.no-downloads tr td {
border-bottom: 1px solid #F0F0F0 !important;
}
tbody.no-downloads tr td>span {
opacity: 0.7;
}
tbody.no-downloads tr td a {
line-height: 2em;
font-size: 1.5em;
@@ -860,7 +856,7 @@ tr.queue-item>td:first-child>a {
}
.multioperations-selector .add-nzb-inputbox {
width: 19%;
width: 20%;
float: left;
}
@@ -871,7 +867,7 @@ tr.queue-item>td:first-child>a {
}
.multioperations-selector .add-nzb-inputbox-small {
width: 115px;
width: 80px;
float: right;
padding-left: 0;
padding-top: 12px;
@@ -1097,13 +1093,6 @@ tr.queue-item>td:first-child>a {
font-weight: bold;
}
.mark-completed-link {
font-weight: bold !important;
color: #28a745 !important;
text-decoration: underline;
margin-left: 10px;
}
.history-status-hidden {
display: none;
}
@@ -1114,26 +1103,20 @@ tr.queue-item>td:first-child>a {
}
#history-options .hover-button {
padding: 7px;
padding: 7px 8px 7px 8px;
line-height: 1.428571429;
display: inline-block;
}
#history-options .hover-button.history-archive {
line-height: 1em;
vertical-align: middle;
}
#history-options div {
display: inline-block;
vertical-align: middle;
margin-left: 5px;
}
#history-options input[name="multieditCheckAll"] {
vertical-align: middle;
position: relative;
top: -2px;
top: -1px;
margin-left: 8px;
}
@@ -1203,10 +1186,6 @@ tr.queue-item>td:first-child>a {
color: red !important;
}
.history-options-show-failed .archive-icon {
fill: #2bbd43;
}
.processing-download {
width: 16px;
height: 18px;
@@ -1542,34 +1521,6 @@ input[name="nzbURL"] {
transition : border 500ms ease-out;
}
/* DELETE MODAL */
.modal-delete-job ul {
margin-top: 10px;
}
.modal-delete-job li {
word-break: break-all;
}
.modal-delete-job .checkbox {
float: left;
margin: 8px 5px 0px;
}
#modal-purge-history .checkbox {
margin: 0;
}
.modal-delete-job .checkbox input,
#modal-purge-history .checkbox input {
margin-top: 3px;
}
.modal-delete-job .checkbox input+span,
#modal-purge-history .checkbox input+span {
font-weight: bold;
}
/* HELP MODAL */
#modal-help .modal-body {

View File

@@ -7,10 +7,6 @@
padding-right: 8px;
}
.container-full-width .container {
width: 100%;
}
.main-navbar {
margin-top: 0;
padding: 0;
@@ -167,7 +163,7 @@ tr.queue-item>td:first-child>a {
}
.multioperations-selector .add-nzb-inputbox-small {
width: 115px;
width: 72px;
}
.multioperations-selector .add-nzb-inputbox-clear {

View File

@@ -7,9 +7,7 @@
<h1>$T('wizard-quickstart')</h1>
<hr />
<script type="text/javascript">
var txtTestServer = "$T('wizard-server-text')";
var txtChecking = "$T('srv-testing')";
var txtTestRequired = "$T('wizard-test-server-required')";
<!--#include raw $webdir + "/static/javascript/checkserver.js"#-->
</script>
<h3>$T('wizard-server')</h3>
@@ -24,7 +22,7 @@
<div class="form-group">
<label for="host" class="col-sm-4 control-label">$T('srv-host')</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="host" id="host" value="$host" placeholder="$T('wizard-example') news.newshosting.com" required />
<input type="text" class="form-control" name="host" id="host" value="$host" placeholder="$T('wizard-example') news.newshosting.com" />
</div>
</div>
<div class="form-group">
@@ -59,31 +57,29 @@
<div class="form-group">
<label for="port" class="col-sm-4 control-label">$T('srv-port')</label>
<div class="col-sm-8">
<input type="number" class="form-control" name="port" id="port" value="<!--#if $port then $port else '563' #-->" min="0" max="65535" required />
<input type="number" class="form-control" name="port" id="port" value="<!--#if $port then $port else '563' #-->" />
</div>
</div>
<div class="form-group">
<label for="connections" class="col-sm-4 control-label">$T('srv-connections')</label>
<div class="col-sm-8">
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '8'#-->" min="1" max="500" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" required />
<input type="number" class="form-control" name="connections" id="connections" value="<!--#if $connections then $connections else '8'#-->" data-toggle="tooltip" data-placement="right" title="$T('wizard-server-con-explain') $T('wizard-server-con-eg')" />
</div>
</div>
<div class="form-group">
<label for="ssl_verify" class="col-sm-4 control-label">$T('opt-ssl_verify')</label>
<div class="col-sm-8">
<select name="ssl_verify" id="ssl_verify" class="form-control">
<option value="3" <!--#if $ssl_verify == 3 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="2" <!--#if $ssl_verify == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-medium')</option>
<option value="1" <!--#if $ssl_verify == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-minimal')</option>
<option value="2" <!--#if $ssl_verify == 2 then 'selected="selected"' else ""#--> >$T('ssl_verify-strict')</option>
<option value="1" <!--#if $ssl_verify == 1 then 'selected="selected"' else ""#--> >$T('ssl_verify-normal')</option>
<option value="0" <!--#if $ssl_verify == 0 then 'selected="selected"' else ""#--> >$T('ssl_verify-disabled')</option>
</select>
<span class="desc">$T('explain-ssl_verify').replace('-', '<br/>-')</span>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-4">
<button id="serverTest" class="btn btn-default" data-toggle="tooltip" data-placement="left"><span class="glyphicon glyphicon-sort"></span> $T('wizard-button-testServer')</button>
<button id="serverTest" class="btn btn-default"><span class="glyphicon glyphicon-sort"></span> $T('wizard-button-testServer')</button>
</div>
<div class="col-sm-8">
<div id="serverResponse" class="well well-sm">$T('wizard-server-text')</div>

View File

@@ -1,31 +1,9 @@
// Variable to track server test results
var serverTestSuccessful = false;
function resetTestResult() {
serverTestSuccessful = false;
$('#serverResponse').html(txtTestServer);
checkRequired();
}
function setTestResult(success) {
serverTestSuccessful = success;
checkRequired();
}
function checkRequired() {
// Check if form is valid using HTML5 validation and if server test passed
if ($("form").get(0).checkValidity() && serverTestSuccessful) {
if ($("#host").val() && $("#connections").val()) {
$("#next-button").removeClass('disabled')
$("#next-button").removeAttr('data-toggle')
$("#next-button").removeAttr('title')
$("#next-button").tooltip('destroy')
return true;
} else {
$("#next-button").addClass('disabled')
$("#next-button").attr('data-toggle', 'tooltip')
$("#next-button").attr('data-placement', 'left')
$("#next-button").attr('title', txtTestRequired)
$("#next-button").tooltip()
return false;
}
}
@@ -34,13 +12,8 @@ $(document).ready(function() {
// Add tooltips
$('[data-toggle="tooltip"]').tooltip()
// On server test button click
// On form-submit
$("#serverTest").click(function() {
// Check HTML5 form validation before testing server
if (!$("form").get(0).reportValidity()) {
return false;
}
$('#serverResponse').html(txtChecking);
$.getJSON(
"../api?mode=config&name=test_server&output=json",
@@ -48,10 +21,8 @@ $(document).ready(function() {
function(result) {
if (result.value.result) {
r = '<span class="success"><span class="glyphicon glyphicon-ok"></span> ' + result.value.message + '</span>';
setTestResult(true);
} else {
r = '<span class="failed"><span class="glyphicon glyphicon-minus-sign"></span> ' + result.value.message + '</span>';
setTestResult(false);
}
r = r.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="failed" target="_blank">https://sabnzbd.org/certificate-errors</a>')
$('#serverResponse').html(r);
@@ -60,9 +31,26 @@ $(document).ready(function() {
return false;
});
// Reset test result when any form field changes
$("#host, #username, #password, #port, #connections, #ssl_verify").bind('input change', function() {
resetTestResult();
$("#port, #connections").bind('keyup blur', function() {
if (this.value > 0) {
$(this).removeClass("incorrect");
$(this).addClass("correct");
} else {
$(this).removeClass("correct");
$(this).addClass("incorrect");
}
checkRequired()
});
$("#host, #username, #password").bind('keyup blur', function() {
if (this.value) {
$(this).removeClass("incorrect");
$(this).addClass("correct");
} else {
$(this).removeClass("correct");
$(this).addClass("incorrect");
}
checkRequired();
});
$('#ssl').click(function() {
@@ -77,14 +65,13 @@ $(document).ready(function() {
$('#port').val('119')
}
}
resetTestResult();
})
checkRequired()
$('form').submit(function(event) {
// Check if server test passed (HTML5 validation is automatic)
if(!serverTestSuccessful) {
// Double check
if(!checkRequired()) {
event.preventDefault();
}
})

View File

@@ -88,25 +88,49 @@ label {
float: right;
margin: 0;
}
.desc {
font-size: 12px;
font-style: italic;
}
.align-right {
text-align: right;
}
.align-center {
text-align: center;
}
.unselected,
.selected {
display: inline-block;
}
.unselected {
padding: 6px 10px 6px 10px;
border: 1px solid #636363;
margin-left: 8px;
margin-right: 8px;
color: #636363;
}
.selected {
padding: 6px 10px 6px 10px;
color: white;
background-color: #636363;
border: 1px solid #636363;
margin-left: 8px;
margin-right: 8px;
}
.bigger {
font-size: 14px;
}
.bigger input {
font-size: 16px;
}
.required-star {
color: red;
}
.full-width {
width: 100%;
}
.correct {
border: 2px solid #00cc22;
}
.incorrect {
border: 2px solid red;
}
.hidden {
display: none !important;
}
@@ -122,12 +146,20 @@ label {
.input-group-bw {
width: 150px;
}
.disabled-text {
text-decoration: line-through;
color: #ccc;
}
#serverResponse {
padding: 6px 10px;
}
#host-tip {
margin-bottom: 5px;
}
.error-text {
display: inline;
color: red;
}
#bandwidth {
display: inline-block;
}

View File

@@ -1,14 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2022-2025 by The SABnzbd-Team (sabnzbd.org) -->
<!-- Copyright 2022-2024 by The SABnzbd-Team (sabnzbd.org) -->
<component type="desktop-application">
<id>org.sabnzbd.sabnzbd</id>
<metadata_license>MIT</metadata_license>
<name>SABnzbd</name>
<summary>Free and easy binary newsreader</summary>
<branding>
<color type="primary" scheme_preference="light">#e7e7e7</color>
<color type="primary" scheme_preference="dark">#444444</color>
</branding>
<description>
<p>
SABnzbd is a free and Open Source web-based binary newsreader,
@@ -21,13 +17,6 @@
and services that help automate the download process.
</p>
</description>
<keywords>
<keyword>usenet</keyword>
<keyword>nzb</keyword>
<keyword>download</keyword>
<keyword>newsreader</keyword>
<keyword>binary</keyword>
</keywords>
<categories>
<category>Network</category>
<category>FileTransfer</category>
@@ -35,49 +24,22 @@
<url type="homepage">https://sabnzbd.org</url>
<url type="bugtracker">https://github.com/sabnzbd/sabnzbd/issues</url>
<url type="vcs-browser">https://github.com/sabnzbd/sabnzbd</url>
<url type="contribute">https://github.com/sabnzbd/sabnzbd</url>
<url type="translate">https://sabnzbd.org/wiki/translate</url>
<url type="donation">https://sabnzbd.org/donate</url>
<url type="help">https://sabnzbd.org/wiki/</url>
<url type="faq">https://sabnzbd.org/wiki/faq</url>
<url type="contact">https://sabnzbd.org/live-chat.html</url>
<releases>
<release version="4.6.0" date="2025-12-24" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.6.0</url>
</release>
<release version="4.5.5" date="2025-10-24" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.5</url>
</release>
<release version="4.5.4" date="2025-10-22" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.4</url>
</release>
<release version="4.5.3" date="2025-08-25" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.3</url>
</release>
<release version="4.5.2" date="2025-07-09" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.2</url>
</release>
<release version="4.5.1" date="2025-04-11" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.1</url>
</release>
<release version="4.5.0" date="2025-04-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.5.0</url>
</release>
<release version="4.4.1" date="2024-12-23" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.4.1</url>
</release>
<release version="4.4.0" date="2024-12-09" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.4.0</url>
</release>
<release version="4.3.3" date="2024-08-01" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.3</url>
</release>
<release version="4.3.2" date="2024-05-30" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.2</url>
</release>
<release version="4.3.1" date="2024-05-03" type="stable">
<url type="details">https://github.com/sabnzbd/sabnzbd/releases/tag/4.3.1</url>
</release>
<release version="4.2.3" date="2024-03-10" type="stable"/>
<release version="4.2.2" date="2024-01-31" type="stable"/>
<release version="4.2.1" date="2024-01-05" type="stable"/>
<release version="4.2.0" date="2024-01-03" type="stable"/>
<release version="4.1.0" date="2023-09-26" type="stable"/>
<release version="4.0.3" date="2023-06-16" type="stable"/>
<release version="4.0.2" date="2023-06-09" type="stable"/>
<release version="4.0.1" date="2023-05-01" type="stable"/>
<release version="4.0.0" date="2023-04-28" type="stable"/>
<release version="3.7.2" date="2023-02-05" type="stable"/>
</releases>
<launchable type="desktop-id">sabnzbd.desktop</launchable>
<provides>
@@ -90,69 +52,19 @@
<control>touch</control>
</supports>
<recommends>
<display_length compare="ge">640</display_length>
<display_length compare="ge">small</display_length>
<internet>always</internet>
</recommends>
<project_license>GPL-2.0-or-later</project_license>
<developer id="org.sabnzbd">
<name>The SABnzbd-Team</name>
</developer>
<developer_name>The SABnzbd-Team</developer_name>
<screenshots>
<screenshot type="default">
<image>https://sabnzbd.org/images/landing/screenshots/interface.png</image>
<caption>Intuitive interface</caption>
<caption>Web interface</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/night-mode.png</image>
<caption>Also comes in Night-mode</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/add-nzb.png</image>
<caption>Add NZB's or use drag-and-drop!</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/phone-interface.png</image>
<caption>Scales to any screen size</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/history-details.png</image>
<caption>Easy overview of all history details</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/phone-extra.png</image>
<caption>Every option, on every screen size</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/file-lists.png</image>
<caption>Manage a job's individual files</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-speedlimit.png</image>
<caption>Easy speed limiting</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-options.png</image>
<caption>Quickly change settings</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/dashboard.png</image>
<caption>Easy system check</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/connections-overview.png</image>
<caption>See active connections</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/skin-settings.png</image>
<caption>Customize the interface</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/tabbed.png</image>
<caption>Tabbed-mode</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/set-custom-pause.png</image>
<caption>Specify any pause duration</caption>
<caption>Night mode</caption>
</screenshot>
<screenshot>
<image>https://sabnzbd.org/images/landing/screenshots/config.png</image>

View File

@@ -1,17 +1,11 @@
[Desktop Entry]
Name=SABnzbd
GenericName=Binary newsreader
GenericName[fr]=Lecteur de newsgroups binaires
GenericName[tr]=İkili haber grupları okuyucusu
Comment=Download from Usenet
Comment[fr]=Télécharger depuis Usenet
Comment[tr]=Usenet ağından dosya indir
Exec=/opt/sabnzbd/SABnzbd.py --browser 1 %F
Icon=sabnzbd
Terminal=false
Type=Application
Categories=Network;FileTransfer;
Keywords=usenet;binaries;download;nzb;nntp;newsreader;
Keywords[fr]=usenet;binaires;télécharger;nzb;nntp;newsreader;
Keywords[tr]=usenet;ikililer;indir;nzb;nntp;okuyucu;
MimeType=application/x-nzb;application/x-compressed-nzb;

View File

@@ -22,11 +22,6 @@ ExecStart=/opt/sabnzbd/SABnzbd.py --disable-file-log --logging 1 --browser 0
User=%I
Type=simple
Restart=on-failure
ProtectSystem=full
DeviceAllow=/dev/null rw
DeviceAllow=/dev/urandom r
DevicePolicy=strict
NoNewPrivileges=yes
[Install]
WantedBy=multi-user.target

View File

Binary file not shown.

View File

@@ -1,144 +0,0 @@
7-Zip for Linux and macOS
~~~~~~~~~~~~~~~~~~~~~~~~~
License for use and distribution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7-Zip Copyright (C) 1999-2024 Igor Pavlov.
The licenses for 7zz and 7zzs files are:
- The "GNU LGPL" as main license for most of the code
- The "GNU LGPL" with "unRAR license restriction" for some code
- The "BSD 3-clause License" for some code
- The "BSD 2-clause License" for some code
Redistributions in binary form must reproduce related license information from this file.
Note:
You can use 7-Zip on any computer, including a computer in a commercial
organization. You don't need to register or pay for 7-Zip.
GNU LGPL information
--------------------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You can receive a copy of the GNU Lesser General Public License from
http://www.gnu.org/
BSD 3-clause License in 7-Zip code
----------------------------------
The "BSD 3-clause License" is used for the following code in 7z.dll
1) LZFSE data decompression.
That code was derived from the code in the "LZFSE compression library" developed by Apple Inc,
that also uses the "BSD 3-clause License".
2) ZSTD data decompression.
that code was developed using original zstd decoder code as reference code.
The original zstd decoder code was developed by Facebook Inc,
that also uses the "BSD 3-clause License".
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Copyright (c) Facebook, Inc. All rights reserved.
Copyright (c) 2023-2024 Igor Pavlov.
Text of the "BSD 3-clause License"
----------------------------------
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
BSD 2-clause License in 7-Zip code
----------------------------------
The "BSD 2-clause License" is used for the XXH64 code in 7-Zip.
XXH64 code in 7-Zip was derived from the original XXH64 code developed by Yann Collet.
Copyright (c) 2012-2021 Yann Collet.
Copyright (c) 2023-2024 Igor Pavlov.
Text of the "BSD 2-clause License"
----------------------------------
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---
unRAR license restriction
-------------------------
The decompression engine for RAR archives was developed using source
code of unRAR program.
All copyrights to original unRAR code are owned by Alexander Roshal.
The license for original unRAR code has the following restriction:
The unRAR sources cannot be used to re-create the RAR compression algorithm,
which is proprietary. Distribution of modified unRAR sources in separate form
or as a part of other software is permitted, provided that it is clearly
stated in the documentation and source comments that the code may
not be used to develop a RAR (WinRAR) compatible archiver.
--

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

BIN
osx/7zip/7zz Executable file
View File

Binary file not shown.

88
osx/7zip/License.txt Normal file
View File

@@ -0,0 +1,88 @@
7-Zip
~~~~~
License for use and distribution
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7-Zip Copyright (C) 1999-2021 Igor Pavlov.
The licenses for 7zz file are:
- The "GNU LGPL" as main license for most of the code
- The "GNU LGPL" with "unRAR license restriction" for some code
- The "BSD 3-clause License" for some code
Redistributions in binary form must reproduce related license information from this file.
Note:
You can use 7-Zip on any computer, including a computer in a commercial
organization. You don't need to register or pay for 7-Zip.
GNU LGPL information
--------------------
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You can receive a copy of the GNU Lesser General Public License from
http://www.gnu.org/
BSD 3-clause License
--------------------
The "BSD 3-clause License" is used for the code in 7z.dll that implements LZFSE data decompression.
That code was derived from the code in the "LZFSE compression library" developed by Apple Inc,
that also uses the "BSD 3-clause License":
----
Copyright (c) 2015-2016, Apple Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----
unRAR license restriction
-------------------------
The decompression engine for RAR archives was developed using source
code of unRAR program.
All copyrights to original unRAR code are owned by Alexander Roshal.
The license for original unRAR code has the following restriction:
The unRAR sources cannot be used to re-create the RAR compression algorithm,
which is proprietary. Distribution of modified unRAR sources in separate form
or as a part of other software is permitted, provided that it is clearly
stated in the documentation and source comments that the code may
not be used to develop a RAR (WinRAR) compatible archiver.
--
Igor Pavlov

BIN
osx/par2/arm64/par2-turbo Executable file
View File

Binary file not shown.

BIN
osx/par2/par2-turbo Executable file
View File

Binary file not shown.

BIN
osx/unrar/arm64/unrar Executable file
View File

Binary file not shown.

BIN
osx/unrar/unrar Executable file
View File

Binary file not shown.

View File

@@ -1,10 +1,10 @@
#
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2RC1\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: team@sabnzbd.org\n"
"Language-Team: SABnzbd <team@sabnzbd.org>\n"
@@ -16,7 +16,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -61,7 +61,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -90,7 +90,7 @@ msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"

View File

@@ -1,9 +1,9 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Czech (https://app.transifex.com/sabnzbd/teams/111101/cs/)\n"
"MIME-Version: 1.0\n"
@@ -17,7 +17,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -62,7 +62,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -91,7 +91,7 @@ msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Danish (https://app.transifex.com/sabnzbd/teams/111101/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Standard E-mail-skabelon til SABnzbd\n"
"## Dette er en Cheetah-skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Linjeskift og blanktegn har betydning!\n"
"##\n"
"## Dette er e-mail-headerne \n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har <!--#if $status then \"hentet\" else \"fejlet\" #--> job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Herefter kommer kroppen, den tomme linje skal være der!\n"
"\n"
"Hej,\n"
"<!--#if $status #-->\n"
"SABnzbd har hentet \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd kunne ikke hente \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Færdig kl. $end_time\n"
"Hentet $size\n"
"\n"
"Resultat af job:\n"
"<!--#for $stage in $stages #-->\n"
"Etape $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Output fra brugerscriptet \"$script\" (Afslutningskode = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Hav det godt!\n"
"<!--#else#-->\n"
"Beklager!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## RSS E-mail-skabelon til SABnzbd\n"
"## Dette er en Cheetah-skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Linjeskift og blanktegn har betydning!\n"
"##\n"
"## Dette er e-mai-headere\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har tilføjet $antal jobs til køen\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Herefter kommer kroppen, den tomme linje skal være der!\n"
"\n"
"Hej,\n"
"\n"
"SABnzbd har tilføjet $antal job(s) til køen.\n"
"De er fra RSS-feedet \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Farvel\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Dårlig URL-hentning af E-mail-skabelon til SABnzbd\n"
"## Dette er en Cheetah-skabelon\n"
"## Dokumentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Linjeskift og blanktegn har betydning!\n"
"##\n"
"## Dette er e-mail-headere\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd kunne ikke hente en NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Herefter kommer kroppen, den tomme linje skal være der!\n"
"\n"
"Hej,\n"
"\n"
"SABnzbd kunne ikke hente NZB fra $url.\n"
"Fejlmeddelelsen er: $msg\n"
"\n"
"Farvel\n"

View File

@@ -1,14 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2025
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: German (https://app.transifex.com/sabnzbd/teams/111101/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -109,7 +109,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -161,7 +161,7 @@ msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Spanish (https://app.transifex.com/sabnzbd/teams/111101/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Plantilla de correo predeterminada para SABnzbd\n"
"## This a Cheetah template\n"
"## Documentación: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## !Los saltos de línea y espacios en blanco son significativos¡\n"
"##\n"
"## Cabeceras de correo electrónico\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd <!--#if $status then \"he bajado\" else \"fallo en bajar\" #--> job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## !Después de esto viene el cuerpo del mensaje, la línea en blanco es necesaria!\n"
"\n"
"Hola,\n"
"<!--#if $status #-->\n"
"SABnzbd he bajado \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd fallo en bajar \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Terminado a las $end_time\n"
"$size bajado\n"
"\n"
"Resultado de la transferencia:\n"
"<!--#for $stage in $stages #-->\n"
"Etapa $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Producción desde el script de usuario \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Que lo disfrutes!\n"
"<!--#else#-->\n"
"Perdon!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Plantilla de correo RSS para SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## !Los saltos de línea y espacios en blanco son significativos¡\n"
"##\n"
"## Cabeceras de correo electrónico\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd he añadido $amount transferencia(s) a la cola\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## !Después de esto viene el cuerpo del mensaje, la línea en blanco es necesaria!\n"
"\n"
"Hola,\n"
"\n"
"SABnzbd he añadido $amount transferencia(s) a la cola.\n"
"Originaron desde el RSS \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
"$job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Adios\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Plantilla de correo para URLs incorrectas de SABnzbd\n"
"## Esta es una plantilla Cheetah\n"
"## Documentación: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Líneas nuevas y espacios en blanco IMPORTAN!\n"
"##\n"
"## Estas son las cabeceras del email\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd ha encontrado un error al recuperar un NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hola,\n"
"\n"
"SABnzbd ha encontrado un error al descargar un NZB desde $url.\n"
"El error ha sido: $msg\n"
"\n"
"Un saludo\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Finnish (https://app.transifex.com/sabnzbd/teams/111101/fi/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Oletus sähköpostipohja SABnzbd:lle\n"
"## Tämä on Cheetah pohja\n"
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
"##\n"
"## Nämä ovat otsaketiedot. Rivien ensimmäisiä sanoja ei saa vaihtaa!\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd on <!--#if $status then \"valmistunut\" else \"epäonnistunut\" #--> työssä $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Tämän jälkeen tulee viestin runko, ensimmäinen rivinvaihto on pakollinen!\n"
"\n"
"Hei,\n"
"<!--#if $status #-->\n"
"SABnzbd on ladannut \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd on epäonnistunut \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#--> latauksessa\n"
"<!--#end if#-->\n"
"Valmistui $end_time\n"
"Ladattu $size\n"
"\n"
"Työn lopputulos:\n"
"<!--#for $stage in $stages #-->\n"
"Tila $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Käyttäjän skriptin tuloste \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Nauti!\n"
"<!--#else#-->\n"
"Pahoittelut!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## RSS sähköpostipohja SABnzbd:lle\n"
"## Tämä on Cheetah pohja\n"
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
"##\n"
"## Nämä ovat otsaketiedot. Rivien ensimmäisiä sanoja ei saa vaihtaa!\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd on lisännyt $amount työtä jonoon\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Tämän jälkeen tulee viestin runko, ensimmäinen rivinvaihto on pakollinen!\n"
"\n"
"Hei,\n"
"\n"
"SABnzbd on lisännyt $amount työtä jonoon.\n"
"Ne ovat RSS syötteestä \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Heippa\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,23 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Virheellisen URL-noudon sähköpostin pohja SABnzbd ohjelmalle\n"
"## Tämä on Cheetah pohja\n"
"## Dokumentaatio: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Rivinvaihdot ja välilyönnit ovat merkitseviä!\n"
"##\n"
"## Tässä on sähköpostin otsikkotiedot\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd ei voinut hakea NZB tiedostoa\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Tämän jälkeen tulee viestin sisältö, tyhjä rivi on pakollinen!\n"
"\n"
"Hei,\n"
"\n"
"SABnzbd ei voinut hakea NZB tiedostoa osoitteesta $url.\n"
"Virheviesti: $msg\n"

View File

@@ -1,14 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Fred L <88com88@gmail.com>, 2025
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Fred L <88com88@gmail.com>, 2025\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: French (https://app.transifex.com/sabnzbd/teams/111101/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -60,44 +60,45 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"#encoding UTF-8\n"
"##\n"
"## Modèle d'e-mail par défaut pour SABnzbd\n"
"## Ceci est un modèle Cheetah\n"
"## Documentation : https://sabnzbd.org/wiki/extra/email-templates\n"
"## Template Email pour SABnzbd\n"
"## Ceci est un template Cheetah\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Les retours à la ligne et les espaces sont importants !\n"
"##\n"
"## Voici les en-têtes de l'email\n"
"À : $to\n"
"De : $from\n"
"Date : $date\n"
"Objet : SABnzbd a <!--#if $status then \"terminé\" else \"n'a pu terminer\" #--> la tâche $name\n"
"X-priorité: 5\n"
"X-MS-priorité: 5\n"
"## Après cela vient le corps du message, la ligne vide est nécessaire !\n"
"## Entêtes de l'email\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd <!--#if $status#-->Succès<!--#else#-->Echec<!--#end if#--> du téléchargement $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Après cela vient le contenu, la ligne vide est nécessaire! \n"
"\n"
"Bonjour,\n"
"<!--#if $status #-->\n"
"SABnzbd a téléchargé \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"SABnzbd a téléchargé avec succès \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd n'a pu télécharger \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"SABnzbd a téléchargé sans succès \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Terminé à $end_time\n"
"$size téléchargés\n"
"Téléchargé $size\n"
"\n"
"Résultats de la tâche :\n"
"Résultat du téléchargement :\n"
"<!--#for $stage in $stages #-->\n"
"Étape $stage <!--#slurp#-->\n"
"Etape $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Résultats du script \"$script\" (Code de sortie = $script_ret):\n"
"Sortie du script utilisateur \"$script\" (Code Retour = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Amusez-vous bien !\n"
"A bientôt !\n"
"<!--#else#-->\n"
"Désolé !\n"
"<!--#end if#-->\n"
@@ -107,7 +108,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -131,37 +132,37 @@ msgid ""
"Bye\n"
msgstr ""
"##\n"
"## Modèle d'e-mail RSS pour SABnzbd\n"
"## Ceci est un modèle Cheetah\n"
"## Documentation : https://sabnzbd.org/wiki/extra/email-templates\n"
"## Template Email pour SABnzbd\n"
"## Ceci est un template Cheetah\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Les retours à la ligne et espaces sont importants !\n"
"## Les retours à la ligne et les espaces sont importants !\n"
"##\n"
"## Voici les en-têtes de l'email\n"
"À : $to\n"
"De : $from\n"
"Date : $date\n"
"Objet : SABnzbd a ajouté $amount tâches dans la file d'attente\n"
"X-priorité: 5\n"
"X-MS-priorité: 5\n"
"## Après cela vient le corps du message, la ligne vide est nécessaire !\n"
"## Entêtes de l'email\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd a ajouté $amount fichier(s) à la file d'attente\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Après cela vient le contenu, la ligne vide est nécessaire!\n"
"\n"
"Bonjour,\n"
"\n"
"SABnzbd a ajouté $amount tâche(s) à la file d'attente.\n"
"Elles viennent du flux RSS \"$feed\".\n"
"SABnzbd a ajouté $amount fichier(s) à la file d'attente.\n"
"Ils proviennent du Flux RSS \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Bye\n"
"Au Revoir\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -182,24 +183,24 @@ msgid ""
"Bye\n"
msgstr ""
"##\n"
"## Modèle d'email d'URL incorrecte pour SABnzbd\n"
"## Ceci est un modèle Cheetah\n"
"## Documentation : https://sabnzbd.org/wiki/extra/email-templates\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Les retours à la ligne et les espaces sont importants !\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## Voici les en-têtes de l'email\n"
"À : $to\n"
"De : $from\n"
"Date : $date\n"
"Objet : SABnzbd n'a pas réussi à récupérer un NZB\n"
"X-priorité: 5\n"
"X-MS-priorité: 5\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd failed to fetch an NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Bonjour,\n"
"Hi,\n"
"\n"
"SABnzbd n'a pas réussi à récupérer le NZB depuis $url.\n"
"Le message d'erreur était : $msg\n"
"SABnzbd has failed to retrieve the NZB from $url.\n"
"The error message was: $msg\n"
"\n"
"Bye\n"

View File

@@ -1,27 +1,27 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2025
# ION, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Last-Translator: ION, 2020\n"
"Language-Team: Hebrew (https://app.transifex.com/sabnzbd/teams/111101/he/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: he\n"
"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: (n % 10 == 0 && n % 1 == 0 && n > 10) ? 2 : 3;\n"
#: email/email.tmpl:1
msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -105,7 +105,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -157,7 +157,7 @@ msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"

View File

@@ -1,9 +1,9 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Language-Team: Italian (https://app.transifex.com/sabnzbd/teams/111101/it/)\n"
"MIME-Version: 1.0\n"
@@ -17,7 +17,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -62,7 +62,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -91,7 +91,7 @@ msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Norwegian Bokmål (https://app.transifex.com/sabnzbd/teams/111101/nb/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,56 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"#encoding UTF-8\n"
"## Translation by ProtX\n"
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har <!--#if $status then \"completed\" else \"failed\" #--> jobb $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hei,\n"
"<!--#if $status #-->\n"
"SABnzbd har lastet ned \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd mislyktes med å laste ned \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Ferdig $end_time\n"
"Nedlastet $size\n"
"\n"
"Resultat av jobben:\n"
"<!--#for $stage in $stages #-->\n"
"Steg $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Utskrift fra brukerskript \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Gratulerer!\n"
"<!--#else#-->\n"
"Synd!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +132,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har lagt $amount jobber til køen\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Etter dette kommer meldingen, den tomme linjen er nødvendig!\n"
"\n"
"Hei,\n"
"\n"
"SABnzbd har lagt $amount jobb(er) til køen.\n"
"Disse er fra RSS feeden \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Hade\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +183,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd ikke klarte å hente en NZB fil\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Etter dette kommer meldingen, den tomme linjen er nødvendig!\n"
"\n"
"Hei,\n"
"\n"
"SABnzbd klarte ikke å hente NZB fra $url.\n"
"Feilmeldingen var: $msg\n"
"\n"
"Hade\n"

View File

@@ -1,14 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2025
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2025\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Dutch (https://app.transifex.com/sabnzbd/teams/111101/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -21,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -107,7 +107,7 @@ msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -159,7 +159,7 @@ msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Polish (https://app.transifex.com/sabnzbd/teams/111101/pl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Domyślny szablon maila w SABnzbd\n"
"## To jest szablon Cheetah\n"
"## Dokumentacja: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Znak nowego wiersza i spacji ma znaczenie!\n"
"##\n"
"## To są nagłowki maila\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd <!--#if $status then \"zakończył\" else \"zakończył z błędem\" #--> zadanie $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Następnie treść maila, wymagana jest pusta linia!\n"
"\n"
"Cześć,\n"
"<!--#if $status #-->\n"
"SABnzbd pobrał \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd nie pobrał \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Zakończono o $end_time\n"
"Pobrano $size\n"
"\n"
"Rezultat zadania:\n"
"<!--#for $stage in $stages #-->\n"
"Etap $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
"$result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Odpowiedź od skryptu \"$script\" (kod wyjścia = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Baw się dobrze!\n"
"<!--#else#-->\n"
"Przykro mi!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Szablon wiadomości RSS dla SABnzbd\n"
"## To jest szablon Cheetah\n"
"## Dokumentacja: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Znak nowego wiersza i spacji ma znaczenie!\n"
"##\n"
"## To są nagłowki maila\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd dodał $amount zadań/zadania do kolejki\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Następnie treść maila, wymagana jest pusta linia!\n"
"\n"
"Cześć,\n"
"\n"
"SABnzbd dodał $amount zadanie/zadań do kolejki.\n"
"Pochodzą one z wiadomości RSS \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
"$job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Nara\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Szablon wiadomości błędnego pobierania URL SABnzbd\n"
"## To jest szablon Cheetah\n"
"## Dokumentacja: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Znaki nowego wiersza i białe znaki mają znaczenie!\n"
"##\n"
"## To są nagłówki wiadomości\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd nie udało się pobrać pliku NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Po tym następuje treść. Pusty wiersz jest wymagany!\n"
"\n"
"Cześć,\n"
"\n"
"SABnzbd nie udało się pobrać pliku NZB z $url.\n"
"Komunikat błędu: $msg\n"
"\n"
"Do usłyszenia.\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/sabnzbd/teams/111101/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Template padrão de e-mail para SABnzbd\n"
"## Este é um template Cheetah\n"
"## Documentação: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Novas linhas e espaços em branco são significativos!\n"
"##\n"
"## Estes são os cabeçalhos de e-mail\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd <!--#if $status then \"completou \" else \"falhou n\" #-->a tarefa $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Depois daqui vem o corpo. A linha vazia é necessária!\n"
"\n"
"Olá,\n"
"<!--#if $status #-->\n"
"SABnzbd baixou \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd falhou no download de \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Completado em $end_time\n"
"Baixados $size\n"
"\n"
"Resultados da tarefa:\n"
"<!--#for $stage in $stages #-->\n"
"Etapa $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Retorno do script de usuário \"$script\" (Código de retorno = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Aproveite!\n"
"<!--#else#-->\n"
"Lamento!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Template de e-mail RSS para SABnzbd\n"
"## Este é um template Cheetah\n"
"## Documentação: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Novas linhas e espaços em branco são significativos!\n"
"##\n"
"## Estes são os cabeçalhos de e-mail\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd adicionou $amount <!--#if $amount == \"1\" then \"tarefa\" else \"tarefas\" #--> à fila\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Depois daqui vem o corpo. A linha vazia é necessária!\n"
"\n"
"Olá,\n"
"\n"
"SABnzbd adicionou $amount <!--#if $amount == \"1\" then \"tarefa\" else \"tarefas\" #--> à fila.\n"
"Elas são do feed RSS \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Tchau!\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Template de e-mail de busca em URL ruim para SABnzbd\n"
"## Este é um template Cheetah\n"
"## Documentação: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Novas linhas e espaços em branco são significativos!\n"
"##\n"
"## Estes são os cabeçalhos de e-mail\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd falhou ao buscar um NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Depois daqui vem o corpo. A linha vazia é necessária!\n"
"\n"
"Olá,\n"
"\n"
"SABnzbd não conseguiu obter o NZB de $url.\n"
"A mensagem de erro foi: $msg\n"
"\n"
"Tchau!\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Romanian (https://app.transifex.com/sabnzbd/teams/111101/ro/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Șablon Email Original pentru SABnzbd\n"
"## Acesta este un Șablon Cheetah\n"
"## Documentație: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"##Rândurile noi și caracterele spațiu sunt importante!\n"
"##\n"
"## Acestea sunt antetele email\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd <!--#if $status then \"a terminat\" else \"nu a reuşit\" #--> sarcina $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## După acesta urmează conţinutul, este necesar o linie goală!\n"
"\n"
"Salut,\n"
"<!--#if $status #-->\n"
"SABnzbd a descărcat \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd nu a reuşit să descarce \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Terminat la $end_time\n"
"Mărime $size\n"
"\n"
"Rezultatele sarcinii:\n"
"<!--#for $stage in $stages #-->\n"
"Stagiu $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Rezultatul script-ului utilizatorului \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Bucuraţi-vă!\n"
"<!--#else#-->\n"
"Ne pare rau!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,37 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"## Şablon Email RSS pentru SABnzbd\n"
"## Acesta este un şablon Cheetah \n"
"## Documentaţie: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Rândurile noi și caracterele spațiu sunt importante!\n"
"##\n"
"## Acestea sunt antetele email\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd a adăugat $amount sarcini în coadă\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## După acesta urmează conţinutul, este necesar o linie goală!\n"
"\n"
"Salut,\n"
"\n"
"SABnzbd a adăugat $amount sarcină(e) în coadă.\n"
"Ele sunt din fluxuri RSS \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"La revedere !\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +180,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Adresă URL Greşită şablon Email pentru SABnybd \n"
"## Acesta este un şablon Cheetah\n"
"## Documentaţie : http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Liniile noi şi spaţiile sunt importante!\n"
"##\n"
"## Acestea sunt headerele email\n"
"Către: $to\n"
"De la: $from\n"
"Dată: $date\n"
"Subiect: SABnzbd nu a reuşit să descarce un NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## După aceasta urmează corpul email, linia goală e necesară !\n"
"\n"
"Salut,\n"
"\n"
"SABnzbd nu a putut descărca NZB-ul de la adresa $url.\n"
"Mesajul de eroare a fost: $msg\n"
"\n"
"La revedere!\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Russian (https://app.transifex.com/sabnzbd/teams/111101/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Стандартный шаблон сообщения электронной почты\n"
"## Это шаблон Cheetah\n"
"## Документация: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Новые строки и пробелы имеют значение!\n"
"##\n"
"## Это заголовки электронной почты\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd: задание $name <!--#if $status then \"успешно выполнено\" else \"не удалось выполнить\" #-->\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Теперь следует тело сообщения. Пустая строка является обязательной!\n"
"\n"
"Привет.\n"
"<!--#if $status #-->\n"
"Системой SABnzbd загружено задание «$name» <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"Системе SABnzbd не удалось загрузить «$name» <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Время окончания загрузки: $end_time\n"
"Загруженный размер: $size\n"
"\n"
"Результаты задания:\n"
"<!--#for $stage in $stages #-->\n"
"Stage $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Результат выполнения сценария «$script» (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Удачи!\n"
"<!--#else#-->\n"
"Сожалеем.\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Шаблон RSS для электронной почты\n"
"## Это шаблон Cheetah\n"
"## Документация: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Новые строки и пробелы имеют значение!\n"
"##\n"
"## Это заголовки электронной почты\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: В очередь загрузки SABnzbd добавлены задания: $amount \n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Теперь следует тело сообщения. Пустая строка является обязательной!\n"
"\n"
"Привет.\n"
"\n"
"В очередь загрузки SABnzbd были добавлены задания: $amount.\n"
"Они были получены из RSS-ленты «$feed».\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"До свидания\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Шаблон электронной почты для излечения неверного URL-адреса\n"
"## Это шаблон Cheetah\n"
"## Документация: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Новые строки и пробелы имеют значение!\n"
"##\n"
"## Это заголовки электронной почты\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: Службе SABnzbd не удалось загрузить NZB-файл\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Теперь следует тело сообщения. Пустая строка является обязательной!\n"
"\n"
"Привет.\n"
"\n"
"Службе SABnzbd не удалось загрузить NZB-файл по адресу $url.\n"
"Сообщение об ошибке: $msg\n"
"\n"
"Конец сообщения\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Serbian (https://app.transifex.com/sabnzbd/teams/111101/sr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## Основни шаблон ел. поште за САБнзбд\n"
"## Ово је Гепард шаблон\n"
"## Документација: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Нови редови и размаци су важни!\n"
"##\n"
"## Ово су заглавља ел. поште\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: САБнзбд је <!--#if $status then \"completed\" else \"failed\" #--> посао „$name“\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## После тога долази разрада, празни редови су потребни!\n"
"\n"
"Здраво,\n"
"<!--#if $status #-->\n"
"САБнзбд је преузео „$name“ <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"САБнзбд није успео да преузме „$name“ <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Завршено је у $end_time\n"
"Преузето је $size\n"
"\\n\n"
"Резултат рада:\n"
"<!--#for $stage in $stages #-->\n"
"Фаза $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Излаз корисничке скрипте „$script“ (Шифра излаза = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Уживајте!\n"
"<!--#else#-->\n"
"Жао ми је!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## РСС шаблон ел. поште за САБнзбд\n"
"## Ово је Гепард шаблон\n"
"## Документација: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Нови редови и размаци су важни!\n"
"##\n"
"## Ово су заглавља ел. поште\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject САБнзбд је додао $amount посла у ред\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## После тога долази разрада, празни редови су потребни!\n"
"\n"
"Здраво,\n"
"\n"
"САБнзбд је додао $amount посао(ла) у ред.\n"
"Долазе са РСС довода „$feed“.\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Поздрав\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Шаблон ел. поште лошег набављања адресе за САБнзбд\n"
"## Ово је Гепард шаблон\n"
"## Документација: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Нови редови и размаци су важни!\n"
"##\n"
"## Ово су заглавља ел. поште\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: САБнзбд није успео да преузме НЗБ\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## После тога долази разрада, празни редови су потребни!\n"
"\n"
"Здраво,\n"
"\n"
"САБнзбд није успео да преузме НЗБ са „$url“.\n"
"Порука грешке је: $msg\n"
"\n"
"Поздрав\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Swedish (https://app.transifex.com/sabnzbd/teams/111101/sv/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"## Translation by Andreas Lindberg andypandyswe@gmail.com\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd has <!--#if $status then \"completed\" else \"failed\" #--> job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hej,\n"
"<!--#if $status #-->\n"
"SABnzbd har laddat ned \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd misslyckades med att ladda ned \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Färdig $end_time\n"
"Nedladdat $size\n"
"\n"
"Resultat:\n"
"<!--#for $stage in $stages #-->\n"
"Stage $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Utmatning från användarskript \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Lycka till!\n"
"<!--#else#-->\n"
"Beklagar!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd har lagt till $amount jobb i kön\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hej,\n"
"\n"
"SABnzbd har lagt till $amount jobb i kön.\n"
"De kommer från RSS feed \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Hej då\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd misslyckades med att hämta en NZB -fil\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hej,\n"
"\n"
"SABnzbd har misslyckats med att hämta NZB -filen från $url.\n"
"Felmeddelandet lyder: $msg\n"
"\n"
"Hej då\n"

View File

@@ -1,205 +0,0 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# mauron, 2025
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: mauron, 2025\n"
"Language-Team: Turkish (https://app.transifex.com/sabnzbd/teams/111101/tr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: tr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: email/email.tmpl:1
msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd has <!--#if $status then \"completed\" else \"failed\" #--> job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hi,\n"
"<!--#if $status #-->\n"
"SABnzbd has downloaded \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd has failed to download \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Finished at $end_time\n"
"Downloaded $size\n"
"\n"
"Results of the job:\n"
"<!--#for $stage in $stages #-->\n"
"Stage $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"Output from user script \"$script\" (Exit code = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Enjoy!\n"
"<!--#else#-->\n"
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## SABnzbd için varsayılan e-posta şablonu\n"
"## Bu, bir Cheetah şablonudur\n"
"## Belgelendirme: https://sabnzbd.org/wiki/extra/email-templates\n"
"##\n"
"## Yeni satırlar ve boşluklar önemlidir!\n"
"##\n"
"## Bunlar e-posta başlıklarıdır\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd, şu işi <!--#if $status then \"indirdi\" else \"indiremedi\" #--> job $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Bu satırdan sonra içerik başlar, boş satır gereklidir!\n"
"\n"
"Merhaba,\n"
"<!--#if $status #-->\n"
"SABnzbd şu dosyayı indirdi \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd şu dosyayı indirmede başarısız oldu \"$name\" <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"Şu tarihte bitti: $end_time\n"
"Şu miktar indirildi: $size\n"
"\n"
"İşin sonucu:\n"
"<!--#for $stage in $stages #-->\n"
"Aşama $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"\"$script\" kullanıcı betiğinin çıktısııkış kodu = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Görüşmek üzere!\n"
"<!--#else#-->\n"
"Özür dileriz!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd has added $amount jobs to the queue\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hi,\n"
"\n"
"SABnzbd has added $amount job(s) to the queue.\n"
"They are from RSS feed \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Bye\n"
msgstr ""
"##\n"
"## SABnzbd için RSS e-posta şablonu\n"
"## Bu, bir Cheetah şablonudur\n"
"## Belgelendirme: https://sabnzbd.org/wiki/extra/email-templates\n"
"##\n"
"## Yeni satırlar ve boşluklar önemlidir!\n"
"##\n"
"## Bunlar, e-posta başlıklarıdır\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd, $amount işi kuyruğa ekledi\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Bundan sonra mesaj gövdesi gelir, boş satır gereklidir!\n"
"\n"
"Merhaba,\n"
"\n"
"SABnzbd, $amount işi kuyruğa ekledi.\n"
"Bunlar şu RSS beslemesindendir: \"$feed\".\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Görüşmek üzere\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
"## These are the email headers\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd failed to fetch an NZB\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## After this comes the body, the empty line is required!\n"
"\n"
"Hi,\n"
"\n"
"SABnzbd has failed to retrieve the NZB from $url.\n"
"The error message was: $msg\n"
"\n"
"Bye\n"
msgstr ""
"##\n"
"## SABnzbd için yanlış URL alma e-posta şablonu\n"
"## Bu, bir Cheetah şablonudur\n"
"## Belgelendirme: https://sabnzbd.org/wiki/extra/email-templates\\n\n"
"##\n"
"## Yeni satırlar ve boşluklar önemlidir!\n"
"##\n"
"## Bunlar, e-posta başlıklarıdır\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd, bir NZB dosyası almada başarısız oldu\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## Bundan sonra mesaj gövdesi gelir, boş satır gereklidir!\n"
"\n"
"Merhaba,\n"
"\n"
"SABnzbd, NZB dosyasını şu bağlantıdan almada başarısız oldu: $url.\n"
"Hata mesajı şuydu: $msg\n"
"\n"
"Görüşmek üzere\n"

View File

@@ -1,10 +1,14 @@
# SABnzbd Translation Template file EMAIL
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
# Copyright 2007-2024 by The SABnzbd-Team (sabnzbd.org)
#
# Translators:
# Safihre <safihre@sabnzbd.org>, 2020
#
msgid ""
msgstr ""
"Project-Id-Version: SABnzbd-4.6.0\n"
"Project-Id-Version: SABnzbd-4.2.2Beta1\n"
"PO-Revision-Date: 2020-06-27 15:56+0000\n"
"Last-Translator: Safihre <safihre@sabnzbd.org>, 2020\n"
"Language-Team: Chinese (China) (https://app.transifex.com/sabnzbd/teams/111101/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -17,7 +21,7 @@ msgid ""
"##\n"
"## Default Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -56,13 +60,54 @@ msgid ""
"Sorry!\n"
"<!--#end if#-->\n"
msgstr ""
"##\n"
"## SABnzbd 默认电子邮件模板\n"
"## 这是一款 Cheetah 模板\n"
"## 文档: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## 新行与空格均有重要意义!\n"
"##\n"
"## 这些是电子邮件头\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd 已<!--#if $status then \"完成\" else \"失败\" #-->任务 $name\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## 到主体部分时,必须要有空行!\n"
"\n"
"Hi,\n"
"<!--#if $status #-->\n"
"SABnzbd 已完成 \"$name\" 的下载 <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#else#-->\n"
"SABnzbd 下载 \"$name\" 失败 <!--#if $msgid==\"\" then \"\" else \"(newzbin #\" + $msgid + \")\"#-->\n"
"<!--#end if#-->\n"
"完成于 $end_time\n"
"已下载 $size\n"
"\n"
"任务结果:\n"
"<!--#for $stage in $stages #-->\n"
"Stage $stage <!--#slurp#-->\n"
"<!--#for $result in $stages[$stage]#-->\n"
" $result <!--#slurp#-->\n"
"<!--#end for#-->\n"
"<!--#end for#-->\n"
"<!--#if $script!=\"\" #-->\n"
"用户脚本 \"$script\" 输出内容 (退出代码 = $script_ret):\n"
"$script_output\n"
"<!--#end if#-->\n"
"<!--#if $status #-->\n"
"Enjoy!\n"
"<!--#else#-->\n"
"非常抱歉!\n"
"<!--#end if#-->\n"
#: email/rss.tmpl:1
msgid ""
"##\n"
"## RSS Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -85,13 +130,38 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## SABnzbd RSS 电子邮件模板\n"
"## 这是一款 Cheetah 模板\n"
"## 文档: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## 新行及空格均有重要意义!\n"
"##\n"
"## 这些是电子邮件头\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd 已将 $amount 项任务加入队列\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## 到主体部分时,必须要有空行!\n"
"\n"
"Hi,\n"
"\n"
"SABnzbd 已将 $amount 项任务加入队列。\n"
"它们出自 RSS feed \"$feed\"。\n"
"<!--#for $job in $jobs#-->\n"
" $job <!--#slurp#-->\n"
"<!--#end for#-->\n"
"\n"
"Bye\n"
#: email/badfetch.tmpl:1
msgid ""
"##\n"
"## Bad URL Fetch Email template for SABnzbd\n"
"## This a Cheetah template\n"
"## Documentation: https://sabnzbd.org/wiki/extra/email-templates\n"
"## Documentation: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## Newlines and whitespace are significant!\n"
"##\n"
@@ -111,3 +181,25 @@ msgid ""
"\n"
"Bye\n"
msgstr ""
"##\n"
"## SABnzbd 装取 URL 错误电子邮件模板\n"
"## 这是一款 Cheetah 模板\n"
"## 文档: http://sabnzbd.wikidot.com/email-templates\n"
"##\n"
"## 新行与空格均有重要意义!\n"
"##\n"
"## 这些是电子邮件头\n"
"To: $to\n"
"From: $from\n"
"Date: $date\n"
"Subject: SABnzbd 装取 NZB 失败\n"
"X-priority: 5\n"
"X-MS-priority: 5\n"
"## 到主体部分时必须要有空行!\n"
"\n"
"Hi,\n"
"\n"
"SABnzbd 从 $url 检索 NZB 失败。\n"
"错误信息为: $msg\n"
"\n"
"Bye\n"

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More