mirror of https://github.com/morpheus65535/bazarr
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
|
- GreekSubtitles
|
||||||
- Hosszupuska
|
- Hosszupuska
|
||||||
- LegendasDivx
|
- LegendasDivx
|
||||||
- LegendasTV
|
|
||||||
- Karagarga.in
|
- Karagarga.in
|
||||||
- Ktuvit (Get `hashed_password` using method described [here](https://github.com/XBMCil/service.subtitles.ktuvit))
|
- Ktuvit (Get `hashed_password` using method described [here](https://github.com/XBMCil/service.subtitles.ktuvit))
|
||||||
- Napiprojekt
|
- Napiprojekt
|
||||||
|
|
|
@ -20,8 +20,8 @@ def check_python_version():
|
||||||
print("Python " + minimum_py3_str + " or greater required. "
|
print("Python " + minimum_py3_str + " or greater required. "
|
||||||
"Current version is " + platform.python_version() + ". Please upgrade Python.")
|
"Current version is " + platform.python_version() + ". Please upgrade Python.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif int(python_version[0]) == 3 and int(python_version[1]) > 10:
|
elif int(python_version[0]) == 3 and int(python_version[1]) > 11:
|
||||||
print("Python version greater than 3.10.x is unsupported. Current version is " + platform.python_version() +
|
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.")
|
". 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 \
|
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]):
|
(int(python_version[0]) != minimum_py3_tuple[0]):
|
||||||
|
|
|
@ -4,7 +4,7 @@ from flask_restx import Resource, Namespace, reqparse
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from app.database import TableHistory, TableHistoryMovie, TableSettingsLanguages
|
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
|
from ..utils import authenticate, False_Keys
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ class Languages(Resource):
|
||||||
try:
|
try:
|
||||||
languages_dicts.append({
|
languages_dicts.append({
|
||||||
'code2': code2,
|
'code2': code2,
|
||||||
|
'code3': alpha3_from_alpha2(code2),
|
||||||
'name': language_from_alpha2(code2),
|
'name': language_from_alpha2(code2),
|
||||||
# Compatibility: Use false temporarily
|
# Compatibility: Use false temporarily
|
||||||
'enabled': False
|
'enabled': False
|
||||||
|
@ -55,6 +56,7 @@ class Languages(Resource):
|
||||||
else:
|
else:
|
||||||
languages_dicts = TableSettingsLanguages.select(TableSettingsLanguages.name,
|
languages_dicts = TableSettingsLanguages.select(TableSettingsLanguages.name,
|
||||||
TableSettingsLanguages.code2,
|
TableSettingsLanguages.code2,
|
||||||
|
TableSettingsLanguages.code3,
|
||||||
TableSettingsLanguages.enabled)\
|
TableSettingsLanguages.enabled)\
|
||||||
.order_by(TableSettingsLanguages.name).dicts()
|
.order_by(TableSettingsLanguages.name).dicts()
|
||||||
languages_dicts = list(languages_dicts)
|
languages_dicts = list(languages_dicts)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from flask import Flask, redirect
|
from flask import Flask, redirect
|
||||||
|
|
||||||
|
from flask_compress import Compress
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_socketio import SocketIO
|
from flask_socketio import SocketIO
|
||||||
|
|
||||||
|
@ -15,6 +16,8 @@ socketio = SocketIO()
|
||||||
def create_app():
|
def create_app():
|
||||||
# Flask Setup
|
# Flask Setup
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config['COMPRESS_ALGORITHM'] = 'gzip'
|
||||||
|
Compress(app)
|
||||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
|
|
||||||
app.config["SECRET_KEY"] = settings.general.flask_secret_key
|
app.config["SECRET_KEY"] = settings.general.flask_secret_key
|
||||||
|
|
|
@ -52,6 +52,7 @@ defaults = {
|
||||||
'movie_default_enabled': 'False',
|
'movie_default_enabled': 'False',
|
||||||
'movie_default_profile': '',
|
'movie_default_profile': '',
|
||||||
'page_size': '25',
|
'page_size': '25',
|
||||||
|
'theme': 'auto',
|
||||||
'page_size_manual_search': '10',
|
'page_size_manual_search': '10',
|
||||||
'minimum_score_movie': '70',
|
'minimum_score_movie': '70',
|
||||||
'use_embedded_subs': 'True',
|
'use_embedded_subs': 'True',
|
||||||
|
@ -83,7 +84,8 @@ defaults = {
|
||||||
'default_und_audio_lang': '',
|
'default_und_audio_lang': '',
|
||||||
'default_und_embedded_subtitles_lang': '',
|
'default_und_embedded_subtitles_lang': '',
|
||||||
'parse_embedded_audio_track': 'False',
|
'parse_embedded_audio_track': 'False',
|
||||||
'skip_hashing': 'False'
|
'skip_hashing': 'False',
|
||||||
|
'language_equals': '[]',
|
||||||
},
|
},
|
||||||
'auth': {
|
'auth': {
|
||||||
'type': 'None',
|
'type': 'None',
|
||||||
|
@ -168,7 +170,8 @@ defaults = {
|
||||||
'verify_ssl': 'True'
|
'verify_ssl': 'True'
|
||||||
},
|
},
|
||||||
'subf2m': {
|
'subf2m': {
|
||||||
'verify_ssl': 'True'
|
'verify_ssl': 'True',
|
||||||
|
'user_agent': ''
|
||||||
},
|
},
|
||||||
'whisperai': {
|
'whisperai': {
|
||||||
'endpoint': 'http://127.0.0.1:9000',
|
'endpoint': 'http://127.0.0.1:9000',
|
||||||
|
@ -183,11 +186,6 @@ defaults = {
|
||||||
'email': '',
|
'email': '',
|
||||||
'hashed_password': ''
|
'hashed_password': ''
|
||||||
},
|
},
|
||||||
'legendastv': {
|
|
||||||
'username': '',
|
|
||||||
'password': '',
|
|
||||||
'featured_only': 'False'
|
|
||||||
},
|
|
||||||
'xsubs': {
|
'xsubs': {
|
||||||
'username': '',
|
'username': '',
|
||||||
'password': ''
|
'password': ''
|
||||||
|
@ -300,7 +298,8 @@ array_keys = ['excluded_tags',
|
||||||
'excluded_series_types',
|
'excluded_series_types',
|
||||||
'enabled_providers',
|
'enabled_providers',
|
||||||
'path_mappings',
|
'path_mappings',
|
||||||
'path_mappings_movie']
|
'path_mappings_movie',
|
||||||
|
'language_equals']
|
||||||
|
|
||||||
str_keys = ['chmod']
|
str_keys = ['chmod']
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ from subliminal_patch.extensions import provider_registry
|
||||||
|
|
||||||
from app.get_args import args
|
from app.get_args import args
|
||||||
from app.config import settings, get_array_from
|
from app.config import settings, get_array_from
|
||||||
|
from languages.get_languages import CustomLanguage
|
||||||
from app.event_handler import event_stream
|
from app.event_handler import event_stream
|
||||||
from utilities.binaries import get_binary
|
from utilities.binaries import get_binary
|
||||||
from radarr.blacklist import blacklist_log_movie
|
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"]
|
"hosszupuska", "supersubtitles", "titlovi", "argenteam", "assrt", "subscene"]
|
||||||
|
|
||||||
throttle_count = {}
|
throttle_count = {}
|
||||||
|
@ -115,6 +116,49 @@ def provider_pool():
|
||||||
return subliminal_patch.core.SZProviderPool
|
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():
|
def get_providers():
|
||||||
providers_list = []
|
providers_list = []
|
||||||
existing_providers = provider_registry.names()
|
existing_providers = provider_registry.names()
|
||||||
|
@ -202,13 +246,6 @@ def get_providers_auth():
|
||||||
'skip_wrong_fps'
|
'skip_wrong_fps'
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'legendastv': {
|
|
||||||
'username': settings.legendastv.username,
|
|
||||||
'password': settings.legendastv.password,
|
|
||||||
'featured_only': settings.legendastv.getboolean(
|
|
||||||
'featured_only'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'xsubs': {
|
'xsubs': {
|
||||||
'username': settings.xsubs.username,
|
'username': settings.xsubs.username,
|
||||||
'password': settings.xsubs.password,
|
'password': settings.xsubs.password,
|
||||||
|
@ -250,11 +287,13 @@ def get_providers_auth():
|
||||||
'f_password': settings.karagarga.f_password,
|
'f_password': settings.karagarga.f_password,
|
||||||
},
|
},
|
||||||
'subf2m': {
|
'subf2m': {
|
||||||
'verify_ssl': settings.subf2m.getboolean('verify_ssl')
|
'verify_ssl': settings.subf2m.getboolean('verify_ssl'),
|
||||||
|
'user_agent': settings.subf2m.user_agent,
|
||||||
},
|
},
|
||||||
'whisperai': {
|
'whisperai': {
|
||||||
'endpoint': settings.whisperai.endpoint,
|
'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'),
|
'build', 'assets'),
|
||||||
static_url_path='/assets')
|
static_url_path='/assets')
|
||||||
|
|
||||||
static_bp = Blueprint('images', __name__,
|
if os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend', 'build',
|
||||||
static_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
'images')):
|
||||||
'frontend', 'build', 'images'), static_url_path='/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)
|
ui_bp.register_blueprint(static_bp)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ class CustomLanguage:
|
||||||
official_alpha3 = "por"
|
official_alpha3 = "por"
|
||||||
name = "Brazilian Portuguese"
|
name = "Brazilian Portuguese"
|
||||||
iso = "BR"
|
iso = "BR"
|
||||||
|
_scripts = []
|
||||||
_possible_matches = ("pt-br", "pob", "pb", "brazilian", "brasil", "brazil")
|
_possible_matches = ("pt-br", "pob", "pb", "brazilian", "brasil", "brazil")
|
||||||
_extensions = (".pt-br", ".pob", ".pb")
|
_extensions = (".pt-br", ".pob", ".pb")
|
||||||
_extensions_forced = (".pt-br.forced", ".pob.forced", ".pb.forced")
|
_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)
|
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):
|
class BrazilianPortuguese(CustomLanguage):
|
||||||
# Same attributes as base class
|
# Same attributes as base class
|
||||||
|
@ -100,6 +110,9 @@ class ChineseTraditional(CustomLanguage):
|
||||||
official_alpha3 = "zho"
|
official_alpha3 = "zho"
|
||||||
name = "Chinese Traditional"
|
name = "Chinese Traditional"
|
||||||
iso = "TW"
|
iso = "TW"
|
||||||
|
# _scripts = (Script("Hant"),)
|
||||||
|
# We'll use literals for now
|
||||||
|
_scripts = ("Hant",)
|
||||||
_extensions = (
|
_extensions = (
|
||||||
".cht",
|
".cht",
|
||||||
".tc",
|
".tc",
|
||||||
|
@ -211,6 +224,7 @@ class LatinAmericanSpanish(CustomLanguage):
|
||||||
official_alpha3 = "spa"
|
official_alpha3 = "spa"
|
||||||
name = "Latin American Spanish"
|
name = "Latin American Spanish"
|
||||||
iso = "MX" # Not fair, but ok
|
iso = "MX" # Not fair, but ok
|
||||||
|
_scripts = ("419",)
|
||||||
_possible_matches = (
|
_possible_matches = (
|
||||||
"es-la",
|
"es-la",
|
||||||
"spa-la",
|
"spa-la",
|
||||||
|
|
|
@ -7,7 +7,7 @@ import re
|
||||||
from guess_language import guess_language
|
from guess_language import guess_language
|
||||||
from subliminal_patch import core
|
from subliminal_patch import core
|
||||||
from subzero.language import Language
|
from subzero.language import Language
|
||||||
from charamel import Detector
|
from chardet import detect
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from constants import hi_regex
|
from constants import hi_regex
|
||||||
|
@ -76,45 +76,34 @@ def guess_external_subtitles(dest_folder, subtitles, media_type, previously_inde
|
||||||
with open(subtitle_path, 'rb') as f:
|
with open(subtitle_path, 'rb') as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
|
|
||||||
try:
|
encoding = detect(text)
|
||||||
text = text.decode('utf-8')
|
if encoding and 'encoding' in encoding:
|
||||||
detected_language = guess_language(text)
|
encoding = detect(text)['encoding']
|
||||||
# add simplified and traditional chinese detection
|
else:
|
||||||
if detected_language == 'zh':
|
logging.debug("BAZARR skipping this subtitles because we can't guess the encoding. "
|
||||||
traditional_chinese_fuzzy = [u"繁", u"雙語"]
|
"It's probably a binary file: " + subtitle_path)
|
||||||
traditional_chinese = [".cht", ".tc", ".zh-tw", ".zht", ".zh-hant", ".zhhant", ".zh_hant",
|
continue
|
||||||
".hant", ".big5", ".traditional"]
|
text = text.decode(encoding)
|
||||||
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'
|
detected_language = guess_language(text)
|
||||||
except UnicodeDecodeError:
|
|
||||||
detector = Detector()
|
# add simplified and traditional chinese detection
|
||||||
|
if detected_language == 'zh':
|
||||||
|
traditional_chinese_fuzzy = [u"繁", u"雙語"]
|
||||||
|
traditional_chinese = [".cht", ".tc", ".zh-tw", ".zht", ".zh-hant", ".zhhant", ".zh_hant",
|
||||||
|
".hant", ".big5", ".traditional"]
|
||||||
|
if str(os.path.splitext(subtitle)[0]).lower().endswith(tuple(traditional_chinese)) or \
|
||||||
|
(str(subtitle_path).lower())[:-5] in traditional_chinese_fuzzy:
|
||||||
|
detected_language = 'zt'
|
||||||
|
|
||||||
|
if detected_language:
|
||||||
|
logging.debug("BAZARR external subtitles detected and guessed this language: " + str(
|
||||||
|
detected_language))
|
||||||
try:
|
try:
|
||||||
guess = detector.detect(text)
|
subtitles[subtitle] = Language.rebuild(Language.fromietf(detected_language), forced=forced,
|
||||||
|
hi=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.debug("BAZARR skipping this subtitles because we can't guess the encoding. "
|
pass
|
||||||
"It's probably a binary file: " + subtitle_path)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
logging.debug('BAZARR detected encoding %r', guess)
|
|
||||||
try:
|
|
||||||
text = text.decode(guess)
|
|
||||||
except Exception:
|
|
||||||
logging.debug(
|
|
||||||
"BAZARR skipping this subtitles because we can't decode the file using the "
|
|
||||||
"guessed encoding. It's probably a binary file: " + subtitle_path)
|
|
||||||
continue
|
|
||||||
detected_language = guess_language(text)
|
|
||||||
except Exception:
|
|
||||||
logging.debug('BAZARR was unable to detect encoding for this subtitles file: %r', subtitle_path)
|
|
||||||
finally:
|
|
||||||
if detected_language:
|
|
||||||
logging.debug("BAZARR external subtitles detected and guessed this language: " + str(
|
|
||||||
detected_language))
|
|
||||||
try:
|
|
||||||
subtitles[subtitle] = Language.rebuild(Language.fromietf(detected_language), forced=forced,
|
|
||||||
hi=False)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# If language is still None (undetected), skip it
|
# If language is still None (undetected), skip it
|
||||||
if hasattr(subtitles[subtitle], 'basename') and not subtitles[subtitle].basename:
|
if hasattr(subtitles[subtitle], 'basename') and not subtitles[subtitle].basename:
|
||||||
|
@ -139,24 +128,14 @@ def guess_external_subtitles(dest_folder, subtitles, media_type, previously_inde
|
||||||
with open(subtitle_path, 'rb') as f:
|
with open(subtitle_path, 'rb') as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
|
|
||||||
try:
|
encoding = detect(text)
|
||||||
text = text.decode('utf-8')
|
if encoding and 'encoding' in encoding:
|
||||||
except UnicodeDecodeError:
|
encoding = detect(text)['encoding']
|
||||||
detector = Detector()
|
else:
|
||||||
try:
|
logging.debug("BAZARR skipping this subtitles because we can't guess the encoding. "
|
||||||
guess = detector.detect(text)
|
"It's probably a binary file: " + subtitle_path)
|
||||||
except Exception:
|
continue
|
||||||
logging.debug("BAZARR skipping this subtitles because we can't guess the encoding. "
|
text = text.decode(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
|
|
||||||
|
|
||||||
if bool(re.search(hi_regex, text)):
|
if bool(re.search(hi_regex, text)):
|
||||||
subtitles[subtitle] = Language.rebuild(subtitles[subtitle], forced=False, hi=True)
|
subtitles[subtitle] = Language.rebuild(subtitles[subtitle], forced=False, hi=True)
|
||||||
|
|
|
@ -87,8 +87,7 @@ def manual_search(path, profile_id, providers, sceneName, title, media_type):
|
||||||
logging.debug(f"BAZARR Skipping {s}, because it doesn't match our series/episode")
|
logging.debug(f"BAZARR Skipping {s}, because it doesn't match our series/episode")
|
||||||
except TypeError:
|
except TypeError:
|
||||||
logging.debug("BAZARR Ignoring invalid subtitles")
|
logging.debug("BAZARR Ignoring invalid subtitles")
|
||||||
finally:
|
continue
|
||||||
continue
|
|
||||||
|
|
||||||
initial_hi = None
|
initial_hi = None
|
||||||
initial_hi_match = False
|
initial_hi_match = False
|
||||||
|
|
|
@ -8,7 +8,7 @@ from inspect import getfullargspec
|
||||||
|
|
||||||
from radarr.blacklist import get_blacklist_movie
|
from radarr.blacklist import get_blacklist_movie
|
||||||
from sonarr.blacklist import get_blacklist
|
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
|
from .utils import get_ban_list
|
||||||
|
|
||||||
|
@ -19,10 +19,11 @@ def _init_pool(media_type, profile_id=None, providers=None):
|
||||||
return pool(
|
return pool(
|
||||||
providers=providers or get_providers(),
|
providers=providers or get_providers(),
|
||||||
provider_configs=get_providers_auth(),
|
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,
|
throttle_callback=provider_throttle,
|
||||||
ban_list=get_ban_list(profile_id),
|
ban_list=get_ban_list(profile_id),
|
||||||
language_hook=None,
|
language_hook=None,
|
||||||
|
language_equals=get_language_equals(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,8 +55,19 @@ def _update_pool(media_type, profile_id=None):
|
||||||
return pool.update(
|
return pool.update(
|
||||||
get_providers(),
|
get_providers(),
|
||||||
get_providers_auth(),
|
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_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):
|
sonarr_episode_id, radarr_id):
|
||||||
language_code_convert_dict = {
|
language_code_convert_dict = {
|
||||||
'he': 'iw',
|
'he': 'iw',
|
||||||
'zt': 'zh-CN',
|
'zh': 'zh-CN',
|
||||||
'zh': 'zh-TW',
|
'zt': 'zh-TW',
|
||||||
}
|
}
|
||||||
|
|
||||||
to_lang = alpha3_from_alpha2(to_lang)
|
to_lang = alpha3_from_alpha2(to_lang)
|
||||||
|
|
|
@ -9,7 +9,7 @@ from functools import reduce
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
from app.database import get_exclusion_clause, get_audio_profile_languages, TableShows, TableEpisodes, TableMovies, \
|
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.event_handler import show_progress, hide_progress
|
||||||
from app.get_providers import get_providers
|
from app.get_providers import get_providers
|
||||||
from app.notifier import send_notifications, send_notifications_movie
|
from app.notifier import send_notifications, send_notifications_movie
|
||||||
|
@ -217,7 +217,7 @@ def get_upgradable_episode_subtitles():
|
||||||
if not upgradable_episodes:
|
if not upgradable_episodes:
|
||||||
return []
|
return []
|
||||||
else:
|
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 "
|
logging.debug(f"{len(upgradable_episodes)} potentially upgradable episode subtitles have been found, let's "
|
||||||
f"filter them...")
|
f"filter them...")
|
||||||
|
|
||||||
|
@ -252,8 +252,32 @@ def get_upgradable_movies_subtitles():
|
||||||
if not upgradable_movies:
|
if not upgradable_movies:
|
||||||
return []
|
return []
|
||||||
else:
|
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 "
|
logging.debug(f"{len(upgradable_movies)} potentially upgradable movie subtitles have been found, let's filter "
|
||||||
f"them...")
|
f"them...")
|
||||||
|
|
||||||
return parse_upgradable_list(upgradable_list=upgradable_movies, perfect_score=117, media_type='movie')
|
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 logging
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from charamel import Detector
|
from chardet import detect
|
||||||
from bs4 import UnicodeDammit
|
from bs4 import UnicodeDammit
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
|
@ -64,8 +64,7 @@ def force_unicode(s):
|
||||||
try:
|
try:
|
||||||
s = s.decode("utf-8")
|
s = s.decode("utf-8")
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
detector = Detector()
|
t = detect(s)['encoding']
|
||||||
t = detector.detect(s)
|
|
||||||
try:
|
try:
|
||||||
s = s.decode(t)
|
s = s.decode(t)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
|
|
|
@ -16,13 +16,19 @@ def _handle_alpha3(detected_language: dict):
|
||||||
alpha3 = detected_language["language"].alpha3
|
alpha3 = detected_language["language"].alpha3
|
||||||
custom = CustomLanguage.from_value(alpha3, "official_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)
|
logging.debug("Custom embedded language found: %s", custom.name)
|
||||||
return custom.alpha3
|
return custom.alpha3
|
||||||
|
|
||||||
return alpha3
|
return alpha3
|
||||||
|
|
||||||
|
|
||||||
def embedded_subs_reader(file, file_size, episode_file_id=None, movie_file_id=None, use_cache=True):
|
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)
|
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)
|
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
|
return subtitles_list
|
||||||
|
|
||||||
cache_provider = None
|
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'
|
cache_provider = 'ffprobe'
|
||||||
elif 'mediainfo' in data and data["mediainfo"] and "subtitle" in data["mediainfo"]:
|
elif 'mediainfo' in data and data["mediainfo"] and "subtitle" in data["mediainfo"]:
|
||||||
cache_provider = '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
|
return audio_list
|
||||||
|
|
||||||
cache_provider = None
|
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'
|
cache_provider = 'ffprobe'
|
||||||
elif 'mediainfo' in data and data["mediainfo"] and "audio" in data["mediainfo"]:
|
elif 'mediainfo' in data and data["mediainfo"] and "audio" in data["mediainfo"]:
|
||||||
cache_provider = '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)
|
audio_list.append(None)
|
||||||
continue
|
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:
|
if language not in audio_list:
|
||||||
audio_list.append(language)
|
audio_list.append(language)
|
||||||
|
|
|
@ -7,3 +7,4 @@ pytest-cov
|
||||||
pytest-vcr
|
pytest-vcr
|
||||||
pytest-mock
|
pytest-mock
|
||||||
requests-mock
|
requests-mock
|
||||||
|
setuptools
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,17 +13,17 @@
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^5.6.0",
|
"@mantine/core": "^6.0.0",
|
||||||
"@mantine/dropzone": "^5.6.0",
|
"@mantine/dropzone": "^6.0.0",
|
||||||
"@mantine/form": "^5.6.0",
|
"@mantine/form": "^6.0.0",
|
||||||
"@mantine/hooks": "^5.6.0",
|
"@mantine/hooks": "^6.0.0",
|
||||||
"@mantine/modals": "^5.6.0",
|
"@mantine/modals": "^6.0.0",
|
||||||
"@mantine/notifications": "^5.6.0",
|
"@mantine/notifications": "^6.0.0",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-query": "^3.39.2",
|
"react-query": "^3.39.2",
|
||||||
"react-router-dom": "~6.3.0",
|
"react-router-dom": "~6.10.0",
|
||||||
"socket.io-client": "^4.5.3"
|
"socket.io-client": "^4.5.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -34,36 +34,35 @@
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^12.1.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/react-hooks": "^8.0.1",
|
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@types/lodash": "^4.14.0",
|
"@types/lodash": "^4.14.0",
|
||||||
"@types/node": "^18.11.7",
|
"@types/node": "^18.16.0",
|
||||||
"@types/react": "^17.0.0",
|
"@types/react": "^18.2.0",
|
||||||
"@types/react-dom": "^17.0.0",
|
"@types/react-dom": "^18.2.0",
|
||||||
"@types/react-table": "^7.7.0",
|
"@types/react-table": "^7.7.0",
|
||||||
"@vitejs/plugin-react": "^2.2.0",
|
"@vitejs/plugin-react": "^4.0.0",
|
||||||
"@vitest/coverage-c8": "^0.25.0",
|
"vitest": "^0.30.1",
|
||||||
"@vitest/ui": "^0.29.1",
|
"@vitest/coverage-c8": "^0.30.0",
|
||||||
|
"@vitest/ui": "^0.30.0",
|
||||||
"clsx": "^1.2.0",
|
"clsx": "^1.2.0",
|
||||||
"eslint": "^8.26.0",
|
"eslint": "^8.39.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-testing-library": "^5.9.0",
|
"eslint-plugin-testing-library": "^5.9.0",
|
||||||
"husky": "^8.0.2",
|
"husky": "^8.0.2",
|
||||||
"jsdom": "^20.0.1",
|
"jsdom": "^21.0.0",
|
||||||
"lodash": "^4.17.0",
|
"lodash": "^4.17.0",
|
||||||
"moment": "^2.29",
|
"moment": "^2.29",
|
||||||
"prettier": "^2.7.0",
|
"prettier": "^2.8.0",
|
||||||
"prettier-plugin-organize-imports": "^3.1.0",
|
"prettier-plugin-organize-imports": "^3.1.0",
|
||||||
"pretty-quick": "^3.1.0",
|
"pretty-quick": "^3.1.0",
|
||||||
"react-table": "^7.8.0",
|
"react-table": "^7.8.0",
|
||||||
"recharts": "~2.4.3",
|
"recharts": "~2.5.0",
|
||||||
"sass": "^1.55.0",
|
"sass": "^1.62.0",
|
||||||
"typescript": "^4",
|
"typescript": "^5",
|
||||||
"vite": "^3.2.1",
|
"vite": "^4.3.0",
|
||||||
"vite-plugin-checker": "^0.5.5",
|
"vite-plugin-checker": "^0.5.5"
|
||||||
"vitest": "^0.25.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
|
|
|
@ -15,12 +15,12 @@ import {
|
||||||
Avatar,
|
Avatar,
|
||||||
Badge,
|
Badge,
|
||||||
Burger,
|
Burger,
|
||||||
createStyles,
|
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Header,
|
Header,
|
||||||
MediaQuery,
|
MediaQuery,
|
||||||
Menu,
|
Menu,
|
||||||
|
createStyles,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent } from "react";
|
||||||
|
|
||||||
|
|
|
@ -357,9 +357,11 @@ const NavbarItem: FunctionComponent<NavbarItemProps> = ({
|
||||||
></FontAwesomeIcon>
|
></FontAwesomeIcon>
|
||||||
)}
|
)}
|
||||||
{name}
|
{name}
|
||||||
<Badge className={classes.badge} radius="xs" hidden={shouldHideBadge}>
|
{shouldHideBadge === false && (
|
||||||
{badge}
|
<Badge className={classes.badge} radius="xs">
|
||||||
</Badge>
|
{badge}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import AppNavbar from "@/App/Navbar";
|
import AppNavbar from "@/App/Navbar";
|
||||||
|
import { RouterNames } from "@/Router/RouterNames";
|
||||||
import ErrorBoundary from "@/components/ErrorBoundary";
|
import ErrorBoundary from "@/components/ErrorBoundary";
|
||||||
import { Layout } from "@/constants";
|
import { Layout } from "@/constants";
|
||||||
import NavbarProvider from "@/contexts/Navbar";
|
import NavbarProvider from "@/contexts/Navbar";
|
||||||
import OnlineProvider from "@/contexts/Online";
|
import OnlineProvider from "@/contexts/Online";
|
||||||
import { notification } from "@/modules/task";
|
import { notification } from "@/modules/task";
|
||||||
import CriticalError from "@/pages/errors/CriticalError";
|
import CriticalError from "@/pages/errors/CriticalError";
|
||||||
import { RouterNames } from "@/Router/RouterNames";
|
|
||||||
import { Environment } from "@/utilities";
|
import { Environment } from "@/utilities";
|
||||||
import { AppShell } from "@mantine/core";
|
import { AppShell } from "@mantine/core";
|
||||||
import { useWindowEvent } from "@mantine/hooks";
|
import { useWindowEvent } from "@mantine/hooks";
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useSystemSettings } from "@/apis/hooks";
|
||||||
import {
|
import {
|
||||||
ColorScheme,
|
ColorScheme,
|
||||||
ColorSchemeProvider,
|
ColorSchemeProvider,
|
||||||
|
@ -6,7 +7,13 @@ import {
|
||||||
MantineThemeOverride,
|
MantineThemeOverride,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useColorScheme } from "@mantine/hooks";
|
import { useColorScheme } from "@mantine/hooks";
|
||||||
import { FunctionComponent, useCallback, useEffect, useState } from "react";
|
import {
|
||||||
|
FunctionComponent,
|
||||||
|
PropsWithChildren,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
const theme: MantineThemeOverride = {
|
const theme: MantineThemeOverride = {
|
||||||
fontFamily: "Roboto, open sans, Helvetica Neue, Helvetica, Arial, sans-serif",
|
fontFamily: "Roboto, open sans, Helvetica Neue, Helvetica, Arial, sans-serif",
|
||||||
|
@ -28,7 +35,19 @@ const theme: MantineThemeOverride = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function useAutoColorScheme() {
|
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);
|
const [colorScheme, setColorScheme] = useState(preferredColorScheme);
|
||||||
|
|
||||||
// automatically switch dark/light theme
|
// automatically switch dark/light theme
|
||||||
|
@ -45,7 +64,7 @@ function useAutoColorScheme() {
|
||||||
|
|
||||||
const emotionCache = createEmotionCache({ key: "bazarr" });
|
const emotionCache = createEmotionCache({ key: "bazarr" });
|
||||||
|
|
||||||
const ThemeProvider: FunctionComponent = ({ children }) => {
|
const ThemeProvider: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||||
const { colorScheme, toggleColorScheme } = useAutoColorScheme();
|
const { colorScheme, toggleColorScheme } = useAutoColorScheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
import App from "@/App";
|
||||||
import { useBadges } from "@/apis/hooks";
|
import { useBadges } from "@/apis/hooks";
|
||||||
import { useEnabledStatus } from "@/apis/hooks/site";
|
import { useEnabledStatus } from "@/apis/hooks/site";
|
||||||
import App from "@/App";
|
|
||||||
import { Lazy } from "@/components/async";
|
import { Lazy } from "@/components/async";
|
||||||
import Authentication from "@/pages/Authentication";
|
import Authentication from "@/pages/Authentication";
|
||||||
import BlacklistMoviesView from "@/pages/Blacklist/Movies";
|
import BlacklistMoviesView from "@/pages/Blacklist/Movies";
|
||||||
import BlacklistSeriesView from "@/pages/Blacklist/Series";
|
import BlacklistSeriesView from "@/pages/Blacklist/Series";
|
||||||
import Episodes from "@/pages/Episodes";
|
import Episodes from "@/pages/Episodes";
|
||||||
import NotFound from "@/pages/errors/NotFound";
|
|
||||||
import MoviesHistoryView from "@/pages/History/Movies";
|
import MoviesHistoryView from "@/pages/History/Movies";
|
||||||
import SeriesHistoryView from "@/pages/History/Series";
|
import SeriesHistoryView from "@/pages/History/Series";
|
||||||
import MovieView from "@/pages/Movies";
|
import MovieView from "@/pages/Movies";
|
||||||
|
@ -31,6 +30,7 @@ import SystemReleasesView from "@/pages/System/Releases";
|
||||||
import SystemTasksView from "@/pages/System/Tasks";
|
import SystemTasksView from "@/pages/System/Tasks";
|
||||||
import WantedMoviesView from "@/pages/Wanted/Movies";
|
import WantedMoviesView from "@/pages/Wanted/Movies";
|
||||||
import WantedSeriesView from "@/pages/Wanted/Series";
|
import WantedSeriesView from "@/pages/Wanted/Series";
|
||||||
|
import NotFound from "@/pages/errors/NotFound";
|
||||||
import { Environment } from "@/utilities";
|
import { Environment } from "@/utilities";
|
||||||
import {
|
import {
|
||||||
faClock,
|
faClock,
|
||||||
|
@ -42,13 +42,13 @@ import {
|
||||||
faPlay,
|
faPlay,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import {
|
import {
|
||||||
createContext,
|
|
||||||
FunctionComponent,
|
FunctionComponent,
|
||||||
|
createContext,
|
||||||
lazy,
|
lazy,
|
||||||
useContext,
|
useContext,
|
||||||
useMemo,
|
useMemo,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||||
import Redirector from "./Redirector";
|
import Redirector from "./Redirector";
|
||||||
import { RouterNames } from "./RouterNames";
|
import { RouterNames } from "./RouterNames";
|
||||||
import { CustomRouteObject } from "./type";
|
import { CustomRouteObject } from "./type";
|
||||||
|
@ -315,12 +315,18 @@ function useRoutes(): CustomRouteObject[] {
|
||||||
|
|
||||||
const RouterItemContext = createContext<CustomRouteObject[]>([]);
|
const RouterItemContext = createContext<CustomRouteObject[]>([]);
|
||||||
|
|
||||||
export const Router: FunctionComponent = ({ children }) => {
|
export const Router: FunctionComponent = () => {
|
||||||
const routes = useRoutes();
|
const routes = useRoutes();
|
||||||
|
|
||||||
|
// TODO: Move this outside the function component scope
|
||||||
|
const router = useMemo(
|
||||||
|
() => createBrowserRouter(routes, { basename: Environment.baseUrl }),
|
||||||
|
[routes]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouterItemContext.Provider value={routes}>
|
<RouterItemContext.Provider value={routes}>
|
||||||
<BrowserRouter basename={Environment.baseUrl}>{children}</BrowserRouter>
|
<RouterProvider router={router}></RouterProvider>
|
||||||
</RouterItemContext.Provider>
|
</RouterItemContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,9 +3,9 @@ import { usePageSize } from "@/utilities/storage";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
QueryKey,
|
QueryKey,
|
||||||
|
UseQueryResult,
|
||||||
useQuery,
|
useQuery,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
UseQueryResult,
|
|
||||||
} from "react-query";
|
} from "react-query";
|
||||||
import { QueryKeys } from "./keys";
|
import { QueryKeys } from "./keys";
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import UIError from "@/pages/errors/UIError";
|
import UIError from "@/pages/errors/UIError";
|
||||||
import { Component } from "react";
|
import { Component, PropsWithChildren } from "react";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ErrorBoundary extends Component<object, State> {
|
class ErrorBoundary extends Component<PropsWithChildren, State> {
|
||||||
constructor(props: object) {
|
constructor(props: object) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { error: null };
|
this.state = { error: null };
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { LoadingOverlay } from "@mantine/core";
|
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>;
|
return <Suspense fallback={<LoadingOverlay visible />}>{children}</Suspense>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 { useMovieSubtitleModification } from "@/apis/hooks";
|
||||||
import { useModals, withModal } from "@/modules/modals";
|
import { useModals, withModal } from "@/modules/modals";
|
||||||
import { task, TaskGroup } from "@/modules/task";
|
import { TaskGroup, task } from "@/modules/task";
|
||||||
import { useTableStyles } from "@/styles";
|
import { useTableStyles } from "@/styles";
|
||||||
import { useArrayAction, useSelectorOptions } from "@/utilities";
|
import { useArrayAction, useSelectorOptions } from "@/utilities";
|
||||||
import FormUtils from "@/utilities/form";
|
import FormUtils from "@/utilities/form";
|
||||||
|
@ -28,9 +28,9 @@ import { useForm } from "@mantine/form";
|
||||||
import { isString } from "lodash";
|
import { isString } from "lodash";
|
||||||
import { FunctionComponent, useEffect, useMemo } from "react";
|
import { FunctionComponent, useEffect, useMemo } from "react";
|
||||||
import { Column } from "react-table";
|
import { Column } from "react-table";
|
||||||
|
import TextPopover from "../TextPopover";
|
||||||
import { Action, Selector } from "../inputs";
|
import { Action, Selector } from "../inputs";
|
||||||
import { SimpleTable } from "../tables";
|
import { SimpleTable } from "../tables";
|
||||||
import TextPopover from "../TextPopover";
|
|
||||||
|
|
||||||
type SubtitleFile = {
|
type SubtitleFile = {
|
||||||
file: File;
|
file: File;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export * from "./inputs";
|
|
||||||
export { default as Search } from "./Search";
|
export { default as Search } from "./Search";
|
||||||
|
export * from "./inputs";
|
||||||
export * from "./tables";
|
export * from "./tables";
|
||||||
export { default as Toolbox } from "./toolbox";
|
export { default as Toolbox } from "./toolbox";
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
faXmark,
|
faXmark,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
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 { Dropzone } from "@mantine/dropzone";
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent } from "react";
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,9 @@ export const FileBrowser: FunctionComponent<FileBrowserProps> = ({
|
||||||
placeholder="Click to start"
|
placeholder="Click to start"
|
||||||
data={data}
|
data={data}
|
||||||
value={value}
|
value={value}
|
||||||
|
// Temporary solution of infinite dropdown items, fix later
|
||||||
|
limit={NaN}
|
||||||
|
maxDropdownHeight={240}
|
||||||
filter={(value, item) => {
|
filter={(value, item) => {
|
||||||
if (item.value === backKey) {
|
if (item.value === backKey) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -93,6 +93,7 @@ export function Selector<T>({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
|
withinPortal={true}
|
||||||
data={data}
|
data={data}
|
||||||
defaultValue={wrappedDefaultValue}
|
defaultValue={wrappedDefaultValue}
|
||||||
value={wrappedValue}
|
value={wrappedValue}
|
||||||
|
|
|
@ -12,11 +12,11 @@ import { Badge, Center, Text } from "@mantine/core";
|
||||||
import { FunctionComponent, useMemo } from "react";
|
import { FunctionComponent, useMemo } from "react";
|
||||||
import { Column } from "react-table";
|
import { Column } from "react-table";
|
||||||
import { PageTable } from "..";
|
import { PageTable } from "..";
|
||||||
|
import TextPopover from "../TextPopover";
|
||||||
import MutateAction from "../async/MutateAction";
|
import MutateAction from "../async/MutateAction";
|
||||||
import QueryOverlay from "../async/QueryOverlay";
|
import QueryOverlay from "../async/QueryOverlay";
|
||||||
import { HistoryIcon } from "../bazarr";
|
import { HistoryIcon } from "../bazarr";
|
||||||
import Language from "../bazarr/Language";
|
import Language from "../bazarr/Language";
|
||||||
import TextPopover from "../TextPopover";
|
|
||||||
|
|
||||||
interface MovieHistoryViewProps {
|
interface MovieHistoryViewProps {
|
||||||
movie: Item.Movie;
|
movie: Item.Movie;
|
||||||
|
|
|
@ -105,7 +105,7 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) {
|
||||||
</Anchor>
|
</Anchor>
|
||||||
);
|
);
|
||||||
} else {
|
} 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>>(
|
export const MovieSearchModal = withModal<Props<Item.Movie>>(
|
||||||
ManualSearchView,
|
ManualSearchView,
|
||||||
"movie-manual-search",
|
"movie-manual-search",
|
||||||
{ title: "Search Subtitles", size: "xl" }
|
{ title: "Search Subtitles", size: "calc(100vw - 4rem)" }
|
||||||
);
|
);
|
||||||
export const EpisodeSearchModal = withModal<Props<Item.Episode>>(
|
export const EpisodeSearchModal = withModal<Props<Item.Episode>>(
|
||||||
ManualSearchView,
|
ManualSearchView,
|
||||||
"episode-manual-search",
|
"episode-manual-search",
|
||||||
{ title: "Search Subtitles", size: "xl" }
|
{ title: "Search Subtitles", size: "calc(100vw - 4rem)" }
|
||||||
);
|
);
|
||||||
|
|
||||||
const StateIcon: FunctionComponent<{ matches: string[]; dont: string[] }> = ({
|
const StateIcon: FunctionComponent<{ matches: string[]; dont: string[] }> = ({
|
||||||
|
@ -237,7 +237,7 @@ const StateIcon: FunctionComponent<{ matches: string[]; dont: string[] }> = ({
|
||||||
const { ref, hovered } = useHover();
|
const { ref, hovered } = useHover();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover opened={hovered} position="top" width={360} withArrow>
|
<Popover opened={hovered} position="top" width={360} withArrow withinPortal>
|
||||||
<Popover.Target>
|
<Popover.Target>
|
||||||
<Text color={hasIssues ? "yellow" : "green"} ref={ref}>
|
<Text color={hasIssues ? "yellow" : "green"} ref={ref}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { SimpleTable } from "@/components/tables";
|
||||||
import { useCustomSelection } from "@/components/tables/plugins";
|
import { useCustomSelection } from "@/components/tables/plugins";
|
||||||
import { withModal } from "@/modules/modals";
|
import { withModal } from "@/modules/modals";
|
||||||
import { isMovie } from "@/utilities";
|
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 { FunctionComponent, useMemo, useState } from "react";
|
||||||
import { Column, useRowSelect } from "react-table";
|
import { Column, useRowSelect } from "react-table";
|
||||||
|
|
||||||
|
@ -60,9 +60,9 @@ const SubtitleToolView: FunctionComponent<SubtitleToolViewProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
return path.slice(idx + 1);
|
return <Text>{path.slice(idx + 1)}</Text>;
|
||||||
} else {
|
} else {
|
||||||
return path;
|
return <Text>{path}</Text>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,7 +30,7 @@ const PageControl: FunctionComponent<Props> = ({
|
||||||
<Pagination
|
<Pagination
|
||||||
size="sm"
|
size="sm"
|
||||||
color={isLoading ? "gray" : "primary"}
|
color={isLoading ? "gray" : "primary"}
|
||||||
page={index + 1}
|
value={index + 1}
|
||||||
onChange={(page) => goto(page - 1)}
|
onChange={(page) => goto(page - 1)}
|
||||||
hidden={count <= 1}
|
hidden={count <= 1}
|
||||||
total={count}
|
total={count}
|
||||||
|
|
|
@ -3,8 +3,8 @@ import { useEffect } from "react";
|
||||||
import { usePagination, useTable } from "react-table";
|
import { usePagination, useTable } from "react-table";
|
||||||
import BaseTable from "./BaseTable";
|
import BaseTable from "./BaseTable";
|
||||||
import PageControl from "./PageControl";
|
import PageControl from "./PageControl";
|
||||||
import { useDefaultSettings } from "./plugins";
|
|
||||||
import { SimpleTableProps } from "./SimpleTable";
|
import { SimpleTableProps } from "./SimpleTable";
|
||||||
|
import { useDefaultSettings } from "./plugins";
|
||||||
|
|
||||||
type Props<T extends object> = SimpleTableProps<T> & {
|
type Props<T extends object> = SimpleTableProps<T> & {
|
||||||
autoScroll?: boolean;
|
autoScroll?: boolean;
|
||||||
|
|
|
@ -4,12 +4,12 @@ import {
|
||||||
CellProps,
|
CellProps,
|
||||||
Column,
|
Column,
|
||||||
ColumnInstance,
|
ColumnInstance,
|
||||||
ensurePluginOrder,
|
|
||||||
HeaderProps,
|
HeaderProps,
|
||||||
Hooks,
|
Hooks,
|
||||||
MetaBase,
|
MetaBase,
|
||||||
TableInstance,
|
TableInstance,
|
||||||
TableToggleCommonProps,
|
TableToggleCommonProps,
|
||||||
|
ensurePluginOrder,
|
||||||
} from "react-table";
|
} from "react-table";
|
||||||
|
|
||||||
const pluginName = "useCustomSelection";
|
const pluginName = "useCustomSelection";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createStyles, Group } from "@mantine/core";
|
import { createStyles, Group } from "@mantine/core";
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent, PropsWithChildren } from "react";
|
||||||
import ToolboxButton, { ToolboxMutateButton } from "./Button";
|
import ToolboxButton, { ToolboxMutateButton } from "./Button";
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
|
@ -11,7 +11,7 @@ const useStyles = createStyles((theme) => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
declare type ToolboxComp = FunctionComponent & {
|
declare type ToolboxComp = FunctionComponent<PropsWithChildren> & {
|
||||||
Button: typeof ToolboxButton;
|
Button: typeof ToolboxButton;
|
||||||
MutateButton: typeof ToolboxMutateButton;
|
MutateButton: typeof ToolboxMutateButton;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import { createRoot } from "react-dom/client";
|
||||||
import { useRoutes } from "react-router-dom";
|
import { Router } from "./Router";
|
||||||
import { AllProviders } from "./providers";
|
import { AllProviders } from "./providers";
|
||||||
import { useRouteItems } from "./Router";
|
|
||||||
|
|
||||||
const RouteApp = () => {
|
const container = document.getElementById("root");
|
||||||
const items = useRouteItems();
|
|
||||||
|
|
||||||
return useRoutes(items);
|
if (container === null) {
|
||||||
};
|
Error("Cannot initialize app, root not found");
|
||||||
|
} else {
|
||||||
ReactDOM.render(
|
const root = createRoot(container);
|
||||||
<StrictMode>
|
root.render(
|
||||||
<AllProviders>
|
<StrictMode>
|
||||||
<RouteApp />
|
<AllProviders>
|
||||||
</AllProviders>
|
<Router />
|
||||||
</StrictMode>,
|
</AllProviders>
|
||||||
document.getElementById("root")
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -2,19 +2,19 @@ import {
|
||||||
ModalsProvider as MantineModalsProvider,
|
ModalsProvider as MantineModalsProvider,
|
||||||
ModalsProviderProps as MantineModalsProviderProps,
|
ModalsProviderProps as MantineModalsProviderProps,
|
||||||
} from "@mantine/modals";
|
} from "@mantine/modals";
|
||||||
import { FunctionComponent, useMemo } from "react";
|
import { FunctionComponent, PropsWithChildren, useMemo } from "react";
|
||||||
import { ModalComponent, StaticModals } from "./WithModal";
|
import { ModalComponent, StaticModals } from "./WithModal";
|
||||||
|
|
||||||
const DefaultModalProps: MantineModalsProviderProps["modalProps"] = {
|
const DefaultModalProps: MantineModalsProviderProps["modalProps"] = {
|
||||||
centered: true,
|
centered: true,
|
||||||
styles: {
|
styles: {
|
||||||
modal: {
|
// modals: {
|
||||||
maxWidth: "100%",
|
// maxWidth: "100%",
|
||||||
},
|
// },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModalsProvider: FunctionComponent = ({ children }) => {
|
const ModalsProvider: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||||
const modals = useMemo(
|
const modals = useMemo(
|
||||||
() =>
|
() =>
|
||||||
StaticModals.reduce<Record<string, ModalComponent>>((prev, curr) => {
|
StaticModals.reduce<Record<string, ModalComponent>>((prev, curr) => {
|
||||||
|
|
|
@ -5,8 +5,11 @@ import { useCallback, useContext, useMemo } from "react";
|
||||||
import { ModalComponent, ModalIdContext } from "./WithModal";
|
import { ModalComponent, ModalIdContext } from "./WithModal";
|
||||||
|
|
||||||
export function useModals() {
|
export function useModals() {
|
||||||
const { openContextModal: openMantineContextModal, ...rest } =
|
const {
|
||||||
useMantineModals();
|
openContextModal: openMantineContextModal,
|
||||||
|
closeContextModal: closeContextModalRaw,
|
||||||
|
...rest
|
||||||
|
} = useMantineModals();
|
||||||
|
|
||||||
const openContextModal = useCallback(
|
const openContextModal = useCallback(
|
||||||
<ARGS extends {}>(
|
<ARGS extends {}>(
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export * from "./hooks";
|
|
||||||
export { default as ModalsProvider } from "./ModalsProvider";
|
export { default as ModalsProvider } from "./ModalsProvider";
|
||||||
export { default as withModal } from "./WithModal";
|
export { default as withModal } from "./WithModal";
|
||||||
|
export * from "./hooks";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { debounce, forIn, remove, uniq } from "lodash";
|
import { debounce, forIn, remove, uniq } from "lodash";
|
||||||
import { onlineManager } from "react-query";
|
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 { Environment, isDevEnv, isTestEnv } from "../../utilities";
|
||||||
import { ENSURE, GROUP, LOG } from "../../utilities/console";
|
import { ENSURE, GROUP, LOG } from "../../utilities/console";
|
||||||
import { createDefaultReducer } from "./reducer";
|
import { createDefaultReducer } from "./reducer";
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RouterNames } from "@/Router/RouterNames";
|
||||||
import {
|
import {
|
||||||
useEpisodesBySeriesId,
|
useEpisodesBySeriesId,
|
||||||
useIsAnyActionRunning,
|
useIsAnyActionRunning,
|
||||||
|
@ -11,9 +12,8 @@ import { ItemEditModal } from "@/components/forms/ItemEditForm";
|
||||||
import { SeriesUploadModal } from "@/components/forms/SeriesUploadForm";
|
import { SeriesUploadModal } from "@/components/forms/SeriesUploadForm";
|
||||||
import { SubtitleToolsModal } from "@/components/modals";
|
import { SubtitleToolsModal } from "@/components/modals";
|
||||||
import { useModals } from "@/modules/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 ItemOverview from "@/pages/views/ItemOverview";
|
||||||
import { RouterNames } from "@/Router/RouterNames";
|
|
||||||
import { useLanguageProfileBy } from "@/utilities/languages";
|
import { useLanguageProfileBy } from "@/utilities/languages";
|
||||||
import {
|
import {
|
||||||
faAdjust,
|
faAdjust,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { useDownloadEpisodeSubtitles, useEpisodesProvider } from "@/apis/hooks";
|
import { useDownloadEpisodeSubtitles, useEpisodesProvider } from "@/apis/hooks";
|
||||||
import { useShowOnlyDesired } from "@/apis/hooks/site";
|
import { useShowOnlyDesired } from "@/apis/hooks/site";
|
||||||
import { Action, GroupTable } from "@/components";
|
import { Action, GroupTable } from "@/components";
|
||||||
|
import TextPopover from "@/components/TextPopover";
|
||||||
import { AudioList } from "@/components/bazarr";
|
import { AudioList } from "@/components/bazarr";
|
||||||
import { EpisodeHistoryModal } from "@/components/modals";
|
import { EpisodeHistoryModal } from "@/components/modals";
|
||||||
import { EpisodeSearchModal } from "@/components/modals/ManualSearchModal";
|
import { EpisodeSearchModal } from "@/components/modals/ManualSearchModal";
|
||||||
import TextPopover from "@/components/TextPopover";
|
|
||||||
import { useModals } from "@/modules/modals";
|
import { useModals } from "@/modules/modals";
|
||||||
import { useTableStyles } from "@/styles";
|
import { useTableStyles } from "@/styles";
|
||||||
import { BuildKey, filterSubtitleBy } from "@/utilities";
|
import { BuildKey, filterSubtitleBy } from "@/utilities";
|
||||||
|
@ -84,7 +84,7 @@ const Table: FunctionComponent<Props> = ({ episodes, profile, disabled }) => {
|
||||||
{
|
{
|
||||||
accessor: "season",
|
accessor: "season",
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
return `Season ${row.value}`;
|
return <Text>Season {row.value}</Text>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,8 +11,8 @@ import { useSelectorOptions } from "@/utilities";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Container,
|
Container,
|
||||||
createStyles,
|
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
|
createStyles,
|
||||||
useMantineTheme,
|
useMantineTheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useDocumentTitle } from "@mantine/hooks";
|
import { useDocumentTitle } from "@mantine/hooks";
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RouterNames } from "@/Router/RouterNames";
|
||||||
import {
|
import {
|
||||||
useDownloadMovieSubtitles,
|
useDownloadMovieSubtitles,
|
||||||
useIsMovieActionRunning,
|
useIsMovieActionRunning,
|
||||||
|
@ -15,9 +16,8 @@ import { MovieUploadModal } from "@/components/forms/MovieUploadForm";
|
||||||
import { MovieHistoryModal, SubtitleToolsModal } from "@/components/modals";
|
import { MovieHistoryModal, SubtitleToolsModal } from "@/components/modals";
|
||||||
import { MovieSearchModal } from "@/components/modals/ManualSearchModal";
|
import { MovieSearchModal } from "@/components/modals/ManualSearchModal";
|
||||||
import { useModals } from "@/modules/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 ItemOverview from "@/pages/views/ItemOverview";
|
||||||
import { RouterNames } from "@/Router/RouterNames";
|
|
||||||
import { useLanguageProfileBy } from "@/utilities/languages";
|
import { useLanguageProfileBy } from "@/utilities/languages";
|
||||||
import {
|
import {
|
||||||
faCloudUploadAlt,
|
faCloudUploadAlt,
|
||||||
|
|
|
@ -65,15 +65,19 @@ const MovieView: FunctionComponent = () => {
|
||||||
accessor: "missing_subtitles",
|
accessor: "missing_subtitles",
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
const missing = row.value;
|
const missing = row.value;
|
||||||
return missing.map((v) => (
|
return (
|
||||||
<Badge
|
<>
|
||||||
mr="xs"
|
{missing.map((v) => (
|
||||||
color="yellow"
|
<Badge
|
||||||
key={BuildKey(v.code2, v.hi, v.forced)}
|
mr="xs"
|
||||||
>
|
color="yellow"
|
||||||
<Language.Text value={v}></Language.Text>
|
key={BuildKey(v.code2, v.hi, v.forced)}
|
||||||
</Badge>
|
>
|
||||||
));
|
<Language.Text value={v}></Language.Text>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
|
@ -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";
|
} from "../keys";
|
||||||
import { useSettingValue } from "../utilities/hooks";
|
import { useSettingValue } from "../utilities/hooks";
|
||||||
import { LanguageSelector, ProfileSelector } from "./components";
|
import { LanguageSelector, ProfileSelector } from "./components";
|
||||||
|
import EqualsTable from "./equals";
|
||||||
import Table from "./table";
|
import Table from "./table";
|
||||||
|
|
||||||
export function useLatestEnabledLanguages() {
|
export function useLatestEnabledLanguages() {
|
||||||
|
@ -69,6 +70,13 @@ const SettingsLanguagesView: FunctionComponent = () => {
|
||||||
></LanguageSelector>
|
></LanguageSelector>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
<Section header="Language Equals">
|
||||||
|
<Message>
|
||||||
|
Treat the following languages as equal across all providers.
|
||||||
|
</Message>
|
||||||
|
<EqualsTable></EqualsTable>
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Section header="Embedded Tracks Language">
|
<Section header="Embedded Tracks Language">
|
||||||
<Check
|
<Check
|
||||||
label="Deep analyze media file to get audio tracks language."
|
label="Deep analyze media file to get audio tracks language."
|
||||||
|
@ -91,7 +99,6 @@ const SettingsLanguagesView: FunctionComponent = () => {
|
||||||
}}
|
}}
|
||||||
></Selector>
|
></Selector>
|
||||||
</CollapseBox>
|
</CollapseBox>
|
||||||
|
|
||||||
<Selector
|
<Selector
|
||||||
clearable
|
clearable
|
||||||
settingKey={defaultUndEmbeddedSubtitlesLang}
|
settingKey={defaultUndEmbeddedSubtitlesLang}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Action, SimpleTable } from "@/components";
|
import { Action, SimpleTable } from "@/components";
|
||||||
import {
|
import {
|
||||||
anyCutoff,
|
|
||||||
ProfileEditModal,
|
ProfileEditModal,
|
||||||
|
anyCutoff,
|
||||||
} from "@/components/forms/ProfileEditForm";
|
} from "@/components/forms/ProfileEditForm";
|
||||||
import { useModals } from "@/modules/modals";
|
import { useModals } from "@/modules/modals";
|
||||||
import { BuildKey, useArrayAction } from "@/utilities";
|
import { BuildKey, useArrayAction } from "@/utilities";
|
||||||
|
@ -87,15 +87,19 @@ const Table: FunctionComponent = () => {
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
const items = row.value;
|
const items = row.value;
|
||||||
if (!items) {
|
if (!items) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
return items.map((v, idx) => {
|
return (
|
||||||
return (
|
<>
|
||||||
<Badge key={BuildKey(idx, v)} color="gray">
|
{items.map((v, idx) => {
|
||||||
{v}
|
return (
|
||||||
</Badge>
|
<Badge key={BuildKey(idx, v)} color="gray">
|
||||||
);
|
{v}
|
||||||
});
|
</Badge>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -104,15 +108,19 @@ const Table: FunctionComponent = () => {
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
const items = row.value;
|
const items = row.value;
|
||||||
if (!items) {
|
if (!items) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
return items.map((v, idx) => {
|
return (
|
||||||
return (
|
<>
|
||||||
<Badge key={BuildKey(idx, v)} color="gray">
|
{items.map((v, idx) => {
|
||||||
{v}
|
return (
|
||||||
</Badge>
|
<Badge key={BuildKey(idx, v)} color="gray">
|
||||||
);
|
{v}
|
||||||
});
|
</Badge>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -123,6 +123,7 @@ export const NotificationView: FunctionComponent = () => {
|
||||||
|
|
||||||
const update = useUpdateArray<Settings.NotificationInfo>(
|
const update = useUpdateArray<Settings.NotificationInfo>(
|
||||||
notificationsKey,
|
notificationsKey,
|
||||||
|
notifications ?? [],
|
||||||
"name"
|
"name"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,15 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Divider,
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
|
Text as MantineText,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
Text as MantineText,
|
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { capitalize } from "lodash";
|
import { capitalize } from "lodash";
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
|
||||||
FunctionComponent,
|
FunctionComponent,
|
||||||
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
|
@ -28,8 +28,8 @@ import {
|
||||||
useFormActions,
|
useFormActions,
|
||||||
useStagedValues,
|
useStagedValues,
|
||||||
} from "../utilities/FormValues";
|
} from "../utilities/FormValues";
|
||||||
import { useSettingValue } from "../utilities/hooks";
|
|
||||||
import { SettingsProvider, useSettings } from "../utilities/SettingsProvider";
|
import { SettingsProvider, useSettings } from "../utilities/SettingsProvider";
|
||||||
|
import { useSettingValue } from "../utilities/hooks";
|
||||||
import { ProviderInfo, ProviderList } from "./list";
|
import { ProviderInfo, ProviderList } from "./list";
|
||||||
|
|
||||||
const ProviderKey = "settings-general-enabled_providers";
|
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: "napiprojekt", description: "Polish Subtitles Provider" },
|
||||||
{
|
{
|
||||||
key: "whisperai",
|
key: "whisperai",
|
||||||
|
@ -329,7 +309,13 @@ export const ProviderList: Readonly<ProviderInfo[]> = [
|
||||||
name: "Verify SSL",
|
name: "Verify SSL",
|
||||||
defaultValue: true,
|
defaultValue: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
key: "user_agent",
|
||||||
|
name: "User-agent header",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
message: "Make sure to use a unique and credible user agent.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "subs4free",
|
key: "subs4free",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { uiPageSizeKey } from "@/utilities/storage";
|
import { uiPageSizeKey } from "@/utilities/storage";
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent } from "react";
|
||||||
import { Layout, Section, Selector } from "../components";
|
import { Layout, Section, Selector } from "../components";
|
||||||
import { pageSizeOptions } from "./options";
|
import { colorSchemeOptions, pageSizeOptions } from "./options";
|
||||||
|
|
||||||
const SettingsUIView: FunctionComponent = () => {
|
const SettingsUIView: FunctionComponent = () => {
|
||||||
return (
|
return (
|
||||||
<Layout name="Interface">
|
<Layout name="Interface">
|
||||||
<Section header="UI">
|
<Section header="List View">
|
||||||
<Selector
|
<Selector
|
||||||
label="Page Size"
|
label="Page Size"
|
||||||
options={pageSizeOptions}
|
options={pageSizeOptions}
|
||||||
|
@ -14,6 +14,14 @@ const SettingsUIView: FunctionComponent = () => {
|
||||||
defaultValue={50}
|
defaultValue={50}
|
||||||
></Selector>
|
></Selector>
|
||||||
</Section>
|
</Section>
|
||||||
|
<Section header="Style">
|
||||||
|
<Selector
|
||||||
|
label="Theme"
|
||||||
|
options={colorSchemeOptions}
|
||||||
|
settingKey="settings-general-theme"
|
||||||
|
defaultValue={"auto"}
|
||||||
|
></Selector>
|
||||||
|
</Section>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,3 +26,18 @@ export const pageSizeOptions: SelectorOption<number>[] = [
|
||||||
value: 1000,
|
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 { Text } from "@mantine/core";
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent, PropsWithChildren } from "react";
|
||||||
|
|
||||||
export const Message: FunctionComponent<{
|
interface MessageProps {
|
||||||
type?: "warning" | "info";
|
type?: "warning" | "info";
|
||||||
}> = ({ type = "info", children }) => {
|
}
|
||||||
|
|
||||||
|
type Props = PropsWithChildren<MessageProps>;
|
||||||
|
|
||||||
|
export const Message: FunctionComponent<Props> = ({
|
||||||
|
type = "info",
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Text size="sm" color={type === "info" ? "dimmed" : "yellow"} my={0}>
|
<Text size="sm" color={type === "info" ? "dimmed" : "yellow"} my={0}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { Divider, Stack, Title } from "@mantine/core";
|
import { Divider, Stack, Title } from "@mantine/core";
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent, PropsWithChildren } from "react";
|
||||||
|
|
||||||
interface SectionProps {
|
interface SectionProps {
|
||||||
header: string;
|
header: string;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Section: FunctionComponent<SectionProps> = ({
|
type Props = PropsWithChildren<SectionProps>;
|
||||||
|
|
||||||
|
export const Section: FunctionComponent<Props> = ({
|
||||||
header,
|
header,
|
||||||
hidden,
|
hidden,
|
||||||
children,
|
children,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Collapse, Stack } from "@mantine/core";
|
import { Collapse, Stack } from "@mantine/core";
|
||||||
import { FunctionComponent, useMemo, useRef } from "react";
|
import { FunctionComponent, PropsWithChildren, useMemo, useRef } from "react";
|
||||||
import { useSettingValue } from "../utilities/hooks";
|
import { useSettingValue } from "../utilities/hooks";
|
||||||
|
|
||||||
interface ContentProps {
|
interface ContentProps {
|
||||||
|
@ -8,7 +8,9 @@ interface ContentProps {
|
||||||
indent?: boolean;
|
indent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollapseBox: FunctionComponent<ContentProps> = ({
|
type Props = PropsWithChildren<ContentProps>;
|
||||||
|
|
||||||
|
const CollapseBox: FunctionComponent<Props> = ({
|
||||||
on,
|
on,
|
||||||
indent,
|
indent,
|
||||||
children,
|
children,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { rawRender, RenderOptions, screen } from "@/tests";
|
import { rawRender, RenderOptions, screen } from "@/tests";
|
||||||
import { useForm } from "@mantine/form";
|
import { useForm } from "@mantine/form";
|
||||||
import { FunctionComponent, ReactElement } from "react";
|
import { FunctionComponent, PropsWithChildren, ReactElement } from "react";
|
||||||
import { describe, it } from "vitest";
|
import { describe, it } from "vitest";
|
||||||
import { FormContext, FormValues } from "../utilities/FormValues";
|
import { FormContext, FormValues } from "../utilities/FormValues";
|
||||||
import { Number, Text } from "./forms";
|
import { Number, Text } from "./forms";
|
||||||
|
|
||||||
const FormSupport: FunctionComponent = ({ children }) => {
|
const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => {
|
||||||
const form = useForm<FormValues>({
|
const form = useForm<FormValues>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
settings: {},
|
settings: {},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
Action as GlobalAction,
|
|
||||||
FileBrowser,
|
FileBrowser,
|
||||||
FileBrowserProps,
|
FileBrowserProps,
|
||||||
|
Action as GlobalAction,
|
||||||
MultiSelector as GlobalMultiSelector,
|
MultiSelector as GlobalMultiSelector,
|
||||||
MultiSelectorProps as GlobalMultiSelectorProps,
|
MultiSelectorProps as GlobalMultiSelectorProps,
|
||||||
Selector as GlobalSelector,
|
Selector as GlobalSelector,
|
||||||
|
@ -12,17 +12,17 @@ import ChipInput, { ChipInputProps } from "@/components/inputs/ChipInput";
|
||||||
import { useSliderMarks } from "@/utilities";
|
import { useSliderMarks } from "@/utilities";
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
|
Slider as MantineSlider,
|
||||||
|
SliderProps as MantineSliderProps,
|
||||||
NumberInput,
|
NumberInput,
|
||||||
NumberInputProps,
|
NumberInputProps,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
PasswordInputProps,
|
PasswordInputProps,
|
||||||
Slider as MantineSlider,
|
|
||||||
SliderProps as MantineSliderProps,
|
|
||||||
Switch,
|
Switch,
|
||||||
TextInput,
|
TextInput,
|
||||||
TextInputProps,
|
TextInputProps,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { FunctionComponent, ReactText } from "react";
|
import { FunctionComponent, ReactNode, ReactText } from "react";
|
||||||
import { BaseInput, useBaseInput } from "../utilities/hooks";
|
import { BaseInput, useBaseInput } from "../utilities/hooks";
|
||||||
|
|
||||||
export type NumberProps = BaseInput<number> & NumberInputProps;
|
export type NumberProps = BaseInput<number> & NumberInputProps;
|
||||||
|
@ -34,7 +34,10 @@ export const Number: FunctionComponent<NumberProps> = (props) => {
|
||||||
<NumberInput
|
<NumberInput
|
||||||
{...rest}
|
{...rest}
|
||||||
value={value ?? 0}
|
value={value ?? 0}
|
||||||
onChange={(val = 0) => {
|
onChange={(val) => {
|
||||||
|
if (val === "") {
|
||||||
|
val = 0;
|
||||||
|
}
|
||||||
update(val);
|
update(val);
|
||||||
}}
|
}}
|
||||||
></NumberInput>
|
></NumberInput>
|
||||||
|
@ -126,7 +129,9 @@ export function MultiSelector<T extends string | number>(
|
||||||
}
|
}
|
||||||
|
|
||||||
type SliderProps = BaseInput<number> &
|
type SliderProps = BaseInput<number> &
|
||||||
Omit<MantineSliderProps, "onChange" | "onChangeEnd" | "marks">;
|
Omit<MantineSliderProps, "onChange" | "onChangeEnd" | "marks" | "label"> & {
|
||||||
|
label?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
export const Slider: FunctionComponent<SliderProps> = (props) => {
|
export const Slider: FunctionComponent<SliderProps> = (props) => {
|
||||||
const { value, update, rest } = useBaseInput(props);
|
const { value, update, rest } = useBaseInput(props);
|
||||||
|
|
|
@ -63,11 +63,11 @@ export const URLTestButton: FunctionComponent<{
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from "./Card";
|
export * from "./Card";
|
||||||
export * from "./collapse";
|
|
||||||
export { default as CollapseBox } from "./collapse";
|
|
||||||
export * from "./forms";
|
|
||||||
export * from "./Layout";
|
export * from "./Layout";
|
||||||
export { default as Layout } from "./Layout";
|
export { default as Layout } from "./Layout";
|
||||||
export * from "./Message";
|
export * from "./Message";
|
||||||
export * from "./pathMapper";
|
|
||||||
export * from "./Section";
|
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 languageProfileKey = "languages-profiles";
|
||||||
export const notificationsKey = "notifications-providers";
|
export const notificationsKey = "notifications-providers";
|
||||||
|
|
||||||
|
export const languageEqualsKey = "settings-general-language_equals";
|
||||||
|
|
||||||
export const pathMappingsKey = "settings-general-path_mappings";
|
export const pathMappingsKey = "settings-general-path_mappings";
|
||||||
export const pathMappingsMovieKey = "settings-general-path_mappings_movie";
|
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);
|
const SettingsContext = createContext<Settings | null>(null);
|
||||||
|
|
||||||
|
@ -12,7 +17,9 @@ type SettingsProviderProps = {
|
||||||
value: Settings | null;
|
value: Settings | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsProvider: FunctionComponent<SettingsProviderProps> = ({
|
type Props = PropsWithChildren<SettingsProviderProps>;
|
||||||
|
|
||||||
|
export const SettingsProvider: FunctionComponent<Props> = ({
|
||||||
value,
|
value,
|
||||||
children,
|
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 { setValue } = useFormActions();
|
||||||
const stagedValue = useStagedValues();
|
const stagedValue = useStagedValues();
|
||||||
|
|
||||||
|
@ -93,9 +97,9 @@ export function useUpdateArray<T>(key: string, compare: keyof T) {
|
||||||
if (key in stagedValue) {
|
if (key in stagedValue) {
|
||||||
return stagedValue[key];
|
return stagedValue[key];
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return current;
|
||||||
}
|
}
|
||||||
}, [key, stagedValue]);
|
}, [key, stagedValue, current]);
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(v: T, hook?: HookType) => {
|
(v: T, hook?: HookType) => {
|
||||||
|
|
|
@ -52,9 +52,7 @@ const ReleaseCard: FunctionComponent<ReleaseInfo> = ({
|
||||||
<Badge color={prerelease ? "yellow" : "green"}>
|
<Badge color={prerelease ? "yellow" : "green"}>
|
||||||
{prerelease ? "Development" : "Master"}
|
{prerelease ? "Development" : "Master"}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge hidden={!current} color="indigo">
|
{current && <Badge color="indigo">Installed</Badge>}
|
||||||
Installed
|
|
||||||
</Badge>
|
|
||||||
</Group>
|
</Group>
|
||||||
<Divider my="sm"></Divider>
|
<Divider my="sm"></Divider>
|
||||||
<Text>From newest to oldest:</Text>
|
<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 { Anchor, Container, Divider, Grid, Stack, Text } from "@mantine/core";
|
||||||
import { useDocumentTitle } from "@mantine/hooks";
|
import { useDocumentTitle } from "@mantine/hooks";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { FunctionComponent, ReactNode, useCallback, useState } from "react";
|
import {
|
||||||
|
FunctionComponent,
|
||||||
|
PropsWithChildren,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import Table from "./table";
|
import Table from "./table";
|
||||||
|
|
||||||
interface InfoProps {
|
interface InfoProps {
|
||||||
|
@ -53,10 +59,13 @@ function Label(props: IconProps): JSX.Element {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const InfoContainer: FunctionComponent<{ title: string }> = ({
|
interface InfoContainerProps {
|
||||||
title,
|
title: string;
|
||||||
children,
|
}
|
||||||
}) => {
|
|
||||||
|
const InfoContainer: FunctionComponent<
|
||||||
|
PropsWithChildren<InfoContainerProps>
|
||||||
|
> = ({ title, children }) => {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<h4>{title}</h4>
|
<h4>{title}</h4>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
useMovieWantedPagination,
|
useMovieWantedPagination,
|
||||||
} from "@/apis/hooks";
|
} from "@/apis/hooks";
|
||||||
import Language from "@/components/bazarr/Language";
|
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 WantedView from "@/pages/views/WantedView";
|
||||||
import { BuildKey } from "@/utilities";
|
import { BuildKey } from "@/utilities";
|
||||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
useSeriesAction,
|
useSeriesAction,
|
||||||
} from "@/apis/hooks";
|
} from "@/apis/hooks";
|
||||||
import Language from "@/components/bazarr/Language";
|
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 WantedView from "@/pages/views/WantedView";
|
||||||
import { useTableStyles } from "@/styles";
|
import { useTableStyles } from "@/styles";
|
||||||
import { BuildKey } from "@/utilities";
|
import { BuildKey } from "@/utilities";
|
||||||
|
|
|
@ -5,17 +5,17 @@ import {
|
||||||
useProfileItemsToLanguages,
|
useProfileItemsToLanguages,
|
||||||
} from "@/utilities/languages";
|
} from "@/utilities/languages";
|
||||||
import {
|
import {
|
||||||
faBookmark as farBookmark,
|
|
||||||
faFolder,
|
faFolder,
|
||||||
|
faBookmark as farBookmark,
|
||||||
} from "@fortawesome/free-regular-svg-icons";
|
} from "@fortawesome/free-regular-svg-icons";
|
||||||
import {
|
import {
|
||||||
|
IconDefinition,
|
||||||
faBookmark,
|
faBookmark,
|
||||||
faClone,
|
faClone,
|
||||||
faLanguage,
|
faLanguage,
|
||||||
faMusic,
|
faMusic,
|
||||||
faStream,
|
faStream,
|
||||||
faTags,
|
faTags,
|
||||||
IconDefinition,
|
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import {
|
import {
|
||||||
|
@ -23,7 +23,6 @@ import {
|
||||||
Badge,
|
Badge,
|
||||||
BadgeProps,
|
BadgeProps,
|
||||||
Box,
|
Box,
|
||||||
createStyles,
|
|
||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
HoverCard,
|
HoverCard,
|
||||||
|
@ -33,6 +32,7 @@ import {
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
Title,
|
Title,
|
||||||
|
createStyles,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { FunctionComponent, useMemo } from "react";
|
import { FunctionComponent, useMemo } from "react";
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,24 @@
|
||||||
import queryClient from "@/apis/queries";
|
|
||||||
import ThemeProvider from "@/App/theme";
|
import ThemeProvider from "@/App/theme";
|
||||||
|
import queryClient from "@/apis/queries";
|
||||||
import { ModalsProvider } from "@/modules/modals";
|
import { ModalsProvider } from "@/modules/modals";
|
||||||
import "@fontsource/roboto/300.css";
|
import "@fontsource/roboto/300.css";
|
||||||
import { NotificationsProvider } from "@mantine/notifications";
|
import { Notifications } from "@mantine/notifications";
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent, PropsWithChildren } from "react";
|
||||||
import { QueryClientProvider } from "react-query";
|
import { QueryClientProvider } from "react-query";
|
||||||
import { ReactQueryDevtools } from "react-query/devtools";
|
import { ReactQueryDevtools } from "react-query/devtools";
|
||||||
import { Router } from "./Router";
|
|
||||||
import { Environment } from "./utilities";
|
import { Environment } from "./utilities";
|
||||||
|
|
||||||
export const AllProviders: FunctionComponent = ({ children }) => {
|
export const AllProviders: FunctionComponent<PropsWithChildren> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<ModalsProvider>
|
<ModalsProvider>
|
||||||
<NotificationsProvider limit={5}>
|
<Notifications limit={5} />
|
||||||
<Router>
|
{/* c8 ignore next 3 */}
|
||||||
{/* c8 ignore next 3 */}
|
{Environment.queryDev && <ReactQueryDevtools initialIsOpen={false} />}
|
||||||
{Environment.queryDev && (
|
{children}
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
|
||||||
)}
|
|
||||||
{children}
|
|
||||||
</Router>
|
|
||||||
</NotificationsProvider>
|
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|
|
@ -1,11 +1,33 @@
|
||||||
import { AllProviders } from "@/providers";
|
import { AllProviders } from "@/providers";
|
||||||
import { render, RenderOptions } from "@testing-library/react";
|
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 (
|
return (
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<AllProviders>{children}</AllProviders>
|
<AllProviders>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</AllProviders>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,6 +12,7 @@ declare namespace Language {
|
||||||
type CodeType = string;
|
type CodeType = string;
|
||||||
interface Server {
|
interface Server {
|
||||||
code2: CodeType;
|
code2: CodeType;
|
||||||
|
code3: CodeType;
|
||||||
name: string;
|
name: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ interface Settings {
|
||||||
opensubtitlescom: Settings.OpenSubtitlesCom;
|
opensubtitlescom: Settings.OpenSubtitlesCom;
|
||||||
addic7ed: Settings.Addic7ed;
|
addic7ed: Settings.Addic7ed;
|
||||||
legendasdivx: Settings.Legandasdivx;
|
legendasdivx: Settings.Legandasdivx;
|
||||||
legendastv: Settings.Legendastv;
|
|
||||||
xsubs: Settings.XSubs;
|
xsubs: Settings.XSubs;
|
||||||
assrt: Settings.Assrt;
|
assrt: Settings.Assrt;
|
||||||
napisy24: Settings.Napisy24;
|
napisy24: Settings.Napisy24;
|
||||||
|
@ -25,6 +24,7 @@ interface Settings {
|
||||||
titlovi: Settings.Titlovi;
|
titlovi: Settings.Titlovi;
|
||||||
ktuvit: Settings.Ktuvit;
|
ktuvit: Settings.Ktuvit;
|
||||||
notifications: Settings.Notifications;
|
notifications: Settings.Notifications;
|
||||||
|
language_equals: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
declare namespace Settings {
|
declare namespace Settings {
|
||||||
|
@ -56,6 +56,7 @@ declare namespace Settings {
|
||||||
path_mappings: [string, string][];
|
path_mappings: [string, string][];
|
||||||
path_mappings_movie: [string, string][];
|
path_mappings_movie: [string, string][];
|
||||||
page_size: number;
|
page_size: number;
|
||||||
|
theme: string;
|
||||||
port: number;
|
port: number;
|
||||||
upgrade_subs: boolean;
|
upgrade_subs: boolean;
|
||||||
postprocessing_cmd?: string;
|
postprocessing_cmd?: string;
|
||||||
|
@ -196,10 +197,6 @@ declare namespace Settings {
|
||||||
skip_wrong_fps: boolean;
|
skip_wrong_fps: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Legendastv extends BaseProvider {
|
|
||||||
featured_only: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface XSubs extends BaseProvider {}
|
interface XSubs extends BaseProvider {}
|
||||||
|
|
||||||
interface Napisy24 extends BaseProvider {}
|
interface Napisy24 extends BaseProvider {}
|
||||||
|
|
|
@ -42,3 +42,12 @@ export function useProfileItemsToLanguages(profile?: Language.Profile) {
|
||||||
[data, profile?.items]
|
[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
|
// A workaround of built-in hooks in React-Router v6
|
||||||
// https://gist.github.com/rmorse/426ffcc579922a82749934826fa9f743
|
// https://gist.github.com/rmorse/426ffcc579922a82749934826fa9f743
|
||||||
|
|
||||||
import type { Blocker, History, Transition } from "history";
|
import { unstable_usePrompt as useUnstablePrompt } from "react-router-dom";
|
||||||
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]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Replace with Mantine's confirmation modal
|
// TODO: Replace with Mantine's confirmation modal
|
||||||
export function usePrompt(when: boolean, message: string) {
|
export function usePrompt(when: boolean, message: string) {
|
||||||
useBlocker((tx) => {
|
useUnstablePrompt({ when, message });
|
||||||
if (window.confirm(message)) {
|
|
||||||
tx.retry();
|
|
||||||
}
|
|
||||||
}, when);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,37 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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
|
import os
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from . import common
|
from . import common
|
||||||
|
@ -43,11 +52,6 @@ from .plugins.NotifyBase import NotifyBase
|
||||||
from . import plugins
|
from . import plugins
|
||||||
from . import __version__
|
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:
|
class Apprise:
|
||||||
"""
|
"""
|
||||||
|
@ -369,91 +373,83 @@ class Apprise:
|
||||||
such as turning a \n into an actual new line, etc.
|
such as turning a \n into an actual new line, etc.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return py3compat.asyncio.tosync(
|
try:
|
||||||
self.async_notify(
|
# Process arguments and build synchronous and asynchronous calls
|
||||||
|
# (this step can throw internal errors).
|
||||||
|
sequential_calls, parallel_calls = self._create_notify_calls(
|
||||||
body, title,
|
body, title,
|
||||||
notify_type=notify_type, body_format=body_format,
|
notify_type=notify_type, body_format=body_format,
|
||||||
tag=tag, match_always=match_always, attach=attach,
|
tag=tag, match_always=match_always, attach=attach,
|
||||||
interpret_escapes=interpret_escapes,
|
interpret_escapes=interpret_escapes
|
||||||
),
|
)
|
||||||
debug=self.debug
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
Send a notification to all the plugins previously loaded, for
|
||||||
asynchronous callers. This method is an async method that should be
|
asynchronous callers.
|
||||||
awaited on, even if it is missing the async keyword in its signature.
|
|
||||||
(This is omitted to preserve syntax compatibility with Python 2.)
|
|
||||||
|
|
||||||
The arguments are identical to those of Apprise.notify().
|
The arguments are identical to those of Apprise.notify().
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
coroutines = list(
|
# Process arguments and build synchronous and asynchronous calls
|
||||||
self._notifyall(
|
# (this step can throw internal errors).
|
||||||
Apprise._notifyhandlerasync, *args, **kwargs))
|
sequential_calls, parallel_calls = self._create_notify_calls(
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# No notifications sent, and there was an internal error.
|
# 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
|
return False
|
||||||
|
|
||||||
except Exception:
|
if not sequential_calls and not parallel_calls:
|
||||||
# A catch all so we don't have to abort early
|
# Nothing to send
|
||||||
# just because one of our plugins has a bug in it.
|
return None
|
||||||
logger.exception("Unhandled Notification Exception")
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
sequential_result = Apprise._notify_sequential(*sequential_calls)
|
||||||
def _notifyhandlerasync(server, **kwargs):
|
parallel_result = \
|
||||||
"""
|
await Apprise._notify_parallel_asyncio(*parallel_calls)
|
||||||
The asynchronous notification sender. Returns a coroutine that yields
|
return sequential_result and parallel_result
|
||||||
True if the notification sent successfully.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if server.asset.async_mode:
|
def _create_notify_calls(self, *args, **kwargs):
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Creates notifications for all the plugins loaded.
|
Creates notifications for all the plugins loaded.
|
||||||
|
|
||||||
Returns a generator that calls handler for each notification. The first
|
Returns a list of (server, notify() kwargs) tuples for plugins with
|
||||||
and only argument supplied to handler is the server, and the keyword
|
parallelism disabled and another list for plugins with parallelism
|
||||||
arguments are exactly as they would be passed to server.notify().
|
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:
|
if len(self) == 0:
|
||||||
|
@ -546,14 +542,121 @@ class Apprise:
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
yield handler(
|
kwargs = dict(
|
||||||
server,
|
|
||||||
body=conversion_body_map[server.notify_format],
|
body=conversion_body_map[server.notify_format],
|
||||||
title=conversion_title_map[server.notify_format],
|
title=conversion_title_map[server.notify_format],
|
||||||
notify_type=notify_type,
|
notify_type=notify_type,
|
||||||
attach=attach,
|
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):
|
def details(self, lang=None, show_requirements=False, show_disabled=False):
|
||||||
"""
|
"""
|
||||||
|
@ -581,6 +684,7 @@ class Apprise:
|
||||||
'setup_url': getattr(plugin, 'setup_url', None),
|
'setup_url': getattr(plugin, 'setup_url', None),
|
||||||
# Placeholder - populated below
|
# Placeholder - populated below
|
||||||
'details': None,
|
'details': None,
|
||||||
|
|
||||||
# Differentiat between what is a custom loaded plugin and
|
# Differentiat between what is a custom loaded plugin and
|
||||||
# which is native.
|
# which is native.
|
||||||
'category': getattr(plugin, 'category', None)
|
'category': getattr(plugin, 'category', None)
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 attachment
|
||||||
from . import URLBase
|
from . import URLBase
|
||||||
|
@ -170,6 +177,11 @@ class AppriseAttachment:
|
||||||
return_status = False
|
return_status = False
|
||||||
continue
|
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):
|
elif not isinstance(_attachment, attachment.AttachBase):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"An invalid attachment (type={}) was specified.".format(
|
"An invalid attachment (type={}) was specified.".format(
|
||||||
|
@ -196,7 +208,11 @@ class AppriseAttachment:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Add our initialized plugin to our server listings
|
# Add our initialized plugin to our server listings
|
||||||
self.attachments.append(instance)
|
if isinstance(instance, list):
|
||||||
|
self.attachments.extend(instance)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.attachments.append(instance)
|
||||||
|
|
||||||
# Return our status
|
# Return our status
|
||||||
return return_status
|
return return_status
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 config
|
||||||
from . import ConfigBase
|
from . import ConfigBase
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 ctypes
|
||||||
import locale
|
import locale
|
||||||
|
@ -67,7 +74,7 @@ class LazyTranslation:
|
||||||
"""
|
"""
|
||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
super(LazyTranslation, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return gettext.gettext(self.text)
|
return gettext.gettext(self.text)
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
from .logger import logger
|
from .logger import logger
|
||||||
|
@ -194,7 +201,7 @@ class URLBase:
|
||||||
asset if isinstance(asset, AppriseAsset) else AppriseAsset()
|
asset if isinstance(asset, AppriseAsset) else AppriseAsset()
|
||||||
|
|
||||||
# Certificate Verification (for SSL calls); default to being enabled
|
# 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
|
# Secure Mode
|
||||||
self.secure = kwargs.get('secure', False)
|
self.secure = kwargs.get('secure', False)
|
||||||
|
@ -222,24 +229,22 @@ class URLBase:
|
||||||
self.password = URLBase.unquote(self.password)
|
self.password = URLBase.unquote(self.password)
|
||||||
|
|
||||||
# Store our Timeout Variables
|
# Store our Timeout Variables
|
||||||
if 'socket_read_timeout' in kwargs:
|
if 'rto' in kwargs:
|
||||||
try:
|
try:
|
||||||
self.socket_read_timeout = \
|
self.socket_read_timeout = float(kwargs.get('rto'))
|
||||||
float(kwargs.get('socket_read_timeout'))
|
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Invalid socket read timeout (rto) was specified {}'
|
'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:
|
try:
|
||||||
self.socket_connect_timeout = \
|
self.socket_connect_timeout = float(kwargs.get('cto'))
|
||||||
float(kwargs.get('socket_connect_timeout'))
|
|
||||||
|
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Invalid socket connect timeout (cto) was specified {}'
|
'Invalid socket connect timeout (cto) was specified {}'
|
||||||
.format(kwargs.get('socket_connect_timeout')))
|
.format(kwargs.get('cto')))
|
||||||
|
|
||||||
if 'tag' in kwargs:
|
if 'tag' in kwargs:
|
||||||
# We want to associate some tags with our notification service.
|
# We want to associate some tags with our notification service.
|
||||||
|
@ -598,7 +603,7 @@ class URLBase:
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@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.
|
"""Parses the URL and returns it broken apart into a dictionary.
|
||||||
|
|
||||||
This is very specific and customized for Apprise.
|
This is very specific and customized for Apprise.
|
||||||
|
@ -618,7 +623,8 @@ class URLBase:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
results = parse_url(
|
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:
|
if not results:
|
||||||
# We're done; we failed to parse our url
|
# We're done; we failed to parse our url
|
||||||
|
@ -646,11 +652,11 @@ class URLBase:
|
||||||
|
|
||||||
# Store our socket read timeout if specified
|
# Store our socket read timeout if specified
|
||||||
if 'rto' in results['qsd']:
|
if 'rto' in results['qsd']:
|
||||||
results['socket_read_timeout'] = results['qsd']['rto']
|
results['rto'] = results['qsd']['rto']
|
||||||
|
|
||||||
# Store our socket connect timeout if specified
|
# Store our socket connect timeout if specified
|
||||||
if 'cto' in results['qsd']:
|
if 'cto' in results['qsd']:
|
||||||
results['socket_connect_timeout'] = results['qsd']['cto']
|
results['cto'] = results['qsd']['cto']
|
||||||
|
|
||||||
if 'port' in results['qsd']:
|
if 'port' in results['qsd']:
|
||||||
results['port'] = results['qsd']['port']
|
results['port'] = results['qsd']['port']
|
||||||
|
@ -679,6 +685,15 @@ class URLBase:
|
||||||
|
|
||||||
return response
|
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):
|
def schemas(self):
|
||||||
"""A simple function that returns a set of all schemas associated
|
"""A simple function that returns a set of all schemas associated
|
||||||
with this object based on the object.protocol and
|
with this object based on the object.protocol and
|
||||||
|
|
|
@ -1,33 +1,40 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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'
|
__title__ = 'Apprise'
|
||||||
__version__ = '1.1.0'
|
__version__ = '1.4.0'
|
||||||
__author__ = 'Chris Caron'
|
__author__ = 'Chris Caron'
|
||||||
__license__ = 'MIT'
|
__license__ = 'BSD'
|
||||||
__copywrite__ = 'Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>'
|
__copywrite__ = 'Copyright (C) 2023 Chris Caron <lead2gold@gmail.com>'
|
||||||
__email__ = 'lead2gold@gmail.com'
|
__email__ = 'lead2gold@gmail.com'
|
||||||
__status__ = 'Production'
|
__status__ = 'Production'
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 os
|
||||||
import time
|
import time
|
||||||
|
@ -120,7 +127,7 @@ class AttachBase(URLBase):
|
||||||
should be considered expired.
|
should be considered expired.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(AttachBase, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
if not mimetypes.inited:
|
if not mimetypes.inited:
|
||||||
# Ensure mimetypes has been initialized
|
# Ensure mimetypes has been initialized
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
import os
|
import os
|
||||||
|
@ -50,7 +57,7 @@ class AttachFile(AttachBase):
|
||||||
Initialize Local File Attachment Object
|
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
|
# Store path but mark it dirty since we have not performed any
|
||||||
# verification at this point.
|
# verification at this point.
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
import os
|
import os
|
||||||
|
@ -61,7 +68,7 @@ class AttachHTTP(AttachBase):
|
||||||
additionally include as part of the server headers to post with
|
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'
|
self.schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
|
@ -254,7 +261,7 @@ class AttachHTTP(AttachBase):
|
||||||
self._temp_file.close()
|
self._temp_file.close()
|
||||||
self._temp_file = None
|
self._temp_file = None
|
||||||
|
|
||||||
super(AttachHTTP, self).invalidate()
|
super().invalidate()
|
||||||
|
|
||||||
def url(self, privacy=False, *args, **kwargs):
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,30 +1,36 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
|
|
||||||
from os import listdir
|
from os import listdir
|
||||||
from os.path import dirname
|
from os.path import dirname
|
||||||
from os.path import abspath
|
from os.path import abspath
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
|
||||||
# All rights reserved.
|
|
||||||
#
|
#
|
||||||
# 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
|
# Redistribution and use in source and binary forms, with or without
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# modification, are permitted provided that the following conditions are met:
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# this list of conditions and the following disclaimer.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# and/or other materials provided with the distribution.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# contributors may be used to endorse or promote products derived from
|
||||||
# THE SOFTWARE.
|
# 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 click
|
||||||
import logging
|
import logging
|
||||||
|
@ -73,28 +80,64 @@ DEFAULT_CONFIG_PATHS = (
|
||||||
'~/.apprise/apprise.yml',
|
'~/.apprise/apprise.yml',
|
||||||
'~/.config/apprise/apprise',
|
'~/.config/apprise/apprise',
|
||||||
'~/.config/apprise/apprise.yml',
|
'~/.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
|
# Define our paths to search for plugins
|
||||||
DEFAULT_PLUGIN_PATHS = (
|
DEFAULT_PLUGIN_PATHS = (
|
||||||
'~/.apprise/plugins',
|
'~/.apprise/plugins',
|
||||||
'~/.config/apprise/plugins',
|
'~/.config/apprise/plugins',
|
||||||
|
|
||||||
|
# Global Plugin Support
|
||||||
|
'/var/lib/apprise/plugins',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Detect Windows
|
# Detect Windows
|
||||||
if platform.system() == 'Windows':
|
if platform.system() == 'Windows':
|
||||||
# Default Config Search Path for Windows Users
|
# Default Config Search Path for Windows Users
|
||||||
DEFAULT_CONFIG_PATHS = (
|
DEFAULT_CONFIG_PATHS = (
|
||||||
expandvars('%APPDATA%/Apprise/apprise'),
|
expandvars('%APPDATA%\\Apprise\\apprise'),
|
||||||
expandvars('%APPDATA%/Apprise/apprise.yml'),
|
expandvars('%APPDATA%\\Apprise\\apprise.yml'),
|
||||||
expandvars('%LOCALAPPDATA%/Apprise/apprise'),
|
expandvars('%LOCALAPPDATA%\\Apprise\\apprise'),
|
||||||
expandvars('%LOCALAPPDATA%/Apprise/apprise.yml'),
|
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 Search Path for Windows Users
|
||||||
DEFAULT_PLUGIN_PATHS = (
|
DEFAULT_PLUGIN_PATHS = (
|
||||||
expandvars('%APPDATA%/Apprise/plugins'),
|
expandvars('%APPDATA%\\Apprise\\plugins'),
|
||||||
expandvars('%LOCALAPPDATA%/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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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
|
# 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
|
# 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:
|
class NotifyType:
|
||||||
INFO: NotifyType
|
INFO: NotifyType
|
||||||
SUCCESS: NotifyType
|
SUCCESS: NotifyType
|
||||||
|
@ -12,4 +16,7 @@ class NotifyFormat:
|
||||||
class ContentLocation:
|
class ContentLocation:
|
||||||
LOCAL: ContentLocation
|
LOCAL: ContentLocation
|
||||||
HOSTED: ContentLocation
|
HOSTED: ContentLocation
|
||||||
INACCESSIBLE: 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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 os
|
||||||
import re
|
import re
|
||||||
|
@ -113,7 +120,7 @@ class ConfigBase(URLBase):
|
||||||
these 'include' entries to be honored, this value must be set to True.
|
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
|
# 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
|
# 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
|
# Define what a valid line should look like
|
||||||
valid_line_re = re.compile(
|
valid_line_re = re.compile(
|
||||||
r'^\s*(?P<line>([;#]+(?P<comment>.*))|'
|
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'(?P<url>[a-z0-9]{2,9}://.*)|'
|
||||||
r'include\s+(?P<config>.+))?\s*$', re.I)
|
r'include\s+(?P<config>.+))?\s*$', re.I)
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
import os
|
import os
|
||||||
|
@ -53,7 +60,7 @@ class ConfigFile(ConfigBase):
|
||||||
additionally include as part of the server headers to post with
|
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
|
# Store our file path as it was set
|
||||||
self.path = os.path.abspath(os.path.expanduser(path))
|
self.path = os.path.abspath(os.path.expanduser(path))
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
import requests
|
import requests
|
||||||
|
@ -75,7 +82,7 @@ class ConfigHTTP(ConfigBase):
|
||||||
additionally include as part of the server headers to post with
|
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'
|
self.schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2020 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 .ConfigBase import ConfigBase
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
@ -46,7 +53,7 @@ class ConfigMemory(ConfigBase):
|
||||||
Memory objects just store the raw configuration in memory. There is
|
Memory objects just store the raw configuration in memory. There is
|
||||||
no external reference point. It's always considered cached.
|
no external reference point. It's always considered cached.
|
||||||
"""
|
"""
|
||||||
super(ConfigMemory, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
# Store our raw config into memory
|
# Store our raw config into memory
|
||||||
self.content = content
|
self.content = content
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
from os import listdir
|
from os import listdir
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 re
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
|
@ -99,7 +106,7 @@ class HTMLConverter(HTMLParser, object):
|
||||||
BLOCK_END = {}
|
BLOCK_END = {}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(HTMLConverter, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
# Shoudl we store the text content or not?
|
# Shoudl we store the text content or not?
|
||||||
self._do_store = True
|
self._do_store = True
|
||||||
|
|
|
@ -1,27 +1,35 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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 ..plugins.NotifyBase import NotifyBase
|
||||||
from ..utils import URL_DETAILS_RE
|
from ..utils import URL_DETAILS_RE
|
||||||
from ..utils import parse_url
|
from ..utils import parse_url
|
||||||
|
@ -134,7 +142,7 @@ class CustomNotifyPlugin(NotifyBase):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# init parent
|
# init parent
|
||||||
super(CustomNotifyPluginWrapper, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self._default_args = {}
|
self._default_args = {}
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,35 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 3-Clause License
|
||||||
#
|
#
|
||||||
# Copyright (C) 2022 Chris Caron <lead2gold@gmail.com>
|
# Apprise - Push Notification Library.
|
||||||
# All rights reserved.
|
# 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
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
# of this software and associated documentation files(the "Software"), to deal
|
# this list of conditions and the following disclaimer.
|
||||||
# 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 :
|
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# all copies or substantial portions of the Software.
|
# 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
|
# 3. Neither the name of the copyright holder nor the names of its
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# contributors may be used to endorse or promote products derived from
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
# this software without specific prior written permission.
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
#
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# THE SOFTWARE.
|
# 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
|
from .notify import notify
|
||||||
|
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue