Merge branch 'development'

# Conflicts:
#	frontend/package-lock.json
#	frontend/package.json
This commit is contained in:
morpheus65535 2023-06-24 18:17:40 -04:00
commit 080710e7e1
399 changed files with 11476 additions and 5647 deletions

View File

@ -57,7 +57,6 @@ If you need something that is not already part of Bazarr, feel free to create a
- GreekSubtitles
- Hosszupuska
- LegendasDivx
- LegendasTV
- Karagarga.in
- Ktuvit (Get `hashed_password` using method described [here](https://github.com/XBMCil/service.subtitles.ktuvit))
- Napiprojekt

View File

@ -20,8 +20,8 @@ def check_python_version():
print("Python " + minimum_py3_str + " or greater required. "
"Current version is " + platform.python_version() + ". Please upgrade Python.")
sys.exit(1)
elif int(python_version[0]) == 3 and int(python_version[1]) > 10:
print("Python version greater than 3.10.x is unsupported. Current version is " + platform.python_version() +
elif int(python_version[0]) == 3 and int(python_version[1]) > 11:
print("Python version greater than 3.11.x is unsupported. Current version is " + platform.python_version() +
". Keep in mind that even if it works, you're on your own.")
elif (int(python_version[0]) == minimum_py3_tuple[0] and int(python_version[1]) < minimum_py3_tuple[1]) or \
(int(python_version[0]) != minimum_py3_tuple[0]):

View File

@ -4,7 +4,7 @@ from flask_restx import Resource, Namespace, reqparse
from operator import itemgetter
from app.database import TableHistory, TableHistoryMovie, TableSettingsLanguages
from languages.get_languages import alpha2_from_alpha3, language_from_alpha2
from languages.get_languages import alpha2_from_alpha3, language_from_alpha2, alpha3_from_alpha2
from ..utils import authenticate, False_Keys
@ -46,6 +46,7 @@ class Languages(Resource):
try:
languages_dicts.append({
'code2': code2,
'code3': alpha3_from_alpha2(code2),
'name': language_from_alpha2(code2),
# Compatibility: Use false temporarily
'enabled': False
@ -55,6 +56,7 @@ class Languages(Resource):
else:
languages_dicts = TableSettingsLanguages.select(TableSettingsLanguages.name,
TableSettingsLanguages.code2,
TableSettingsLanguages.code3,
TableSettingsLanguages.enabled)\
.order_by(TableSettingsLanguages.name).dicts()
languages_dicts = list(languages_dicts)

View File

@ -2,6 +2,7 @@
from flask import Flask, redirect
from flask_compress import Compress
from flask_cors import CORS
from flask_socketio import SocketIO
@ -15,6 +16,8 @@ socketio = SocketIO()
def create_app():
# Flask Setup
app = Flask(__name__)
app.config['COMPRESS_ALGORITHM'] = 'gzip'
Compress(app)
app.wsgi_app = ReverseProxied(app.wsgi_app)
app.config["SECRET_KEY"] = settings.general.flask_secret_key

View File

@ -52,6 +52,7 @@ defaults = {
'movie_default_enabled': 'False',
'movie_default_profile': '',
'page_size': '25',
'theme': 'auto',
'page_size_manual_search': '10',
'minimum_score_movie': '70',
'use_embedded_subs': 'True',
@ -83,7 +84,8 @@ defaults = {
'default_und_audio_lang': '',
'default_und_embedded_subtitles_lang': '',
'parse_embedded_audio_track': 'False',
'skip_hashing': 'False'
'skip_hashing': 'False',
'language_equals': '[]',
},
'auth': {
'type': 'None',
@ -168,7 +170,8 @@ defaults = {
'verify_ssl': 'True'
},
'subf2m': {
'verify_ssl': 'True'
'verify_ssl': 'True',
'user_agent': ''
},
'whisperai': {
'endpoint': 'http://127.0.0.1:9000',
@ -183,11 +186,6 @@ defaults = {
'email': '',
'hashed_password': ''
},
'legendastv': {
'username': '',
'password': '',
'featured_only': 'False'
},
'xsubs': {
'username': '',
'password': ''
@ -300,7 +298,8 @@ array_keys = ['excluded_tags',
'excluded_series_types',
'enabled_providers',
'path_mappings',
'path_mappings_movie']
'path_mappings_movie',
'language_equals']
str_keys = ['chmod']

View File

@ -20,6 +20,7 @@ from subliminal_patch.extensions import provider_registry
from app.get_args import args
from app.config import settings, get_array_from
from languages.get_languages import CustomLanguage
from app.event_handler import event_stream
from utilities.binaries import get_binary
from radarr.blacklist import blacklist_log_movie
@ -103,7 +104,7 @@ def provider_throttle_map():
}
PROVIDERS_FORCED_OFF = ["addic7ed", "tvsubtitles", "legendasdivx", "legendastv", "napiprojekt", "shooter",
PROVIDERS_FORCED_OFF = ["addic7ed", "tvsubtitles", "legendasdivx", "napiprojekt", "shooter",
"hosszupuska", "supersubtitles", "titlovi", "argenteam", "assrt", "subscene"]
throttle_count = {}
@ -115,6 +116,49 @@ def provider_pool():
return subliminal_patch.core.SZProviderPool
def _lang_from_str(content: str):
" Formats: es-MX en@hi es-MX@forced "
extra_info = content.split("@")
if len(extra_info) > 1:
kwargs = {extra_info[-1]: True}
else:
kwargs = {}
content = extra_info[0]
try:
code, country = content.split("-")
except ValueError:
lang = CustomLanguage.from_value(content)
if lang is not None:
lang = lang.subzero_language()
return lang.rebuild(lang, **kwargs)
code, country = content, None
return subliminal_patch.core.Language(code, country, **kwargs)
def get_language_equals(settings_=None):
settings_ = settings_ or settings
equals = get_array_from(settings_.general.language_equals)
if not equals:
return []
items = []
for equal in equals:
try:
from_, to_ = equal.split(":")
from_, to_ = _lang_from_str(from_), _lang_from_str(to_)
except Exception as error:
logging.info("Invalid equal value: '%s' [%s]", equal, error)
else:
items.append((from_, to_))
return items
def get_providers():
providers_list = []
existing_providers = provider_registry.names()
@ -202,13 +246,6 @@ def get_providers_auth():
'skip_wrong_fps'
),
},
'legendastv': {
'username': settings.legendastv.username,
'password': settings.legendastv.password,
'featured_only': settings.legendastv.getboolean(
'featured_only'
),
},
'xsubs': {
'username': settings.xsubs.username,
'password': settings.xsubs.password,
@ -250,11 +287,13 @@ def get_providers_auth():
'f_password': settings.karagarga.f_password,
},
'subf2m': {
'verify_ssl': settings.subf2m.getboolean('verify_ssl')
'verify_ssl': settings.subf2m.getboolean('verify_ssl'),
'user_agent': settings.subf2m.user_agent,
},
'whisperai': {
'endpoint': settings.whisperai.endpoint,
'timeout': settings.whisperai.timeout
'timeout': settings.whisperai.timeout,
'ffmpeg_path': _FFMPEG_BINARY,
}
}

View File

@ -24,9 +24,14 @@ ui_bp = Blueprint('ui', __name__,
'build', 'assets'),
static_url_path='/assets')
static_bp = Blueprint('images', __name__,
static_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
'frontend', 'build', 'images'), static_url_path='/images')
if os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'build',
'images')):
static_directory = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'build',
'images')
else:
static_directory = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'public',
'images')
static_bp = Blueprint('images', __name__, static_folder=static_directory, static_url_path='/images')
ui_bp.register_blueprint(static_bp)

View File

@ -18,6 +18,7 @@ class CustomLanguage:
official_alpha3 = "por"
name = "Brazilian Portuguese"
iso = "BR"
_scripts = []
_possible_matches = ("pt-br", "pob", "pb", "brazilian", "brasil", "brazil")
_extensions = (".pt-br", ".pob", ".pb")
_extensions_forced = (".pt-br.forced", ".pob.forced", ".pb.forced")
@ -86,6 +87,15 @@ class CustomLanguage:
return any(ext in name for ext in self._possible_matches)
def language_found(self, language: Language):
if str(language.country) == self.iso:
return True
if language.script and str(language.script) in self._scripts:
return True
return False
class BrazilianPortuguese(CustomLanguage):
# Same attributes as base class
@ -100,6 +110,9 @@ class ChineseTraditional(CustomLanguage):
official_alpha3 = "zho"
name = "Chinese Traditional"
iso = "TW"
# _scripts = (Script("Hant"),)
# We'll use literals for now
_scripts = ("Hant",)
_extensions = (
".cht",
".tc",
@ -211,6 +224,7 @@ class LatinAmericanSpanish(CustomLanguage):
official_alpha3 = "spa"
name = "Latin American Spanish"
iso = "MX" # Not fair, but ok
_scripts = ("419",)
_possible_matches = (
"es-la",
"spa-la",

View File

@ -7,7 +7,7 @@ import re
from guess_language import guess_language
from subliminal_patch import core
from subzero.language import Language
from charamel import Detector
from chardet import detect
from app.config import settings
from constants import hi_regex
@ -76,45 +76,34 @@ def guess_external_subtitles(dest_folder, subtitles, media_type, previously_inde
with open(subtitle_path, 'rb') as f:
text = f.read()
try:
text = text.decode('utf-8')
detected_language = guess_language(text)
# add simplified and traditional chinese detection
if detected_language == 'zh':
traditional_chinese_fuzzy = [u"", u"雙語"]
traditional_chinese = [".cht", ".tc", ".zh-tw", ".zht", ".zh-hant", ".zhhant", ".zh_hant",
".hant", ".big5", ".traditional"]
if str(os.path.splitext(subtitle)[0]).lower().endswith(tuple(traditional_chinese)) or (str(subtitle_path).lower())[:-5] in traditional_chinese_fuzzy:
detected_language == 'zt'
except UnicodeDecodeError:
detector = Detector()
encoding = detect(text)
if encoding and 'encoding' in encoding:
encoding = detect(text)['encoding']
else:
logging.debug("BAZARR skipping this subtitles because we can't guess the encoding. "
"It's probably a binary file: " + subtitle_path)
continue
text = text.decode(encoding)
detected_language = guess_language(text)
# add simplified and traditional chinese detection
if detected_language == 'zh':
traditional_chinese_fuzzy = [u"", u"雙語"]
traditional_chinese = [".cht", ".tc", ".zh-tw", ".zht", ".zh-hant", ".zhhant", ".zh_hant",
".hant", ".big5", ".traditional"]
if str(os.path.splitext(subtitle)[0]).lower().endswith(tuple(traditional_chinese)) or \
(str(subtitle_path).lower())[:-5] in traditional_chinese_fuzzy:
detected_language = 'zt'
if detected_language:
logging.debug("BAZARR external subtitles detected and guessed this language: " + str(
detected_language))
try:
guess = detector.detect(text)
subtitles[subtitle] = Language.rebuild(Language.fromietf(detected_language), forced=forced,
hi=False)
except Exception:
logging.debug("BAZARR skipping this subtitles because we can't guess the encoding. "
"It's probably a binary file: " + subtitle_path)
continue
else:
logging.debug('BAZARR detected encoding %r', guess)
try:
text = text.decode(guess)
except Exception:
logging.debug(
"BAZARR skipping this subtitles because we can't decode the file using the "
"guessed encoding. It's probably a binary file: " + subtitle_path)
continue
detected_language = guess_language(text)
except Exception:
logging.debug('BAZARR was unable to detect encoding for this subtitles file: %r', subtitle_path)
finally:
if detected_language:
logging.debug("BAZARR external subtitles detected and guessed this language: " + str(
detected_language))
try:
subtitles[subtitle] = Language.rebuild(Language.fromietf(detected_language), forced=forced,
hi=False)
except Exception:
pass
pass
# If language is still None (undetected), skip it
if hasattr(subtitles[subtitle], 'basename') and not subtitles[subtitle].basename:
@ -139,24 +128,14 @@ def guess_external_subtitles(dest_folder, subtitles, media_type, previously_inde
with open(subtitle_path, 'rb') as f:
text = f.read()
try:
text = text.decode('utf-8')
except UnicodeDecodeError:
detector = Detector()
try:
guess = detector.detect(text)
except Exception:
logging.debug("BAZARR skipping this subtitles because we can't guess the encoding. "
"It's probably a binary file: " + subtitle_path)
continue
else:
logging.debug('BAZARR detected encoding %r', guess)
try:
text = text.decode(guess)
except Exception:
logging.debug("BAZARR skipping this subtitles because we can't decode the file using the "
"guessed encoding. It's probably a binary file: " + subtitle_path)
continue
encoding = detect(text)
if encoding and 'encoding' in encoding:
encoding = detect(text)['encoding']
else:
logging.debug("BAZARR skipping this subtitles because we can't guess the encoding. "
"It's probably a binary file: " + subtitle_path)
continue
text = text.decode(encoding)
if bool(re.search(hi_regex, text)):
subtitles[subtitle] = Language.rebuild(subtitles[subtitle], forced=False, hi=True)

View File

@ -87,8 +87,7 @@ def manual_search(path, profile_id, providers, sceneName, title, media_type):
logging.debug(f"BAZARR Skipping {s}, because it doesn't match our series/episode")
except TypeError:
logging.debug("BAZARR Ignoring invalid subtitles")
finally:
continue
continue
initial_hi = None
initial_hi_match = False

View File

@ -8,7 +8,7 @@ from inspect import getfullargspec
from radarr.blacklist import get_blacklist_movie
from sonarr.blacklist import get_blacklist
from app.get_providers import get_providers, get_providers_auth, provider_throttle, provider_pool
from app.get_providers import get_providers, get_providers_auth, provider_throttle, provider_pool, get_language_equals
from .utils import get_ban_list
@ -19,10 +19,11 @@ def _init_pool(media_type, profile_id=None, providers=None):
return pool(
providers=providers or get_providers(),
provider_configs=get_providers_auth(),
blacklist=get_blacklist() if media_type == 'series' else get_blacklist_movie(),
blacklist=get_blacklist() if media_type == "series" else get_blacklist_movie(),
throttle_callback=provider_throttle,
ban_list=get_ban_list(profile_id),
language_hook=None,
language_equals=get_language_equals(),
)
@ -54,8 +55,19 @@ def _update_pool(media_type, profile_id=None):
return pool.update(
get_providers(),
get_providers_auth(),
get_blacklist() if media_type == 'series' else get_blacklist_movie(),
get_blacklist() if media_type == "series" else get_blacklist_movie(),
get_ban_list(profile_id),
get_language_equals(),
)
def _pool_update(pool, media_type, profile_id=None):
return pool.update(
get_providers(),
get_providers_auth(),
get_blacklist() if media_type == "series" else get_blacklist_movie(),
get_ban_list(profile_id),
get_language_equals(),
)

View File

@ -18,8 +18,8 @@ def translate_subtitles_file(video_path, source_srt_file, from_lang, to_lang, fo
sonarr_episode_id, radarr_id):
language_code_convert_dict = {
'he': 'iw',
'zt': 'zh-CN',
'zh': 'zh-TW',
'zh': 'zh-CN',
'zt': 'zh-TW',
}
to_lang = alpha3_from_alpha2(to_lang)

View File

@ -9,7 +9,7 @@ from functools import reduce
from app.config import settings
from app.database import get_exclusion_clause, get_audio_profile_languages, TableShows, TableEpisodes, TableMovies, \
TableHistory, TableHistoryMovie
TableHistory, TableHistoryMovie, get_profiles_list
from app.event_handler import show_progress, hide_progress
from app.get_providers import get_providers
from app.notifier import send_notifications, send_notifications_movie
@ -217,7 +217,7 @@ def get_upgradable_episode_subtitles():
if not upgradable_episodes:
return []
else:
upgradable_episodes = list(upgradable_episodes)
upgradable_episodes = [x for x in upgradable_episodes if _language_still_desired(x['language'], x['profileId'])]
logging.debug(f"{len(upgradable_episodes)} potentially upgradable episode subtitles have been found, let's "
f"filter them...")
@ -252,8 +252,32 @@ def get_upgradable_movies_subtitles():
if not upgradable_movies:
return []
else:
upgradable_movies = list(upgradable_movies)
upgradable_movies = [x for x in upgradable_movies if _language_still_desired(x['language'], x['profileId'])]
logging.debug(f"{len(upgradable_movies)} potentially upgradable movie subtitles have been found, let's filter "
f"them...")
return parse_upgradable_list(upgradable_list=upgradable_movies, perfect_score=117, media_type='movie')
def _language_still_desired(language, profile_id):
if not profile_id:
return False
profile = get_profiles_list(profile_id)
if profile and language in _language_from_items(profile['items']):
return True
else:
return False
def _language_from_items(items):
results = []
for item in items:
if item['forced'] == 'True':
results.append(item['language'] + ':forced')
elif item['hi'] == 'True':
results.append(item['language'] + ':hi')
else:
results.append(item['language'])
results.append(item['language'] + ':hi')
return results

View File

@ -4,7 +4,7 @@ import os
import logging
import hashlib
from charamel import Detector
from chardet import detect
from bs4 import UnicodeDammit
from app.config import settings
@ -64,8 +64,7 @@ def force_unicode(s):
try:
s = s.decode("utf-8")
except UnicodeDecodeError:
detector = Detector()
t = detector.detect(s)
t = detect(s)['encoding']
try:
s = s.decode(t)
except UnicodeDecodeError:

View File

@ -16,13 +16,19 @@ def _handle_alpha3(detected_language: dict):
alpha3 = detected_language["language"].alpha3
custom = CustomLanguage.from_value(alpha3, "official_alpha3")
if custom and custom.ffprobe_found(detected_language):
if not custom:
return alpha3
found = custom.language_found(detected_language["language"])
if not found:
found = custom.ffprobe_found(detected_language)
if found:
logging.debug("Custom embedded language found: %s", custom.name)
return custom.alpha3
return alpha3
def embedded_subs_reader(file, file_size, episode_file_id=None, movie_file_id=None, use_cache=True):
data = parse_video_metadata(file, file_size, episode_file_id, movie_file_id, use_cache=use_cache)
und_default_language = alpha3_from_alpha2(settings.general.default_und_embedded_subtitles_lang)
@ -33,7 +39,7 @@ def embedded_subs_reader(file, file_size, episode_file_id=None, movie_file_id=No
return subtitles_list
cache_provider = None
if data["ffprobe"] and "subtitle" in data["ffprobe"]:
if "ffprobe" in data and data["ffprobe"] and "subtitle" in data["ffprobe"]:
cache_provider = 'ffprobe'
elif 'mediainfo' in data and data["mediainfo"] and "subtitle" in data["mediainfo"]:
cache_provider = 'mediainfo'
@ -75,7 +81,7 @@ def embedded_audio_reader(file, file_size, episode_file_id=None, movie_file_id=N
return audio_list
cache_provider = None
if data["ffprobe"] and "audio" in data["ffprobe"]:
if "ffprobe" in data and data["ffprobe"] and "audio" in data["ffprobe"]:
cache_provider = 'ffprobe'
elif 'mediainfo' in data and data["mediainfo"] and "audio" in data["mediainfo"]:
cache_provider = 'mediainfo'
@ -86,7 +92,8 @@ def embedded_audio_reader(file, file_size, episode_file_id=None, movie_file_id=N
audio_list.append(None)
continue
language = language_from_alpha3(detected_language["language"].alpha3)
alpha3 = _handle_alpha3(detected_language)
language = language_from_alpha3(alpha3)
if language not in audio_list:
audio_list.append(language)

View File

@ -7,3 +7,4 @@ pytest-cov
pytest-vcr
pytest-mock
requests-mock
setuptools

File diff suppressed because it is too large Load Diff

View File

@ -13,17 +13,17 @@
},
"private": true,
"dependencies": {
"@mantine/core": "^5.6.0",
"@mantine/dropzone": "^5.6.0",
"@mantine/form": "^5.6.0",
"@mantine/hooks": "^5.6.0",
"@mantine/modals": "^5.6.0",
"@mantine/notifications": "^5.6.0",
"@mantine/core": "^6.0.0",
"@mantine/dropzone": "^6.0.0",
"@mantine/form": "^6.0.0",
"@mantine/hooks": "^6.0.0",
"@mantine/modals": "^6.0.0",
"@mantine/notifications": "^6.0.0",
"axios": "^0.27.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-query": "^3.39.2",
"react-router-dom": "~6.3.0",
"react-router-dom": "~6.10.0",
"socket.io-client": "^4.5.3"
},
"devDependencies": {
@ -34,36 +34,35 @@
"@fortawesome/free-solid-svg-icons": "^6.2.0",
"@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/lodash": "^4.14.0",
"@types/node": "^18.11.7",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/node": "^18.16.0",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/react-table": "^7.7.0",
"@vitejs/plugin-react": "^2.2.0",
"@vitest/coverage-c8": "^0.25.0",
"@vitest/ui": "^0.29.1",
"@vitejs/plugin-react": "^4.0.0",
"vitest": "^0.30.1",
"@vitest/coverage-c8": "^0.30.0",
"@vitest/ui": "^0.30.0",
"clsx": "^1.2.0",
"eslint": "^8.26.0",
"eslint": "^8.39.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-testing-library": "^5.9.0",
"husky": "^8.0.2",
"jsdom": "^20.0.1",
"jsdom": "^21.0.0",
"lodash": "^4.17.0",
"moment": "^2.29",
"prettier": "^2.7.0",
"prettier": "^2.8.0",
"prettier-plugin-organize-imports": "^3.1.0",
"pretty-quick": "^3.1.0",
"react-table": "^7.8.0",
"recharts": "~2.4.3",
"sass": "^1.55.0",
"typescript": "^4",
"vite": "^3.2.1",
"vite-plugin-checker": "^0.5.5",
"vitest": "^0.25.0"
"recharts": "~2.5.0",
"sass": "^1.62.0",
"typescript": "^5",
"vite": "^4.3.0",
"vite-plugin-checker": "^0.5.5"
},
"scripts": {
"start": "vite",

View File

@ -15,12 +15,12 @@ import {
Avatar,
Badge,
Burger,
createStyles,
Divider,
Group,
Header,
MediaQuery,
Menu,
createStyles,
} from "@mantine/core";
import { FunctionComponent } from "react";

View File

@ -357,9 +357,11 @@ const NavbarItem: FunctionComponent<NavbarItemProps> = ({
></FontAwesomeIcon>
)}
{name}
<Badge className={classes.badge} radius="xs" hidden={shouldHideBadge}>
{badge}
</Badge>
{shouldHideBadge === false && (
<Badge className={classes.badge} radius="xs">
{badge}
</Badge>
)}
</Text>
</NavLink>
);

View File

@ -1,11 +1,11 @@
import AppNavbar from "@/App/Navbar";
import { RouterNames } from "@/Router/RouterNames";
import ErrorBoundary from "@/components/ErrorBoundary";
import { Layout } from "@/constants";
import NavbarProvider from "@/contexts/Navbar";
import OnlineProvider from "@/contexts/Online";
import { notification } from "@/modules/task";
import CriticalError from "@/pages/errors/CriticalError";
import { RouterNames } from "@/Router/RouterNames";
import { Environment } from "@/utilities";
import { AppShell } from "@mantine/core";
import { useWindowEvent } from "@mantine/hooks";

View File

@ -1,3 +1,4 @@
import { useSystemSettings } from "@/apis/hooks";
import {
ColorScheme,
ColorSchemeProvider,
@ -6,7 +7,13 @@ import {
MantineThemeOverride,
} from "@mantine/core";
import { useColorScheme } from "@mantine/hooks";
import { FunctionComponent, useCallback, useEffect, useState } from "react";
import {
FunctionComponent,
PropsWithChildren,
useCallback,
useEffect,
useState,
} from "react";
const theme: MantineThemeOverride = {
fontFamily: "Roboto, open sans, Helvetica Neue, Helvetica, Arial, sans-serif",
@ -28,7 +35,19 @@ const theme: MantineThemeOverride = {
};
function useAutoColorScheme() {
const preferredColorScheme = useColorScheme();
const settings = useSystemSettings();
const settingsColorScheme = settings.data?.general.theme;
let preferredColorScheme: ColorScheme = useColorScheme();
switch (settingsColorScheme) {
case "light":
preferredColorScheme = "light" as ColorScheme;
break;
case "dark":
preferredColorScheme = "dark" as ColorScheme;
break;
}
const [colorScheme, setColorScheme] = useState(preferredColorScheme);
// automatically switch dark/light theme
@ -45,7 +64,7 @@ function useAutoColorScheme() {
const emotionCache = createEmotionCache({ key: "bazarr" });
const ThemeProvider: FunctionComponent = ({ children }) => {
const ThemeProvider: FunctionComponent<PropsWithChildren> = ({ children }) => {
const { colorScheme, toggleColorScheme } = useAutoColorScheme();
return (

View File

@ -1,12 +1,11 @@
import App from "@/App";
import { useBadges } from "@/apis/hooks";
import { useEnabledStatus } from "@/apis/hooks/site";
import App from "@/App";
import { Lazy } from "@/components/async";
import Authentication from "@/pages/Authentication";
import BlacklistMoviesView from "@/pages/Blacklist/Movies";
import BlacklistSeriesView from "@/pages/Blacklist/Series";
import Episodes from "@/pages/Episodes";
import NotFound from "@/pages/errors/NotFound";
import MoviesHistoryView from "@/pages/History/Movies";
import SeriesHistoryView from "@/pages/History/Series";
import MovieView from "@/pages/Movies";
@ -31,6 +30,7 @@ import SystemReleasesView from "@/pages/System/Releases";
import SystemTasksView from "@/pages/System/Tasks";
import WantedMoviesView from "@/pages/Wanted/Movies";
import WantedSeriesView from "@/pages/Wanted/Series";
import NotFound from "@/pages/errors/NotFound";
import { Environment } from "@/utilities";
import {
faClock,
@ -42,13 +42,13 @@ import {
faPlay,
} from "@fortawesome/free-solid-svg-icons";
import {
createContext,
FunctionComponent,
createContext,
lazy,
useContext,
useMemo,
} from "react";
import { BrowserRouter } from "react-router-dom";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import Redirector from "./Redirector";
import { RouterNames } from "./RouterNames";
import { CustomRouteObject } from "./type";
@ -315,12 +315,18 @@ function useRoutes(): CustomRouteObject[] {
const RouterItemContext = createContext<CustomRouteObject[]>([]);
export const Router: FunctionComponent = ({ children }) => {
export const Router: FunctionComponent = () => {
const routes = useRoutes();
// TODO: Move this outside the function component scope
const router = useMemo(
() => createBrowserRouter(routes, { basename: Environment.baseUrl }),
[routes]
);
return (
<RouterItemContext.Provider value={routes}>
<BrowserRouter basename={Environment.baseUrl}>{children}</BrowserRouter>
<RouterProvider router={router}></RouterProvider>
</RouterItemContext.Provider>
);
};

View File

@ -3,9 +3,9 @@ import { usePageSize } from "@/utilities/storage";
import { useCallback, useEffect, useState } from "react";
import {
QueryKey,
UseQueryResult,
useQuery,
useQueryClient,
UseQueryResult,
} from "react-query";
import { QueryKeys } from "./keys";

View File

@ -1,11 +1,11 @@
import UIError from "@/pages/errors/UIError";
import { Component } from "react";
import { Component, PropsWithChildren } from "react";
interface State {
error: Error | null;
}
class ErrorBoundary extends Component<object, State> {
class ErrorBoundary extends Component<PropsWithChildren, State> {
constructor(props: object) {
super(props);
this.state = { error: null };

View File

@ -1,7 +1,7 @@
import { LoadingOverlay } from "@mantine/core";
import { FunctionComponent, Suspense } from "react";
import { FunctionComponent, PropsWithChildren, Suspense } from "react";
const Lazy: FunctionComponent = ({ children }) => {
const Lazy: FunctionComponent<PropsWithChildren> = ({ children }) => {
return <Suspense fallback={<LoadingOverlay visible />}>{children}</Suspense>;
};

View File

@ -0,0 +1,34 @@
import { useLanguages } from "@/apis/hooks";
import { Selector, SelectorProps } from "@/components/inputs";
import { useSelectorOptions } from "@/utilities";
import { FunctionComponent, useMemo } from "react";
interface LanguageSelectorProps
extends Omit<SelectorProps<Language.Server>, "options" | "getkey"> {
enabled?: boolean;
}
const LanguageSelector: FunctionComponent<LanguageSelectorProps> = ({
enabled = false,
...selector
}) => {
const { data } = useLanguages();
const filteredData = useMemo(() => {
if (enabled) {
return data?.filter((value) => value.enabled);
} else {
return data;
}
}, [data, enabled]);
const options = useSelectorOptions(
filteredData ?? [],
(value) => value.name,
(value) => value.code3
);
return <Selector {...options} searchable {...selector}></Selector>;
};
export default LanguageSelector;

View File

@ -1,6 +1,6 @@
import { useMovieSubtitleModification } from "@/apis/hooks";
import { useModals, withModal } from "@/modules/modals";
import { task, TaskGroup } from "@/modules/task";
import { TaskGroup, task } from "@/modules/task";
import { useTableStyles } from "@/styles";
import { useArrayAction, useSelectorOptions } from "@/utilities";
import FormUtils from "@/utilities/form";
@ -28,9 +28,9 @@ import { useForm } from "@mantine/form";
import { isString } from "lodash";
import { FunctionComponent, useEffect, useMemo } from "react";
import { Column } from "react-table";
import TextPopover from "../TextPopover";
import { Action, Selector } from "../inputs";
import { SimpleTable } from "../tables";
import TextPopover from "../TextPopover";
type SubtitleFile = {
file: File;

View File

@ -1,4 +1,4 @@
export * from "./inputs";
export { default as Search } from "./Search";
export * from "./inputs";
export * from "./tables";
export { default as Toolbox } from "./toolbox";

View File

@ -4,7 +4,7 @@ import {
faXmark,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createStyles, Group, Stack, Text } from "@mantine/core";
import { Group, Stack, Text, createStyles } from "@mantine/core";
import { Dropzone } from "@mantine/dropzone";
import { FunctionComponent } from "react";

View File

@ -83,6 +83,9 @@ export const FileBrowser: FunctionComponent<FileBrowserProps> = ({
placeholder="Click to start"
data={data}
value={value}
// Temporary solution of infinite dropdown items, fix later
limit={NaN}
maxDropdownHeight={240}
filter={(value, item) => {
if (item.value === backKey) {
return true;

View File

@ -93,6 +93,7 @@ export function Selector<T>({
return (
<Select
withinPortal={true}
data={data}
defaultValue={wrappedDefaultValue}
value={wrappedValue}

View File

@ -12,11 +12,11 @@ import { Badge, Center, Text } from "@mantine/core";
import { FunctionComponent, useMemo } from "react";
import { Column } from "react-table";
import { PageTable } from "..";
import TextPopover from "../TextPopover";
import MutateAction from "../async/MutateAction";
import QueryOverlay from "../async/QueryOverlay";
import { HistoryIcon } from "../bazarr";
import Language from "../bazarr/Language";
import TextPopover from "../TextPopover";
interface MovieHistoryViewProps {
movie: Item.Movie;

View File

@ -105,7 +105,7 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) {
</Anchor>
);
} else {
return value;
return <Text>{value}</Text>;
}
},
},
@ -220,12 +220,12 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) {
export const MovieSearchModal = withModal<Props<Item.Movie>>(
ManualSearchView,
"movie-manual-search",
{ title: "Search Subtitles", size: "xl" }
{ title: "Search Subtitles", size: "calc(100vw - 4rem)" }
);
export const EpisodeSearchModal = withModal<Props<Item.Episode>>(
ManualSearchView,
"episode-manual-search",
{ title: "Search Subtitles", size: "xl" }
{ title: "Search Subtitles", size: "calc(100vw - 4rem)" }
);
const StateIcon: FunctionComponent<{ matches: string[]; dont: string[] }> = ({
@ -237,7 +237,7 @@ const StateIcon: FunctionComponent<{ matches: string[]; dont: string[] }> = ({
const { ref, hovered } = useHover();
return (
<Popover opened={hovered} position="top" width={360} withArrow>
<Popover opened={hovered} position="top" width={360} withArrow withinPortal>
<Popover.Target>
<Text color={hasIssues ? "yellow" : "green"} ref={ref}>
<FontAwesomeIcon

View File

@ -4,7 +4,7 @@ import { SimpleTable } from "@/components/tables";
import { useCustomSelection } from "@/components/tables/plugins";
import { withModal } from "@/modules/modals";
import { isMovie } from "@/utilities";
import { Badge, Button, Divider, Group, Stack } from "@mantine/core";
import { Badge, Button, Divider, Group, Stack, Text } from "@mantine/core";
import { FunctionComponent, useMemo, useState } from "react";
import { Column, useRowSelect } from "react-table";
@ -60,9 +60,9 @@ const SubtitleToolView: FunctionComponent<SubtitleToolViewProps> = ({
}
if (idx !== -1) {
return path.slice(idx + 1);
return <Text>{path.slice(idx + 1)}</Text>;
} else {
return path;
return <Text>{path}</Text>;
}
},
},

View File

@ -30,7 +30,7 @@ const PageControl: FunctionComponent<Props> = ({
<Pagination
size="sm"
color={isLoading ? "gray" : "primary"}
page={index + 1}
value={index + 1}
onChange={(page) => goto(page - 1)}
hidden={count <= 1}
total={count}

View File

@ -3,8 +3,8 @@ import { useEffect } from "react";
import { usePagination, useTable } from "react-table";
import BaseTable from "./BaseTable";
import PageControl from "./PageControl";
import { useDefaultSettings } from "./plugins";
import { SimpleTableProps } from "./SimpleTable";
import { useDefaultSettings } from "./plugins";
type Props<T extends object> = SimpleTableProps<T> & {
autoScroll?: boolean;

View File

@ -4,12 +4,12 @@ import {
CellProps,
Column,
ColumnInstance,
ensurePluginOrder,
HeaderProps,
Hooks,
MetaBase,
TableInstance,
TableToggleCommonProps,
ensurePluginOrder,
} from "react-table";
const pluginName = "useCustomSelection";

View File

@ -1,5 +1,5 @@
import { createStyles, Group } from "@mantine/core";
import { FunctionComponent } from "react";
import { FunctionComponent, PropsWithChildren } from "react";
import ToolboxButton, { ToolboxMutateButton } from "./Button";
const useStyles = createStyles((theme) => ({
@ -11,7 +11,7 @@ const useStyles = createStyles((theme) => ({
},
}));
declare type ToolboxComp = FunctionComponent & {
declare type ToolboxComp = FunctionComponent<PropsWithChildren> & {
Button: typeof ToolboxButton;
MutateButton: typeof ToolboxMutateButton;
};

View File

@ -1,20 +1,19 @@
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { useRoutes } from "react-router-dom";
import { createRoot } from "react-dom/client";
import { Router } from "./Router";
import { AllProviders } from "./providers";
import { useRouteItems } from "./Router";
const RouteApp = () => {
const items = useRouteItems();
const container = document.getElementById("root");
return useRoutes(items);
};
ReactDOM.render(
<StrictMode>
<AllProviders>
<RouteApp />
</AllProviders>
</StrictMode>,
document.getElementById("root")
);
if (container === null) {
Error("Cannot initialize app, root not found");
} else {
const root = createRoot(container);
root.render(
<StrictMode>
<AllProviders>
<Router />
</AllProviders>
</StrictMode>
);
}

View File

@ -2,19 +2,19 @@ import {
ModalsProvider as MantineModalsProvider,
ModalsProviderProps as MantineModalsProviderProps,
} from "@mantine/modals";
import { FunctionComponent, useMemo } from "react";
import { FunctionComponent, PropsWithChildren, useMemo } from "react";
import { ModalComponent, StaticModals } from "./WithModal";
const DefaultModalProps: MantineModalsProviderProps["modalProps"] = {
centered: true,
styles: {
modal: {
maxWidth: "100%",
},
// modals: {
// maxWidth: "100%",
// },
},
};
const ModalsProvider: FunctionComponent = ({ children }) => {
const ModalsProvider: FunctionComponent<PropsWithChildren> = ({ children }) => {
const modals = useMemo(
() =>
StaticModals.reduce<Record<string, ModalComponent>>((prev, curr) => {

View File

@ -5,8 +5,11 @@ import { useCallback, useContext, useMemo } from "react";
import { ModalComponent, ModalIdContext } from "./WithModal";
export function useModals() {
const { openContextModal: openMantineContextModal, ...rest } =
useMantineModals();
const {
openContextModal: openMantineContextModal,
closeContextModal: closeContextModalRaw,
...rest
} = useMantineModals();
const openContextModal = useCallback(
<ARGS extends {}>(

View File

@ -1,3 +1,3 @@
export * from "./hooks";
export { default as ModalsProvider } from "./ModalsProvider";
export { default as withModal } from "./WithModal";
export * from "./hooks";

View File

@ -1,6 +1,6 @@
import { debounce, forIn, remove, uniq } from "lodash";
import { onlineManager } from "react-query";
import { io, Socket } from "socket.io-client";
import { Socket, io } from "socket.io-client";
import { Environment, isDevEnv, isTestEnv } from "../../utilities";
import { ENSURE, GROUP, LOG } from "../../utilities/console";
import { createDefaultReducer } from "./reducer";

View File

@ -1,3 +1,4 @@
import { RouterNames } from "@/Router/RouterNames";
import {
useEpisodesBySeriesId,
useIsAnyActionRunning,
@ -11,9 +12,8 @@ import { ItemEditModal } from "@/components/forms/ItemEditForm";
import { SeriesUploadModal } from "@/components/forms/SeriesUploadForm";
import { SubtitleToolsModal } from "@/components/modals";
import { useModals } from "@/modules/modals";
import { notification, task, TaskGroup } from "@/modules/task";
import { TaskGroup, notification, task } from "@/modules/task";
import ItemOverview from "@/pages/views/ItemOverview";
import { RouterNames } from "@/Router/RouterNames";
import { useLanguageProfileBy } from "@/utilities/languages";
import {
faAdjust,

View File

@ -1,10 +1,10 @@
import { useDownloadEpisodeSubtitles, useEpisodesProvider } from "@/apis/hooks";
import { useShowOnlyDesired } from "@/apis/hooks/site";
import { Action, GroupTable } from "@/components";
import TextPopover from "@/components/TextPopover";
import { AudioList } from "@/components/bazarr";
import { EpisodeHistoryModal } from "@/components/modals";
import { EpisodeSearchModal } from "@/components/modals/ManualSearchModal";
import TextPopover from "@/components/TextPopover";
import { useModals } from "@/modules/modals";
import { useTableStyles } from "@/styles";
import { BuildKey, filterSubtitleBy } from "@/utilities";
@ -84,7 +84,7 @@ const Table: FunctionComponent<Props> = ({ episodes, profile, disabled }) => {
{
accessor: "season",
Cell: (row) => {
return `Season ${row.value}`;
return <Text>Season {row.value}</Text>;
},
},
{

View File

@ -11,8 +11,8 @@ import { useSelectorOptions } from "@/utilities";
import {
Box,
Container,
createStyles,
SimpleGrid,
createStyles,
useMantineTheme,
} from "@mantine/core";
import { useDocumentTitle } from "@mantine/hooks";

View File

@ -1,3 +1,4 @@
import { RouterNames } from "@/Router/RouterNames";
import {
useDownloadMovieSubtitles,
useIsMovieActionRunning,
@ -15,9 +16,8 @@ import { MovieUploadModal } from "@/components/forms/MovieUploadForm";
import { MovieHistoryModal, SubtitleToolsModal } from "@/components/modals";
import { MovieSearchModal } from "@/components/modals/ManualSearchModal";
import { useModals } from "@/modules/modals";
import { notification, task, TaskGroup } from "@/modules/task";
import { TaskGroup, notification, task } from "@/modules/task";
import ItemOverview from "@/pages/views/ItemOverview";
import { RouterNames } from "@/Router/RouterNames";
import { useLanguageProfileBy } from "@/utilities/languages";
import {
faCloudUploadAlt,

View File

@ -65,15 +65,19 @@ const MovieView: FunctionComponent = () => {
accessor: "missing_subtitles",
Cell: (row) => {
const missing = row.value;
return missing.map((v) => (
<Badge
mr="xs"
color="yellow"
key={BuildKey(v.code2, v.hi, v.forced)}
>
<Language.Text value={v}></Language.Text>
</Badge>
));
return (
<>
{missing.map((v) => (
<Badge
mr="xs"
color="yellow"
key={BuildKey(v.code2, v.hi, v.forced)}
>
<Language.Text value={v}></Language.Text>
</Badge>
))}
</>
);
},
},
{

View File

@ -0,0 +1,196 @@
import {
decodeEqualData,
encodeEqualData,
LanguageEqualData,
LanguageEqualImmediateData,
} from "@/pages/Settings/Languages/equals";
import { describe, expect, it } from "vitest";
describe("Equals Parser", () => {
it("should parse from string correctly", () => {
interface TestData {
text: string;
expected: LanguageEqualImmediateData;
}
function testParsedResult(
text: string,
expected: LanguageEqualImmediateData
) {
const result = decodeEqualData(text);
if (result === undefined) {
expect(false, `Cannot parse '${text}' as language equal data`);
return;
}
expect(
result,
`${text} does not match with the expected equal data`
).toStrictEqual(expected);
}
const testValues: TestData[] = [
{
text: "spa-MX:spa",
expected: {
source: {
content: "spa-MX",
hi: false,
forced: false,
},
target: {
content: "spa",
hi: false,
forced: false,
},
},
},
{
text: "zho@hi:zht",
expected: {
source: {
content: "zho",
hi: true,
forced: false,
},
target: {
content: "zht",
hi: false,
forced: false,
},
},
},
{
text: "es-MX@forced:es-MX",
expected: {
source: {
content: "es-MX",
hi: false,
forced: true,
},
target: {
content: "es-MX",
hi: false,
forced: false,
},
},
},
{
text: "en:en@hi",
expected: {
source: {
content: "en",
hi: false,
forced: false,
},
target: {
content: "en",
hi: true,
forced: false,
},
},
},
];
testValues.forEach((data) => {
testParsedResult(data.text, data.expected);
});
});
it("should encode to string correctly", () => {
interface TestData {
source: LanguageEqualData;
expected: string;
}
const testValues: TestData[] = [
{
source: {
source: {
content: {
name: "Abkhazian",
code2: "ab",
code3: "abk",
enabled: false,
},
hi: false,
forced: false,
},
target: {
content: {
name: "Aragonese",
code2: "an",
code3: "arg",
enabled: false,
},
hi: false,
forced: false,
},
},
expected: "abk:arg",
},
{
source: {
source: {
content: {
name: "Abkhazian",
code2: "ab",
code3: "abk",
enabled: false,
},
hi: true,
forced: false,
},
target: {
content: {
name: "Aragonese",
code2: "an",
code3: "arg",
enabled: false,
},
hi: false,
forced: false,
},
},
expected: "abk@hi:arg",
},
{
source: {
source: {
content: {
name: "Abkhazian",
code2: "ab",
code3: "abk",
enabled: false,
},
hi: false,
forced: true,
},
target: {
content: {
name: "Aragonese",
code2: "an",
code3: "arg",
enabled: false,
},
hi: false,
forced: false,
},
},
expected: "abk@forced:arg",
},
];
function testEncodeResult({ source, expected }: TestData) {
const encoded = encodeEqualData(source);
expect(
encoded,
`Encoded result '${encoded}' is not matched to '${expected}'`
).toEqual(expected);
}
testValues.forEach(testEncodeResult);
});
});

View File

@ -0,0 +1,365 @@
import { useLanguages } from "@/apis/hooks";
import { Action, SimpleTable } from "@/components";
import LanguageSelector from "@/components/bazarr/LanguageSelector";
import { languageEqualsKey } from "@/pages/Settings/keys";
import { useFormActions } from "@/pages/Settings/utilities/FormValues";
import { useSettingValue } from "@/pages/Settings/utilities/hooks";
import { LOG } from "@/utilities/console";
import { faEquals, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Button, Checkbox } from "@mantine/core";
import { FunctionComponent, useCallback, useMemo } from "react";
import { Column } from "react-table";
interface GenericEqualTarget<T> {
content: T;
hi: boolean;
forced: boolean;
}
interface LanguageEqualGenericData<T> {
source: GenericEqualTarget<T>;
target: GenericEqualTarget<T>;
}
export type LanguageEqualImmediateData =
LanguageEqualGenericData<Language.CodeType>;
export type LanguageEqualData = LanguageEqualGenericData<Language.Server>;
function decodeEqualTarget(
text: string
): GenericEqualTarget<Language.CodeType> | undefined {
const [code, decoration] = text.split("@");
if (code.length === 0) {
return undefined;
}
const forced = decoration === "forced";
const hi = decoration === "hi";
return {
content: code,
forced,
hi,
};
}
export function decodeEqualData(
text: string
): LanguageEqualImmediateData | undefined {
const [first, second] = text.split(":");
const source = decodeEqualTarget(first);
const target = decodeEqualTarget(second);
if (source === undefined || target === undefined) {
return undefined;
}
return {
source,
target,
};
}
function encodeEqualTarget(data: GenericEqualTarget<Language.Server>): string {
let text = data.content.code3;
if (data.hi) {
text += "@hi";
} else if (data.forced) {
text += "@forced";
}
return text;
}
export function encodeEqualData(data: LanguageEqualData): string {
const source = encodeEqualTarget(data.source);
const target = encodeEqualTarget(data.target);
return `${source}:${target}`;
}
export function useLatestLanguageEquals(): LanguageEqualData[] {
const { data } = useLanguages();
const latest = useSettingValue<string[]>(languageEqualsKey);
return useMemo(
() =>
latest
?.map(decodeEqualData)
.map((parsed) => {
if (parsed === undefined) {
return undefined;
}
const source = data?.find(
(value) => value.code3 === parsed.source.content
);
const target = data?.find(
(value) => value.code3 === parsed.target.content
);
if (source === undefined || target === undefined) {
return undefined;
}
return {
source: { ...parsed.source, content: source },
target: { ...parsed.target, content: target },
};
})
.filter((v): v is LanguageEqualData => v !== undefined) ?? [],
[data, latest]
);
}
interface EqualsTableProps {}
const EqualsTable: FunctionComponent<EqualsTableProps> = () => {
const { data: languages } = useLanguages();
const canAdd = languages !== undefined;
const equals = useLatestLanguageEquals();
const { setValue } = useFormActions();
const setEquals = useCallback(
(values: LanguageEqualData[]) => {
const encodedValues = values.map(encodeEqualData);
LOG("info", "updating language equals data", values);
setValue(encodedValues, languageEqualsKey);
},
[setValue]
);
const add = useCallback(() => {
if (languages === undefined) {
return;
}
const enabled = languages.find((value) => value.enabled);
if (enabled === undefined) {
return;
}
const newValue: LanguageEqualData[] = [
...equals,
{
source: {
content: enabled,
hi: false,
forced: false,
},
target: {
content: enabled,
hi: false,
forced: false,
},
},
];
setEquals(newValue);
}, [equals, languages, setEquals]);
const update = useCallback(
(index: number, value: LanguageEqualData) => {
if (index < 0 || index >= equals.length) {
return;
}
const newValue: LanguageEqualData[] = [...equals];
newValue[index] = { ...value };
setEquals(newValue);
},
[equals, setEquals]
);
const remove = useCallback(
(index: number) => {
if (index < 0 || index >= equals.length) {
return;
}
const newValue: LanguageEqualData[] = [...equals];
newValue.splice(index, 1);
setEquals(newValue);
},
[equals, setEquals]
);
const columns = useMemo<Column<LanguageEqualData>[]>(
() => [
{
Header: "Source",
id: "source-lang",
accessor: "source",
Cell: ({ value: { content }, row }) => {
return (
<LanguageSelector
enabled
value={content}
onChange={(result) => {
if (result !== null) {
update(row.index, {
...row.original,
source: { ...row.original.source, content: result },
});
}
}}
></LanguageSelector>
);
},
},
{
id: "source-hi",
accessor: "source",
Cell: ({ value: { hi }, row }) => {
return (
<Checkbox
label="HI"
checked={hi}
onChange={({ currentTarget: { checked } }) => {
update(row.index, {
...row.original,
source: {
...row.original.source,
hi: checked,
forced: checked ? false : row.original.source.forced,
},
});
}}
></Checkbox>
);
},
},
{
id: "source-forced",
accessor: "source",
Cell: ({ value: { forced }, row }) => {
return (
<Checkbox
label="Forced"
checked={forced}
onChange={({ currentTarget: { checked } }) => {
update(row.index, {
...row.original,
source: {
...row.original.source,
forced: checked,
hi: checked ? false : row.original.source.hi,
},
});
}}
></Checkbox>
);
},
},
{
id: "equal-icon",
Cell: () => {
return <FontAwesomeIcon icon={faEquals} />;
},
},
{
Header: "Target",
id: "target-lang",
accessor: "target",
Cell: ({ value: { content }, row }) => {
return (
<LanguageSelector
enabled
value={content}
onChange={(result) => {
if (result !== null) {
update(row.index, {
...row.original,
target: { ...row.original.target, content: result },
});
}
}}
></LanguageSelector>
);
},
},
{
id: "target-hi",
accessor: "target",
Cell: ({ value: { hi }, row }) => {
return (
<Checkbox
label="HI"
checked={hi}
onChange={({ currentTarget: { checked } }) => {
update(row.index, {
...row.original,
target: {
...row.original.target,
hi: checked,
forced: checked ? false : row.original.target.forced,
},
});
}}
></Checkbox>
);
},
},
{
id: "target-forced",
accessor: "target",
Cell: ({ value: { forced }, row }) => {
return (
<Checkbox
label="Forced"
checked={forced}
onChange={({ currentTarget: { checked } }) => {
update(row.index, {
...row.original,
target: {
...row.original.target,
forced: checked,
hi: checked ? false : row.original.target.hi,
},
});
}}
></Checkbox>
);
},
},
{
id: "action",
accessor: "target",
Cell: ({ row }) => {
return (
<Action
label="Remove"
icon={faTrash}
color="red"
onClick={() => remove(row.index)}
></Action>
);
},
},
],
[remove, update]
);
return (
<>
<SimpleTable data={equals} columns={columns}></SimpleTable>
<Button fullWidth disabled={!canAdd} color="light" onClick={add}>
{canAdd ? "Add Equal" : "No Enabled Languages"}
</Button>
</>
);
};
export default EqualsTable;

View File

@ -17,6 +17,7 @@ import {
} from "../keys";
import { useSettingValue } from "../utilities/hooks";
import { LanguageSelector, ProfileSelector } from "./components";
import EqualsTable from "./equals";
import Table from "./table";
export function useLatestEnabledLanguages() {
@ -69,6 +70,13 @@ const SettingsLanguagesView: FunctionComponent = () => {
></LanguageSelector>
</Section>
<Section header="Language Equals">
<Message>
Treat the following languages as equal across all providers.
</Message>
<EqualsTable></EqualsTable>
</Section>
<Section header="Embedded Tracks Language">
<Check
label="Deep analyze media file to get audio tracks language."
@ -91,7 +99,6 @@ const SettingsLanguagesView: FunctionComponent = () => {
}}
></Selector>
</CollapseBox>
<Selector
clearable
settingKey={defaultUndEmbeddedSubtitlesLang}

View File

@ -1,7 +1,7 @@
import { Action, SimpleTable } from "@/components";
import {
anyCutoff,
ProfileEditModal,
anyCutoff,
} from "@/components/forms/ProfileEditForm";
import { useModals } from "@/modules/modals";
import { BuildKey, useArrayAction } from "@/utilities";
@ -87,15 +87,19 @@ const Table: FunctionComponent = () => {
Cell: (row) => {
const items = row.value;
if (!items) {
return false;
return null;
}
return items.map((v, idx) => {
return (
<Badge key={BuildKey(idx, v)} color="gray">
{v}
</Badge>
);
});
return (
<>
{items.map((v, idx) => {
return (
<Badge key={BuildKey(idx, v)} color="gray">
{v}
</Badge>
);
})}
</>
);
},
},
{
@ -104,15 +108,19 @@ const Table: FunctionComponent = () => {
Cell: (row) => {
const items = row.value;
if (!items) {
return false;
return null;
}
return items.map((v, idx) => {
return (
<Badge key={BuildKey(idx, v)} color="gray">
{v}
</Badge>
);
});
return (
<>
{items.map((v, idx) => {
return (
<Badge key={BuildKey(idx, v)} color="gray">
{v}
</Badge>
);
})}
</>
);
},
},
{

View File

@ -123,6 +123,7 @@ export const NotificationView: FunctionComponent = () => {
const update = useUpdateArray<Settings.NotificationInfo>(
notificationsKey,
notifications ?? [],
"name"
);

View File

@ -6,15 +6,15 @@ import {
Button,
Divider,
Group,
Text as MantineText,
SimpleGrid,
Stack,
Text as MantineText,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { capitalize } from "lodash";
import {
forwardRef,
FunctionComponent,
forwardRef,
useCallback,
useMemo,
useRef,
@ -28,8 +28,8 @@ import {
useFormActions,
useStagedValues,
} from "../utilities/FormValues";
import { useSettingValue } from "../utilities/hooks";
import { SettingsProvider, useSettings } from "../utilities/SettingsProvider";
import { useSettingValue } from "../utilities/hooks";
import { ProviderInfo, ProviderList } from "./list";
const ProviderKey = "settings-general-enabled_providers";

View File

@ -185,26 +185,6 @@ export const ProviderList: Readonly<ProviderInfo[]> = [
},
],
},
{
key: "legendastv",
name: "LegendasTV",
description: "Brazilian / Portuguese Subtitles Provider",
inputs: [
{
type: "text",
key: "username",
},
{
type: "password",
key: "password",
},
{
type: "switch",
key: "featured_only",
name: "Only Download Featured",
},
],
},
{ key: "napiprojekt", description: "Polish Subtitles Provider" },
{
key: "whisperai",
@ -329,7 +309,13 @@ export const ProviderList: Readonly<ProviderInfo[]> = [
name: "Verify SSL",
defaultValue: true,
},
{
type: "text",
key: "user_agent",
name: "User-agent header",
},
],
message: "Make sure to use a unique and credible user agent.",
},
{
key: "subs4free",

View File

@ -1,12 +1,12 @@
import { uiPageSizeKey } from "@/utilities/storage";
import { FunctionComponent } from "react";
import { Layout, Section, Selector } from "../components";
import { pageSizeOptions } from "./options";
import { colorSchemeOptions, pageSizeOptions } from "./options";
const SettingsUIView: FunctionComponent = () => {
return (
<Layout name="Interface">
<Section header="UI">
<Section header="List View">
<Selector
label="Page Size"
options={pageSizeOptions}
@ -14,6 +14,14 @@ const SettingsUIView: FunctionComponent = () => {
defaultValue={50}
></Selector>
</Section>
<Section header="Style">
<Selector
label="Theme"
options={colorSchemeOptions}
settingKey="settings-general-theme"
defaultValue={"auto"}
></Selector>
</Section>
</Layout>
);
};

View File

@ -26,3 +26,18 @@ export const pageSizeOptions: SelectorOption<number>[] = [
value: 1000,
},
];
export const colorSchemeOptions: SelectorOption<string>[] = [
{
label: "Auto",
value: "auto",
},
{
label: "Light",
value: "light",
},
{
label: "Dark",
value: "dark",
},
];

View File

@ -1,9 +1,16 @@
import { Text } from "@mantine/core";
import { FunctionComponent } from "react";
import { FunctionComponent, PropsWithChildren } from "react";
export const Message: FunctionComponent<{
interface MessageProps {
type?: "warning" | "info";
}> = ({ type = "info", children }) => {
}
type Props = PropsWithChildren<MessageProps>;
export const Message: FunctionComponent<Props> = ({
type = "info",
children,
}) => {
return (
<Text size="sm" color={type === "info" ? "dimmed" : "yellow"} my={0}>
{children}

View File

@ -1,12 +1,14 @@
import { Divider, Stack, Title } from "@mantine/core";
import { FunctionComponent } from "react";
import { FunctionComponent, PropsWithChildren } from "react";
interface SectionProps {
header: string;
hidden?: boolean;
}
export const Section: FunctionComponent<SectionProps> = ({
type Props = PropsWithChildren<SectionProps>;
export const Section: FunctionComponent<Props> = ({
header,
hidden,
children,

View File

@ -1,5 +1,5 @@
import { Collapse, Stack } from "@mantine/core";
import { FunctionComponent, useMemo, useRef } from "react";
import { FunctionComponent, PropsWithChildren, useMemo, useRef } from "react";
import { useSettingValue } from "../utilities/hooks";
interface ContentProps {
@ -8,7 +8,9 @@ interface ContentProps {
indent?: boolean;
}
const CollapseBox: FunctionComponent<ContentProps> = ({
type Props = PropsWithChildren<ContentProps>;
const CollapseBox: FunctionComponent<Props> = ({
on,
indent,
children,

View File

@ -1,11 +1,11 @@
import { rawRender, RenderOptions, screen } from "@/tests";
import { useForm } from "@mantine/form";
import { FunctionComponent, ReactElement } from "react";
import { FunctionComponent, PropsWithChildren, ReactElement } from "react";
import { describe, it } from "vitest";
import { FormContext, FormValues } from "../utilities/FormValues";
import { Number, Text } from "./forms";
const FormSupport: FunctionComponent = ({ children }) => {
const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
const form = useForm<FormValues>({
initialValues: {
settings: {},

View File

@ -1,7 +1,7 @@
import {
Action as GlobalAction,
FileBrowser,
FileBrowserProps,
Action as GlobalAction,
MultiSelector as GlobalMultiSelector,
MultiSelectorProps as GlobalMultiSelectorProps,
Selector as GlobalSelector,
@ -12,17 +12,17 @@ import ChipInput, { ChipInputProps } from "@/components/inputs/ChipInput";
import { useSliderMarks } from "@/utilities";
import {
Input,
Slider as MantineSlider,
SliderProps as MantineSliderProps,
NumberInput,
NumberInputProps,
PasswordInput,
PasswordInputProps,
Slider as MantineSlider,
SliderProps as MantineSliderProps,
Switch,
TextInput,
TextInputProps,
} from "@mantine/core";
import { FunctionComponent, ReactText } from "react";
import { FunctionComponent, ReactNode, ReactText } from "react";
import { BaseInput, useBaseInput } from "../utilities/hooks";
export type NumberProps = BaseInput<number> & NumberInputProps;
@ -34,7 +34,10 @@ export const Number: FunctionComponent<NumberProps> = (props) => {
<NumberInput
{...rest}
value={value ?? 0}
onChange={(val = 0) => {
onChange={(val) => {
if (val === "") {
val = 0;
}
update(val);
}}
></NumberInput>
@ -126,7 +129,9 @@ export function MultiSelector<T extends string | number>(
}
type SliderProps = BaseInput<number> &
Omit<MantineSliderProps, "onChange" | "onChangeEnd" | "marks">;
Omit<MantineSliderProps, "onChange" | "onChangeEnd" | "marks" | "label"> & {
label?: ReactNode;
};
export const Slider: FunctionComponent<SliderProps> = (props) => {
const { value, update, rest } = useBaseInput(props);

View File

@ -63,11 +63,11 @@ export const URLTestButton: FunctionComponent<{
};
export * from "./Card";
export * from "./collapse";
export { default as CollapseBox } from "./collapse";
export * from "./forms";
export * from "./Layout";
export { default as Layout } from "./Layout";
export * from "./Message";
export * from "./pathMapper";
export * from "./Section";
export * from "./collapse";
export { default as CollapseBox } from "./collapse";
export * from "./forms";
export * from "./pathMapper";

View File

@ -5,6 +5,8 @@ export const defaultUndEmbeddedSubtitlesLang =
export const languageProfileKey = "languages-profiles";
export const notificationsKey = "notifications-providers";
export const languageEqualsKey = "settings-general-language_equals";
export const pathMappingsKey = "settings-general-path_mappings";
export const pathMappingsMovieKey = "settings-general-path_mappings_movie";

View File

@ -1,4 +1,9 @@
import { createContext, FunctionComponent, useContext } from "react";
import {
createContext,
FunctionComponent,
PropsWithChildren,
useContext,
} from "react";
const SettingsContext = createContext<Settings | null>(null);
@ -12,7 +17,9 @@ type SettingsProviderProps = {
value: Settings | null;
};
export const SettingsProvider: FunctionComponent<SettingsProviderProps> = ({
type Props = PropsWithChildren<SettingsProviderProps>;
export const SettingsProvider: FunctionComponent<Props> = ({
value,
children,
}) => {

View File

@ -82,7 +82,11 @@ export function useSettingValue<T>(
}
}
export function useUpdateArray<T>(key: string, compare: keyof T) {
export function useUpdateArray<T>(
key: string,
current: Readonly<T[]>,
compare: keyof T
) {
const { setValue } = useFormActions();
const stagedValue = useStagedValues();
@ -93,9 +97,9 @@ export function useUpdateArray<T>(key: string, compare: keyof T) {
if (key in stagedValue) {
return stagedValue[key];
} else {
return [];
return current;
}
}, [key, stagedValue]);
}, [key, stagedValue, current]);
return useCallback(
(v: T, hook?: HookType) => {

View File

@ -52,9 +52,7 @@ const ReleaseCard: FunctionComponent<ReleaseInfo> = ({
<Badge color={prerelease ? "yellow" : "green"}>
{prerelease ? "Development" : "Master"}
</Badge>
<Badge hidden={!current} color="indigo">
Installed
</Badge>
{current && <Badge color="indigo">Installed</Badge>}
</Group>
<Divider my="sm"></Divider>
<Text>From newest to oldest:</Text>

View File

@ -13,7 +13,13 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Anchor, Container, Divider, Grid, Stack, Text } from "@mantine/core";
import { useDocumentTitle } from "@mantine/hooks";
import moment from "moment";
import { FunctionComponent, ReactNode, useCallback, useState } from "react";
import {
FunctionComponent,
PropsWithChildren,
ReactNode,
useCallback,
useState,
} from "react";
import Table from "./table";
interface InfoProps {
@ -53,10 +59,13 @@ function Label(props: IconProps): JSX.Element {
);
}
const InfoContainer: FunctionComponent<{ title: string }> = ({
title,
children,
}) => {
interface InfoContainerProps {
title: string;
}
const InfoContainer: FunctionComponent<
PropsWithChildren<InfoContainerProps>
> = ({ title, children }) => {
return (
<Stack>
<h4>{title}</h4>

View File

@ -4,7 +4,7 @@ import {
useMovieWantedPagination,
} from "@/apis/hooks";
import Language from "@/components/bazarr/Language";
import { task, TaskGroup } from "@/modules/task";
import { TaskGroup, task } from "@/modules/task";
import WantedView from "@/pages/views/WantedView";
import { BuildKey } from "@/utilities";
import { faSearch } from "@fortawesome/free-solid-svg-icons";

View File

@ -4,7 +4,7 @@ import {
useSeriesAction,
} from "@/apis/hooks";
import Language from "@/components/bazarr/Language";
import { task, TaskGroup } from "@/modules/task";
import { TaskGroup, task } from "@/modules/task";
import WantedView from "@/pages/views/WantedView";
import { useTableStyles } from "@/styles";
import { BuildKey } from "@/utilities";

View File

@ -5,17 +5,17 @@ import {
useProfileItemsToLanguages,
} from "@/utilities/languages";
import {
faBookmark as farBookmark,
faFolder,
faBookmark as farBookmark,
} from "@fortawesome/free-regular-svg-icons";
import {
IconDefinition,
faBookmark,
faClone,
faLanguage,
faMusic,
faStream,
faTags,
IconDefinition,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
@ -23,7 +23,6 @@ import {
Badge,
BadgeProps,
Box,
createStyles,
Grid,
Group,
HoverCard,
@ -33,6 +32,7 @@ import {
Stack,
Text,
Title,
createStyles,
} from "@mantine/core";
import { FunctionComponent, useMemo } from "react";

View File

@ -1,28 +1,24 @@
import queryClient from "@/apis/queries";
import ThemeProvider from "@/App/theme";
import queryClient from "@/apis/queries";
import { ModalsProvider } from "@/modules/modals";
import "@fontsource/roboto/300.css";
import { NotificationsProvider } from "@mantine/notifications";
import { FunctionComponent } from "react";
import { Notifications } from "@mantine/notifications";
import { FunctionComponent, PropsWithChildren } from "react";
import { QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import { Router } from "./Router";
import { Environment } from "./utilities";
export const AllProviders: FunctionComponent = ({ children }) => {
export const AllProviders: FunctionComponent<PropsWithChildren> = ({
children,
}) => {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<ModalsProvider>
<NotificationsProvider limit={5}>
<Router>
{/* c8 ignore next 3 */}
{Environment.queryDev && (
<ReactQueryDevtools initialIsOpen={false} />
)}
{children}
</Router>
</NotificationsProvider>
<Notifications limit={5} />
{/* c8 ignore next 3 */}
{Environment.queryDev && <ReactQueryDevtools initialIsOpen={false} />}
{children}
</ModalsProvider>
</ThemeProvider>
</QueryClientProvider>

View File

@ -1,11 +1,33 @@
import { AllProviders } from "@/providers";
import { render, RenderOptions } from "@testing-library/react";
import { FunctionComponent, ReactElement, StrictMode } from "react";
import {
FunctionComponent,
PropsWithChildren,
ReactElement,
StrictMode,
} from "react";
import {
createBrowserRouter,
RouteObject,
RouterProvider,
} from "react-router-dom";
const AllProvidersWithStrictMode: FunctionComponent<PropsWithChildren> = ({
children,
}) => {
const route: RouteObject = {
path: "/",
element: children,
};
// TODO: Update router system
const router = createBrowserRouter([route]);
const AllProvidersWithStrictMode: FunctionComponent = ({ children }) => {
return (
<StrictMode>
<AllProviders>{children}</AllProviders>
<AllProviders>
<RouterProvider router={router} />
</AllProviders>
</StrictMode>
);
};

View File

@ -12,6 +12,7 @@ declare namespace Language {
type CodeType = string;
interface Server {
code2: CodeType;
code3: CodeType;
name: string;
enabled: boolean;
}

View File

@ -16,7 +16,6 @@ interface Settings {
opensubtitlescom: Settings.OpenSubtitlesCom;
addic7ed: Settings.Addic7ed;
legendasdivx: Settings.Legandasdivx;
legendastv: Settings.Legendastv;
xsubs: Settings.XSubs;
assrt: Settings.Assrt;
napisy24: Settings.Napisy24;
@ -25,6 +24,7 @@ interface Settings {
titlovi: Settings.Titlovi;
ktuvit: Settings.Ktuvit;
notifications: Settings.Notifications;
language_equals: string[];
}
declare namespace Settings {
@ -56,6 +56,7 @@ declare namespace Settings {
path_mappings: [string, string][];
path_mappings_movie: [string, string][];
page_size: number;
theme: string;
port: number;
upgrade_subs: boolean;
postprocessing_cmd?: string;
@ -196,10 +197,6 @@ declare namespace Settings {
skip_wrong_fps: boolean;
}
interface Legendastv extends BaseProvider {
featured_only: boolean;
}
interface XSubs extends BaseProvider {}
interface Napisy24 extends BaseProvider {}

View File

@ -42,3 +42,12 @@ export function useProfileItemsToLanguages(profile?: Language.Profile) {
[data, profile?.items]
);
}
export function useLanguageFromCode3(code3: string) {
const { data } = useLanguages();
return useMemo(
() => data?.find((value) => value.code3 === code3),
[data, code3]
);
}

View File

@ -1,41 +1,9 @@
// A workaround of built-in hooks in React-Router v6
// https://gist.github.com/rmorse/426ffcc579922a82749934826fa9f743
import type { Blocker, History, Transition } from "history";
import { useContext, useEffect } from "react";
// eslint-disable-next-line camelcase
import { UNSAFE_NavigationContext } from "react-router-dom";
export function useBlocker(blocker: Blocker, when = true) {
const navigator = useContext(UNSAFE_NavigationContext).navigator as History;
useEffect(() => {
if (!when) return;
const unblock = navigator.block((tx: Transition) => {
const autoUnblockingTx = {
...tx,
retry() {
// Automatically unblock the transition so it can play all the way
// through before retrying it. TODO: Figure out how to re-enable
// this block if the transition is cancelled for some reason.
unblock();
tx.retry();
},
};
blocker(autoUnblockingTx);
});
return unblock;
}, [navigator, blocker, when]);
}
import { unstable_usePrompt as useUnstablePrompt } from "react-router-dom";
// TODO: Replace with Mantine's confirmation modal
export function usePrompt(when: boolean, message: string) {
useBlocker((tx) => {
if (window.confirm(message)) {
tx.retry();
}
}, when);
useUnstablePrompt({ when, message });
}

View File

@ -1,28 +1,37 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import asyncio
import concurrent.futures as cf
import os
from itertools import chain
from . import common
@ -43,11 +52,6 @@ from .plugins.NotifyBase import NotifyBase
from . import plugins
from . import __version__
# Python v3+ support code made importable, so it can remain backwards
# compatible with Python v2
# TODO: Review after dropping support for Python 2.
from . import py3compat
class Apprise:
"""
@ -369,91 +373,83 @@ class Apprise:
such as turning a \n into an actual new line, etc.
"""
return py3compat.asyncio.tosync(
self.async_notify(
try:
# Process arguments and build synchronous and asynchronous calls
# (this step can throw internal errors).
sequential_calls, parallel_calls = self._create_notify_calls(
body, title,
notify_type=notify_type, body_format=body_format,
tag=tag, match_always=match_always, attach=attach,
interpret_escapes=interpret_escapes,
),
debug=self.debug
)
interpret_escapes=interpret_escapes
)
def async_notify(self, *args, **kwargs):
except TypeError:
# No notifications sent, and there was an internal error.
return False
if not sequential_calls and not parallel_calls:
# Nothing to send
return None
sequential_result = Apprise._notify_sequential(*sequential_calls)
parallel_result = Apprise._notify_parallel_threadpool(*parallel_calls)
return sequential_result and parallel_result
async def async_notify(self, *args, **kwargs):
"""
Send a notification to all the plugins previously loaded, for
asynchronous callers. This method is an async method that should be
awaited on, even if it is missing the async keyword in its signature.
(This is omitted to preserve syntax compatibility with Python 2.)
asynchronous callers.
The arguments are identical to those of Apprise.notify().
"""
try:
coroutines = list(
self._notifyall(
Apprise._notifyhandlerasync, *args, **kwargs))
# Process arguments and build synchronous and asynchronous calls
# (this step can throw internal errors).
sequential_calls, parallel_calls = self._create_notify_calls(
*args, **kwargs)
except TypeError:
# No notifications sent, and there was an internal error.
return py3compat.asyncio.toasyncwrapvalue(False)
else:
if len(coroutines) > 0:
# All notifications sent, return False if any failed.
return py3compat.asyncio.notify(coroutines)
else:
# No notifications sent.
return py3compat.asyncio.toasyncwrapvalue(None)
@staticmethod
def _notifyhandler(server, **kwargs):
"""
The synchronous notification sender. Returns True if the notification
sent successfully.
"""
try:
# Send notification
return server.notify(**kwargs)
except TypeError:
# These our our internally thrown notifications
return False
except Exception:
# A catch all so we don't have to abort early
# just because one of our plugins has a bug in it.
logger.exception("Unhandled Notification Exception")
return False
if not sequential_calls and not parallel_calls:
# Nothing to send
return None
@staticmethod
def _notifyhandlerasync(server, **kwargs):
"""
The asynchronous notification sender. Returns a coroutine that yields
True if the notification sent successfully.
"""
sequential_result = Apprise._notify_sequential(*sequential_calls)
parallel_result = \
await Apprise._notify_parallel_asyncio(*parallel_calls)
return sequential_result and parallel_result
if server.asset.async_mode:
return server.async_notify(**kwargs)
else:
# Send the notification immediately, and wrap the result in a
# coroutine.
status = Apprise._notifyhandler(server, **kwargs)
return py3compat.asyncio.toasyncwrapvalue(status)
def _notifyall(self, handler, body, title='',
notify_type=common.NotifyType.INFO, body_format=None,
tag=common.MATCH_ALL_TAG, match_always=True, attach=None,
interpret_escapes=None):
def _create_notify_calls(self, *args, **kwargs):
"""
Creates notifications for all the plugins loaded.
Returns a generator that calls handler for each notification. The first
and only argument supplied to handler is the server, and the keyword
arguments are exactly as they would be passed to server.notify().
Returns a list of (server, notify() kwargs) tuples for plugins with
parallelism disabled and another list for plugins with parallelism
enabled.
"""
all_calls = list(self._create_notify_gen(*args, **kwargs))
# Split into sequential and parallel notify() calls.
sequential, parallel = [], []
for (server, notify_kwargs) in all_calls:
if server.asset.async_mode:
parallel.append((server, notify_kwargs))
else:
sequential.append((server, notify_kwargs))
return sequential, parallel
def _create_notify_gen(self, body, title='',
notify_type=common.NotifyType.INFO,
body_format=None, tag=common.MATCH_ALL_TAG,
match_always=True, attach=None,
interpret_escapes=None):
"""
Internal generator function for _create_notify_calls().
"""
if len(self) == 0:
@ -546,14 +542,121 @@ class Apprise:
logger.error(msg)
raise TypeError(msg)
yield handler(
server,
kwargs = dict(
body=conversion_body_map[server.notify_format],
title=conversion_title_map[server.notify_format],
notify_type=notify_type,
attach=attach,
body_format=body_format,
body_format=body_format
)
yield (server, kwargs)
@staticmethod
def _notify_sequential(*servers_kwargs):
"""
Process a list of notify() calls sequentially and synchronously.
"""
success = True
for (server, kwargs) in servers_kwargs:
try:
# Send notification
result = server.notify(**kwargs)
success = success and result
except TypeError:
# These are our internally thrown notifications.
success = False
except Exception:
# A catch all so we don't have to abort early
# just because one of our plugins has a bug in it.
logger.exception("Unhandled Notification Exception")
success = False
return success
@staticmethod
def _notify_parallel_threadpool(*servers_kwargs):
"""
Process a list of notify() calls in parallel and synchronously.
"""
n_calls = len(servers_kwargs)
# 0-length case
if n_calls == 0:
return True
# There's no need to use a thread pool for just a single notification
if n_calls == 1:
return Apprise._notify_sequential(servers_kwargs[0])
# Create log entry
logger.info(
'Notifying %d service(s) with threads.', len(servers_kwargs))
with cf.ThreadPoolExecutor() as executor:
success = True
futures = [executor.submit(server.notify, **kwargs)
for (server, kwargs) in servers_kwargs]
for future in cf.as_completed(futures):
try:
result = future.result()
success = success and result
except TypeError:
# These are our internally thrown notifications.
success = False
except Exception:
# A catch all so we don't have to abort early
# just because one of our plugins has a bug in it.
logger.exception("Unhandled Notification Exception")
success = False
return success
@staticmethod
async def _notify_parallel_asyncio(*servers_kwargs):
"""
Process a list of async_notify() calls in parallel and asynchronously.
"""
n_calls = len(servers_kwargs)
# 0-length case
if n_calls == 0:
return True
# (Unlike with the thread pool, we don't optimize for the single-
# notification case because asyncio can do useful work while waiting
# for that thread to complete)
# Create log entry
logger.info(
'Notifying %d service(s) asynchronously.', len(servers_kwargs))
async def do_call(server, kwargs):
return await server.async_notify(**kwargs)
cors = (do_call(server, kwargs) for (server, kwargs) in servers_kwargs)
results = await asyncio.gather(*cors, return_exceptions=True)
if any(isinstance(status, Exception)
and not isinstance(status, TypeError) for status in results):
# A catch all so we don't have to abort early just because
# one of our plugins has a bug in it.
logger.exception("Unhandled Notification Exception")
return False
if any(isinstance(status, TypeError) for status in results):
# These are our internally thrown notifications.
return False
return all(results)
def details(self, lang=None, show_requirements=False, show_disabled=False):
"""
@ -581,6 +684,7 @@ class Apprise:
'setup_url': getattr(plugin, 'setup_url', None),
# Placeholder - populated below
'details': None,
# Differentiat between what is a custom loaded plugin and
# which is native.
'category': getattr(plugin, 'category', None)

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
from uuid import uuid4

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
from . import attachment
from . import URLBase
@ -170,6 +177,11 @@ class AppriseAttachment:
return_status = False
continue
elif isinstance(_attachment, AppriseAttachment):
# We were provided a list of Apprise Attachments
# append our content together
instance = _attachment.attachments
elif not isinstance(_attachment, attachment.AttachBase):
logger.warning(
"An invalid attachment (type={}) was specified.".format(
@ -196,7 +208,11 @@ class AppriseAttachment:
continue
# Add our initialized plugin to our server listings
self.attachments.append(instance)
if isinstance(instance, list):
self.attachments.extend(instance)
else:
self.attachments.append(instance)
# Return our status
return return_status

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
from . import config
from . import ConfigBase

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import ctypes
import locale
@ -67,7 +74,7 @@ class LazyTranslation:
"""
self.text = text
super(LazyTranslation, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def __str__(self):
return gettext.gettext(self.text)

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
from .logger import logger
@ -194,7 +201,7 @@ class URLBase:
asset if isinstance(asset, AppriseAsset) else AppriseAsset()
# Certificate Verification (for SSL calls); default to being enabled
self.verify_certificate = kwargs.get('verify', True)
self.verify_certificate = parse_bool(kwargs.get('verify', True))
# Secure Mode
self.secure = kwargs.get('secure', False)
@ -222,24 +229,22 @@ class URLBase:
self.password = URLBase.unquote(self.password)
# Store our Timeout Variables
if 'socket_read_timeout' in kwargs:
if 'rto' in kwargs:
try:
self.socket_read_timeout = \
float(kwargs.get('socket_read_timeout'))
self.socket_read_timeout = float(kwargs.get('rto'))
except (TypeError, ValueError):
self.logger.warning(
'Invalid socket read timeout (rto) was specified {}'
.format(kwargs.get('socket_read_timeout')))
.format(kwargs.get('rto')))
if 'socket_connect_timeout' in kwargs:
if 'cto' in kwargs:
try:
self.socket_connect_timeout = \
float(kwargs.get('socket_connect_timeout'))
self.socket_connect_timeout = float(kwargs.get('cto'))
except (TypeError, ValueError):
self.logger.warning(
'Invalid socket connect timeout (cto) was specified {}'
.format(kwargs.get('socket_connect_timeout')))
.format(kwargs.get('cto')))
if 'tag' in kwargs:
# We want to associate some tags with our notification service.
@ -598,7 +603,7 @@ class URLBase:
}
@staticmethod
def parse_url(url, verify_host=True):
def parse_url(url, verify_host=True, plus_to_space=False):
"""Parses the URL and returns it broken apart into a dictionary.
This is very specific and customized for Apprise.
@ -618,7 +623,8 @@ class URLBase:
"""
results = parse_url(
url, default_schema='unknown', verify_host=verify_host)
url, default_schema='unknown', verify_host=verify_host,
plus_to_space=plus_to_space)
if not results:
# We're done; we failed to parse our url
@ -646,11 +652,11 @@ class URLBase:
# Store our socket read timeout if specified
if 'rto' in results['qsd']:
results['socket_read_timeout'] = results['qsd']['rto']
results['rto'] = results['qsd']['rto']
# Store our socket connect timeout if specified
if 'cto' in results['qsd']:
results['socket_connect_timeout'] = results['qsd']['cto']
results['cto'] = results['qsd']['cto']
if 'port' in results['qsd']:
results['port'] = results['qsd']['port']
@ -679,6 +685,15 @@ class URLBase:
return response
def __len__(self):
"""
Should be over-ridden and allows the tracking of how many targets
are associated with each URLBase object.
Default is always 1
"""
return 1
def schemas(self):
"""A simple function that returns a set of all schemas associated
with this object based on the object.protocol and

View File

@ -1,33 +1,40 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
__title__ = 'Apprise'
__version__ = '1.1.0'
__version__ = '1.4.0'
__author__ = 'Chris Caron'
__license__ = 'MIT'
__copywrite__ = 'Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>'
__license__ = 'BSD'
__copywrite__ = 'Copyright (C) 2023 Chris Caron <lead2gold@gmail.com>'
__email__ = 'lead2gold@gmail.com'
__status__ = 'Production'

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import os
import time
@ -120,7 +127,7 @@ class AttachBase(URLBase):
should be considered expired.
"""
super(AttachBase, self).__init__(**kwargs)
super().__init__(**kwargs)
if not mimetypes.inited:
# Ensure mimetypes has been initialized

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
import os
@ -50,7 +57,7 @@ class AttachFile(AttachBase):
Initialize Local File Attachment Object
"""
super(AttachFile, self).__init__(**kwargs)
super().__init__(**kwargs)
# Store path but mark it dirty since we have not performed any
# verification at this point.

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
import os
@ -61,7 +68,7 @@ class AttachHTTP(AttachBase):
additionally include as part of the server headers to post with
"""
super(AttachHTTP, self).__init__(**kwargs)
super().__init__(**kwargs)
self.schema = 'https' if self.secure else 'http'
@ -254,7 +261,7 @@ class AttachHTTP(AttachBase):
self._temp_file.close()
self._temp_file = None
super(AttachHTTP, self).invalidate()
super().invalidate()
def url(self, privacy=False, *args, **kwargs):
"""

View File

@ -1,30 +1,36 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
from os import listdir
from os.path import dirname
from os.path import abspath

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# BSD 3-Clause License
#
# This code is licensed under the MIT License.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import click
import logging
@ -73,28 +80,64 @@ DEFAULT_CONFIG_PATHS = (
'~/.apprise/apprise.yml',
'~/.config/apprise/apprise',
'~/.config/apprise/apprise.yml',
# Global Configuration Support
'/etc/apprise',
'/etc/apprise.yml',
'/etc/apprise/apprise',
'/etc/apprise/apprise.yml',
)
# Define our paths to search for plugins
DEFAULT_PLUGIN_PATHS = (
'~/.apprise/plugins',
'~/.config/apprise/plugins',
# Global Plugin Support
'/var/lib/apprise/plugins',
)
# Detect Windows
if platform.system() == 'Windows':
# Default Config Search Path for Windows Users
DEFAULT_CONFIG_PATHS = (
expandvars('%APPDATA%/Apprise/apprise'),
expandvars('%APPDATA%/Apprise/apprise.yml'),
expandvars('%LOCALAPPDATA%/Apprise/apprise'),
expandvars('%LOCALAPPDATA%/Apprise/apprise.yml'),
expandvars('%APPDATA%\\Apprise\\apprise'),
expandvars('%APPDATA%\\Apprise\\apprise.yml'),
expandvars('%LOCALAPPDATA%\\Apprise\\apprise'),
expandvars('%LOCALAPPDATA%\\Apprise\\apprise.yml'),
#
# Global Support
#
# C:\ProgramData\Apprise\
expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise'),
expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise.yml'),
# C:\Program Files\Apprise
expandvars('%PROGRAMFILES%\\Apprise\\apprise'),
expandvars('%PROGRAMFILES%\\Apprise\\apprise.yml'),
# C:\Program Files\Common Files
expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise'),
expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise.yml'),
)
# Default Plugin Search Path for Windows Users
DEFAULT_PLUGIN_PATHS = (
expandvars('%APPDATA%/Apprise/plugins'),
expandvars('%LOCALAPPDATA%/Apprise/plugins'),
expandvars('%APPDATA%\\Apprise\\plugins'),
expandvars('%LOCALAPPDATA%\\Apprise\\plugins'),
#
# Global Support
#
# C:\ProgramData\Apprise\plugins
expandvars('%ALLUSERSPROFILE%\\Apprise\\plugins'),
# C:\Program Files\Apprise\plugins
expandvars('%PROGRAMFILES%\\Apprise\\plugins'),
# C:\Program Files\Common Files
expandvars('%COMMONPROGRAMFILES%\\Apprise\\plugins'),
)

View File

@ -1,28 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
# we mirror our base purely for the ability to reset everything; this
# is generally only used in testing and should not be used by developers

View File

@ -1,3 +1,7 @@
import types
import typing as t
class NotifyType:
INFO: NotifyType
SUCCESS: NotifyType
@ -12,4 +16,7 @@ class NotifyFormat:
class ContentLocation:
LOCAL: ContentLocation
HOSTED: ContentLocation
INACCESSIBLE: ContentLocation
INACCESSIBLE: ContentLocation
NOTIFY_MODULE_MAP: t.Dict[str, t.Dict[str, t.Union[t.Type["NotifyBase"], types.ModuleType]]]

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import os
import re
@ -113,7 +120,7 @@ class ConfigBase(URLBase):
these 'include' entries to be honored, this value must be set to True.
"""
super(ConfigBase, self).__init__(**kwargs)
super().__init__(**kwargs)
# Tracks the time the content was last retrieved on. This place a role
# for cases where we are not caching our response and are required to
@ -548,7 +555,7 @@ class ConfigBase(URLBase):
# Define what a valid line should look like
valid_line_re = re.compile(
r'^\s*(?P<line>([;#]+(?P<comment>.*))|'
r'(\s*(?P<tags>[^=]+)=|=)?\s*'
r'(\s*(?P<tags>[a-z0-9, \t_-]+)\s*=|=)?\s*'
r'(?P<url>[a-z0-9]{2,9}://.*)|'
r'include\s+(?P<config>.+))?\s*$', re.I)

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
import os
@ -53,7 +60,7 @@ class ConfigFile(ConfigBase):
additionally include as part of the server headers to post with
"""
super(ConfigFile, self).__init__(**kwargs)
super().__init__(**kwargs)
# Store our file path as it was set
self.path = os.path.abspath(os.path.expanduser(path))

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
import requests
@ -75,7 +82,7 @@ class ConfigHTTP(ConfigBase):
additionally include as part of the server headers to post with
"""
super(ConfigHTTP, self).__init__(**kwargs)
super().__init__(**kwargs)
self.schema = 'https' if self.secure else 'http'

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
from .ConfigBase import ConfigBase
from ..AppriseLocale import gettext_lazy as _
@ -46,7 +53,7 @@ class ConfigMemory(ConfigBase):
Memory objects just store the raw configuration in memory. There is
no external reference point. It's always considered cached.
"""
super(ConfigMemory, self).__init__(**kwargs)
super().__init__(**kwargs)
# Store our raw config into memory
self.content = content

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
from os import listdir

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
import re
from markdown import markdown
@ -99,7 +106,7 @@ class HTMLConverter(HTMLParser, object):
BLOCK_END = {}
def __init__(self, **kwargs):
super(HTMLConverter, self).__init__(**kwargs)
super().__init__(**kwargs)
# Shoudl we store the text content or not?
self._do_store = True

View File

@ -1,27 +1,35 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
from ..plugins.NotifyBase import NotifyBase
from ..utils import URL_DETAILS_RE
from ..utils import parse_url
@ -134,7 +142,7 @@ class CustomNotifyPlugin(NotifyBase):
"""
# init parent
super(CustomNotifyPluginWrapper, self).__init__(**kwargs)
super().__init__(**kwargs)
self._default_args = {}

View File

@ -1,27 +1,35 @@
# -*- coding: utf-8 -*-
# BSD 3-Clause License
#
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
# Apprise - Push Notification Library.
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
#
# This code is licensed under the MIT License.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files(the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions :
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 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.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 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.
from .notify import notify

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