mirror of
https://github.com/morpheus65535/bazarr.git
synced 2024-09-20 07:25:58 +08:00
Merge branch 'development'
# Conflicts: # frontend/package-lock.json # frontend/package.json
This commit is contained in:
commit
080710e7e1
|
@ -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
|
||||
|
|
|
@ -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]):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,37 +76,26 @@ 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')
|
||||
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'
|
||||
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
|
||||
detected_language = guess_language(text)
|
||||
except Exception:
|
||||
logging.debug('BAZARR was unable to detect encoding for this subtitles file: %r', subtitle_path)
|
||||
finally:
|
||||
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))
|
||||
|
@ -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:
|
||||
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
|
||||
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
|
||||
text = text.decode(encoding)
|
||||
|
||||
if bool(re.search(hi_regex, text)):
|
||||
subtitles[subtitle] = Language.rebuild(subtitles[subtitle], forced=False, hi=True)
|
||||
|
|
|
@ -87,7 +87,6 @@ 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
|
||||
|
||||
initial_hi = None
|
||||
|
|
|
@ -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(),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -7,3 +7,4 @@ pytest-cov
|
|||
pytest-vcr
|
||||
pytest-mock
|
||||
requests-mock
|
||||
setuptools
|
||||
|
|
1829
frontend/package-lock.json
generated
1829
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||
|
|
|
@ -15,12 +15,12 @@ import {
|
|||
Avatar,
|
||||
Badge,
|
||||
Burger,
|
||||
createStyles,
|
||||
Divider,
|
||||
Group,
|
||||
Header,
|
||||
MediaQuery,
|
||||
Menu,
|
||||
createStyles,
|
||||
} from "@mantine/core";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
|
|
|
@ -357,9 +357,11 @@ const NavbarItem: FunctionComponent<NavbarItemProps> = ({
|
|||
></FontAwesomeIcon>
|
||||
)}
|
||||
{name}
|
||||
<Badge className={classes.badge} radius="xs" hidden={shouldHideBadge}>
|
||||
{shouldHideBadge === false && (
|
||||
<Badge className={classes.badge} radius="xs">
|
||||
{badge}
|
||||
</Badge>
|
||||
)}
|
||||
</Text>
|
||||
</NavLink>
|
||||
);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
||||
|
|
34
frontend/src/components/bazarr/LanguageSelector.tsx
Normal file
34
frontend/src/components/bazarr/LanguageSelector.tsx
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -93,6 +93,7 @@ export function Selector<T>({
|
|||
|
||||
return (
|
||||
<Select
|
||||
withinPortal={true}
|
||||
data={data}
|
||||
defaultValue={wrappedDefaultValue}
|
||||
value={wrappedValue}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -4,12 +4,12 @@ import {
|
|||
CellProps,
|
||||
Column,
|
||||
ColumnInstance,
|
||||
ensurePluginOrder,
|
||||
HeaderProps,
|
||||
Hooks,
|
||||
MetaBase,
|
||||
TableInstance,
|
||||
TableToggleCommonProps,
|
||||
ensurePluginOrder,
|
||||
} from "react-table";
|
||||
|
||||
const pluginName = "useCustomSelection";
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
if (container === null) {
|
||||
Error("Cannot initialize app, root not found");
|
||||
} else {
|
||||
const root = createRoot(container);
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<AllProviders>
|
||||
<RouteApp />
|
||||
<Router />
|
||||
</AllProviders>
|
||||
</StrictMode>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 {}>(
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export * from "./hooks";
|
||||
export { default as ModalsProvider } from "./ModalsProvider";
|
||||
export { default as withModal } from "./WithModal";
|
||||
export * from "./hooks";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -11,8 +11,8 @@ import { useSelectorOptions } from "@/utilities";
|
|||
import {
|
||||
Box,
|
||||
Container,
|
||||
createStyles,
|
||||
SimpleGrid,
|
||||
createStyles,
|
||||
useMantineTheme,
|
||||
} from "@mantine/core";
|
||||
import { useDocumentTitle } from "@mantine/hooks";
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -65,7 +65,9 @@ const MovieView: FunctionComponent = () => {
|
|||
accessor: "missing_subtitles",
|
||||
Cell: (row) => {
|
||||
const missing = row.value;
|
||||
return missing.map((v) => (
|
||||
return (
|
||||
<>
|
||||
{missing.map((v) => (
|
||||
<Badge
|
||||
mr="xs"
|
||||
color="yellow"
|
||||
|
@ -73,7 +75,9 @@ const MovieView: FunctionComponent = () => {
|
|||
>
|
||||
<Language.Text value={v}></Language.Text>
|
||||
</Badge>
|
||||
));
|
||||
))}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
196
frontend/src/pages/Settings/Languages/equals.test.ts
Normal file
196
frontend/src/pages/Settings/Languages/equals.test.ts
Normal 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);
|
||||
});
|
||||
});
|
365
frontend/src/pages/Settings/Languages/equals.tsx
Normal file
365
frontend/src/pages/Settings/Languages/equals.tsx
Normal 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;
|
|
@ -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}
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
{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 (
|
||||
<>
|
||||
{items.map((v, idx) => {
|
||||
return (
|
||||
<Badge key={BuildKey(idx, v)} color="gray">
|
||||
{v}
|
||||
</Badge>
|
||||
);
|
||||
});
|
||||
})}
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -123,6 +123,7 @@ export const NotificationView: FunctionComponent = () => {
|
|||
|
||||
const update = useUpdateArray<Settings.NotificationInfo>(
|
||||
notificationsKey,
|
||||
notifications ?? [],
|
||||
"name"
|
||||
);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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,
|
||||
}) => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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>
|
||||
<Notifications limit={5} />
|
||||
{/* c8 ignore next 3 */}
|
||||
{Environment.queryDev && (
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
)}
|
||||
{Environment.queryDev && <ReactQueryDevtools initialIsOpen={false} />}
|
||||
{children}
|
||||
</Router>
|
||||
</NotificationsProvider>
|
||||
</ModalsProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
1
frontend/src/types/api.d.ts
vendored
1
frontend/src/types/api.d.ts
vendored
|
@ -12,6 +12,7 @@ declare namespace Language {
|
|||
type CodeType = string;
|
||||
interface Server {
|
||||
code2: CodeType;
|
||||
code3: CodeType;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
|
7
frontend/src/types/settings.d.ts
vendored
7
frontend/src/types/settings.d.ts
vendored
|
@ -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 {}
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,6 +208,10 @@ class AppriseAttachment:
|
|||
continue
|
||||
|
||||
# Add our initialized plugin to our server listings
|
||||
if isinstance(instance, list):
|
||||
self.attachments.extend(instance)
|
||||
|
||||
else:
|
||||
self.attachments.append(instance)
|
||||
|
||||
# Return our status
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'),
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import types
|
||||
import typing as t
|
||||
|
||||
|
||||
class NotifyType:
|
||||
INFO: NotifyType
|
||||
SUCCESS: NotifyType
|
||||
|
@ -13,3 +17,6 @@ class ContentLocation:
|
|||
LOCAL: ContentLocation
|
||||
HOSTED: ContentLocation
|
||||
INACCESSIBLE: ContentLocation
|
||||
|
||||
|
||||
NOTIFY_MODULE_MAP: t.Dict[str, t.Dict[str, t.Union[t.Type["NotifyBase"], types.ModuleType]]]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue