plexflow 0.0.64__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plexflow/__init__.py +0 -0
- plexflow/__main__.py +15 -0
- plexflow/core/.DS_Store +0 -0
- plexflow/core/__init__.py +0 -0
- plexflow/core/context/__init__.py +0 -0
- plexflow/core/context/metadata/__init__.py +0 -0
- plexflow/core/context/metadata/context.py +32 -0
- plexflow/core/context/metadata/tmdb/__init__.py +0 -0
- plexflow/core/context/metadata/tmdb/context.py +45 -0
- plexflow/core/context/partial_context.py +46 -0
- plexflow/core/context/partials/__init__.py +8 -0
- plexflow/core/context/partials/cache.py +16 -0
- plexflow/core/context/partials/context.py +12 -0
- plexflow/core/context/partials/ids.py +37 -0
- plexflow/core/context/partials/movie.py +115 -0
- plexflow/core/context/partials/tgx_batch.py +33 -0
- plexflow/core/context/partials/tgx_context.py +34 -0
- plexflow/core/context/partials/torrents.py +23 -0
- plexflow/core/context/partials/watchlist.py +35 -0
- plexflow/core/context/plexflow_context.py +29 -0
- plexflow/core/context/plexflow_property.py +36 -0
- plexflow/core/context/root/__init__.py +0 -0
- plexflow/core/context/root/context.py +25 -0
- plexflow/core/context/select/__init__.py +0 -0
- plexflow/core/context/select/context.py +45 -0
- plexflow/core/context/torrent/__init__.py +0 -0
- plexflow/core/context/torrent/context.py +43 -0
- plexflow/core/context/torrent/tpb/__init__.py +0 -0
- plexflow/core/context/torrent/tpb/context.py +45 -0
- plexflow/core/context/torrent/yts/__init__.py +0 -0
- plexflow/core/context/torrent/yts/context.py +45 -0
- plexflow/core/context/watchlist/__init__.py +0 -0
- plexflow/core/context/watchlist/context.py +46 -0
- plexflow/core/downloads/__init__.py +0 -0
- plexflow/core/downloads/candidates/__init__.py +0 -0
- plexflow/core/downloads/candidates/download_candidate.py +210 -0
- plexflow/core/downloads/candidates/filtered.py +51 -0
- plexflow/core/downloads/candidates/utils.py +39 -0
- plexflow/core/env/__init__.py +0 -0
- plexflow/core/env/env.py +31 -0
- plexflow/core/genai/__init__.py +0 -0
- plexflow/core/genai/bot.py +9 -0
- plexflow/core/genai/plexa.py +54 -0
- plexflow/core/genai/torrent/imdb_verify.py +65 -0
- plexflow/core/genai/torrent/movie.py +25 -0
- plexflow/core/genai/utils/__init__.py +0 -0
- plexflow/core/genai/utils/loader.py +5 -0
- plexflow/core/metadata/__init__.py +0 -0
- plexflow/core/metadata/auto/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_meta.py +40 -0
- plexflow/core/metadata/auto/auto_providers/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/auto/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/auto/episode.py +49 -0
- plexflow/core/metadata/auto/auto_providers/auto/item.py +55 -0
- plexflow/core/metadata/auto/auto_providers/auto/movie.py +13 -0
- plexflow/core/metadata/auto/auto_providers/auto/season.py +43 -0
- plexflow/core/metadata/auto/auto_providers/auto/show.py +26 -0
- plexflow/core/metadata/auto/auto_providers/imdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/imdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/imdb/show.py +45 -0
- plexflow/core/metadata/auto/auto_providers/moviemeter/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/moviemeter/movie.py +40 -0
- plexflow/core/metadata/auto/auto_providers/plex/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/plex/movie.py +39 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/episode.py +30 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/season.py +23 -0
- plexflow/core/metadata/auto/auto_providers/tmdb/show.py +41 -0
- plexflow/core/metadata/auto/auto_providers/tmdb.py +92 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/__init__.py +0 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/episode.py +28 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/movie.py +36 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/season.py +25 -0
- plexflow/core/metadata/auto/auto_providers/tvdb/show.py +41 -0
- plexflow/core/metadata/providers/__init__.py +0 -0
- plexflow/core/metadata/providers/imdb/__init__.py +0 -0
- plexflow/core/metadata/providers/imdb/datatypes.py +53 -0
- plexflow/core/metadata/providers/imdb/imdb.py +112 -0
- plexflow/core/metadata/providers/moviemeter/__init__.py +0 -0
- plexflow/core/metadata/providers/moviemeter/datatypes.py +111 -0
- plexflow/core/metadata/providers/moviemeter/moviemeter.py +42 -0
- plexflow/core/metadata/providers/plex/__init__.py +0 -0
- plexflow/core/metadata/providers/plex/datatypes.py +693 -0
- plexflow/core/metadata/providers/plex/plex.py +167 -0
- plexflow/core/metadata/providers/tmdb/__init__.py +0 -0
- plexflow/core/metadata/providers/tmdb/datatypes.py +460 -0
- plexflow/core/metadata/providers/tmdb/tmdb.py +85 -0
- plexflow/core/metadata/providers/tvdb/__init__.py +0 -0
- plexflow/core/metadata/providers/tvdb/datatypes.py +257 -0
- plexflow/core/metadata/providers/tvdb/tv_datatypes.py +554 -0
- plexflow/core/metadata/providers/tvdb/tvdb.py +65 -0
- plexflow/core/metadata/providers/universal/__init__.py +0 -0
- plexflow/core/metadata/providers/universal/movie.py +130 -0
- plexflow/core/metadata/providers/universal/old.py +192 -0
- plexflow/core/metadata/providers/universal/show.py +107 -0
- plexflow/core/plex/__init__.py +0 -0
- plexflow/core/plex/api/context/authorized.py +15 -0
- plexflow/core/plex/api/context/discover.py +14 -0
- plexflow/core/plex/api/context/library.py +14 -0
- plexflow/core/plex/discover/__init__.py +0 -0
- plexflow/core/plex/discover/activity.py +448 -0
- plexflow/core/plex/discover/comment.py +89 -0
- plexflow/core/plex/discover/feed.py +11 -0
- plexflow/core/plex/hooks/__init__.py +0 -0
- plexflow/core/plex/hooks/plex_authorized.py +60 -0
- plexflow/core/plex/hooks/plexflow_database.py +6 -0
- plexflow/core/plex/library/__init__.py +0 -0
- plexflow/core/plex/library/library.py +103 -0
- plexflow/core/plex/token/__init__.py +0 -0
- plexflow/core/plex/token/auto_token.py +91 -0
- plexflow/core/plex/utils/__init__.py +0 -0
- plexflow/core/plex/utils/paginated.py +39 -0
- plexflow/core/plex/watchlist/__init__.py +0 -0
- plexflow/core/plex/watchlist/datatypes.py +124 -0
- plexflow/core/plex/watchlist/watchlist.py +23 -0
- plexflow/core/storage/__init__.py +0 -0
- plexflow/core/storage/object/__init__.py +0 -0
- plexflow/core/storage/object/plexflow_storage.py +143 -0
- plexflow/core/storage/object/redis_storage.py +169 -0
- plexflow/core/subtitles/__init__.py +0 -0
- plexflow/core/subtitles/providers/__init__.py +0 -0
- plexflow/core/subtitles/providers/auto_subtitles.py +48 -0
- plexflow/core/subtitles/providers/oss/__init__.py +0 -0
- plexflow/core/subtitles/providers/oss/datatypes.py +104 -0
- plexflow/core/subtitles/providers/oss/download.py +48 -0
- plexflow/core/subtitles/providers/oss/old.py +144 -0
- plexflow/core/subtitles/providers/oss/oss.py +400 -0
- plexflow/core/subtitles/providers/oss/oss_subtitle.py +32 -0
- plexflow/core/subtitles/providers/oss/search.py +52 -0
- plexflow/core/subtitles/providers/oss/unlimited_oss.py +231 -0
- plexflow/core/subtitles/providers/oss/utils/__init__.py +0 -0
- plexflow/core/subtitles/providers/oss/utils/config.py +63 -0
- plexflow/core/subtitles/providers/oss/utils/download_client.py +22 -0
- plexflow/core/subtitles/providers/oss/utils/exceptions.py +35 -0
- plexflow/core/subtitles/providers/oss/utils/file_utils.py +83 -0
- plexflow/core/subtitles/providers/oss/utils/languages.py +78 -0
- plexflow/core/subtitles/providers/oss/utils/response_base.py +221 -0
- plexflow/core/subtitles/providers/oss/utils/responses.py +176 -0
- plexflow/core/subtitles/providers/oss/utils/srt.py +561 -0
- plexflow/core/subtitles/results/__init__.py +0 -0
- plexflow/core/subtitles/results/subtitle.py +170 -0
- plexflow/core/torrents/__init__.py +0 -0
- plexflow/core/torrents/analyzers/analyzed_torrent.py +143 -0
- plexflow/core/torrents/analyzers/analyzer.py +45 -0
- plexflow/core/torrents/analyzers/torrentquest/analyzer.py +47 -0
- plexflow/core/torrents/auto/auto_providers/auto/__init__.py +0 -0
- plexflow/core/torrents/auto/auto_providers/auto/torrent.py +64 -0
- plexflow/core/torrents/auto/auto_providers/tpb/torrent.py +62 -0
- plexflow/core/torrents/auto/auto_torrents.py +29 -0
- plexflow/core/torrents/providers/__init__.py +0 -0
- plexflow/core/torrents/providers/ext/__init__.py +0 -0
- plexflow/core/torrents/providers/ext/ext.py +18 -0
- plexflow/core/torrents/providers/ext/utils.py +64 -0
- plexflow/core/torrents/providers/extratorrent/__init__.py +0 -0
- plexflow/core/torrents/providers/extratorrent/extratorrent.py +21 -0
- plexflow/core/torrents/providers/extratorrent/utils.py +66 -0
- plexflow/core/torrents/providers/eztv/__init__.py +0 -0
- plexflow/core/torrents/providers/eztv/eztv.py +47 -0
- plexflow/core/torrents/providers/eztv/utils.py +83 -0
- plexflow/core/torrents/providers/rarbg2/__init__.py +0 -0
- plexflow/core/torrents/providers/rarbg2/rarbg2.py +19 -0
- plexflow/core/torrents/providers/rarbg2/utils.py +76 -0
- plexflow/core/torrents/providers/snowfl/__init__.py +0 -0
- plexflow/core/torrents/providers/snowfl/snowfl.py +36 -0
- plexflow/core/torrents/providers/snowfl/utils.py +59 -0
- plexflow/core/torrents/providers/tgx/__init__.py +0 -0
- plexflow/core/torrents/providers/tgx/context.py +50 -0
- plexflow/core/torrents/providers/tgx/dump.py +40 -0
- plexflow/core/torrents/providers/tgx/tgx.py +22 -0
- plexflow/core/torrents/providers/tgx/utils.py +61 -0
- plexflow/core/torrents/providers/therarbg/__init__.py +0 -0
- plexflow/core/torrents/providers/therarbg/therarbg.py +17 -0
- plexflow/core/torrents/providers/therarbg/utils.py +61 -0
- plexflow/core/torrents/providers/torrentquest/__init__.py +0 -0
- plexflow/core/torrents/providers/torrentquest/torrentquest.py +20 -0
- plexflow/core/torrents/providers/torrentquest/utils.py +70 -0
- plexflow/core/torrents/providers/tpb/__init__.py +0 -0
- plexflow/core/torrents/providers/tpb/tpb.py +17 -0
- plexflow/core/torrents/providers/tpb/utils.py +139 -0
- plexflow/core/torrents/providers/yts/__init__.py +0 -0
- plexflow/core/torrents/providers/yts/utils.py +57 -0
- plexflow/core/torrents/providers/yts/yts.py +31 -0
- plexflow/core/torrents/results/__init__.py +0 -0
- plexflow/core/torrents/results/torrent.py +165 -0
- plexflow/core/torrents/results/universal.py +220 -0
- plexflow/core/torrents/results/utils.py +15 -0
- plexflow/events/__init__.py +0 -0
- plexflow/events/download/__init__.py +0 -0
- plexflow/events/download/torrent_events.py +96 -0
- plexflow/events/publish/__init__.py +0 -0
- plexflow/events/publish/publish.py +34 -0
- plexflow/logging/__init__.py +0 -0
- plexflow/logging/log_setup.py +8 -0
- plexflow/spiders/quiet_logger.py +9 -0
- plexflow/spiders/tgx/pipelines/dump_json_pipeline.py +30 -0
- plexflow/spiders/tgx/pipelines/meta_pipeline.py +13 -0
- plexflow/spiders/tgx/pipelines/publish_pipeline.py +14 -0
- plexflow/spiders/tgx/pipelines/torrent_info_pipeline.py +12 -0
- plexflow/spiders/tgx/pipelines/validation_pipeline.py +17 -0
- plexflow/spiders/tgx/settings.py +36 -0
- plexflow/spiders/tgx/spider.py +72 -0
- plexflow/utils/__init__.py +0 -0
- plexflow/utils/antibot/human_like_requests.py +122 -0
- plexflow/utils/api/__init__.py +0 -0
- plexflow/utils/api/context/http.py +62 -0
- plexflow/utils/api/rest/__init__.py +0 -0
- plexflow/utils/api/rest/antibot_restful.py +68 -0
- plexflow/utils/api/rest/restful.py +49 -0
- plexflow/utils/captcha/__init__.py +0 -0
- plexflow/utils/captcha/bypass/__init__.py +0 -0
- plexflow/utils/captcha/bypass/decode_audio.py +34 -0
- plexflow/utils/download/__init__.py +0 -0
- plexflow/utils/download/gz.py +26 -0
- plexflow/utils/filesystem/__init__.py +0 -0
- plexflow/utils/filesystem/search.py +129 -0
- plexflow/utils/gmail/__init__.py +0 -0
- plexflow/utils/gmail/mails.py +116 -0
- plexflow/utils/hooks/__init__.py +0 -0
- plexflow/utils/hooks/http.py +84 -0
- plexflow/utils/hooks/postgresql.py +93 -0
- plexflow/utils/hooks/redis.py +112 -0
- plexflow/utils/image/storage.py +36 -0
- plexflow/utils/imdb/__init__.py +0 -0
- plexflow/utils/imdb/imdb_codes.py +107 -0
- plexflow/utils/pubsub/consume.py +82 -0
- plexflow/utils/pubsub/produce.py +25 -0
- plexflow/utils/retry/__init__.py +0 -0
- plexflow/utils/retry/utils.py +38 -0
- plexflow/utils/strings/__init__.py +0 -0
- plexflow/utils/strings/filesize.py +55 -0
- plexflow/utils/strings/language.py +14 -0
- plexflow/utils/subtitle/search.py +76 -0
- plexflow/utils/tasks/decorators.py +78 -0
- plexflow/utils/tasks/k8s/task.py +70 -0
- plexflow/utils/thread_safe/safe_list.py +54 -0
- plexflow/utils/thread_safe/safe_set.py +69 -0
- plexflow/utils/torrent/__init__.py +0 -0
- plexflow/utils/torrent/analyze.py +118 -0
- plexflow/utils/torrent/extract/common.py +37 -0
- plexflow/utils/torrent/extract/ext.py +2391 -0
- plexflow/utils/torrent/extract/extratorrent.py +56 -0
- plexflow/utils/torrent/extract/kat.py +1581 -0
- plexflow/utils/torrent/extract/tgx.py +96 -0
- plexflow/utils/torrent/extract/therarbg.py +170 -0
- plexflow/utils/torrent/extract/torrentquest.py +171 -0
- plexflow/utils/torrent/files.py +36 -0
- plexflow/utils/torrent/hash.py +90 -0
- plexflow/utils/transcribe/__init__.py +0 -0
- plexflow/utils/transcribe/speech2text.py +40 -0
- plexflow/utils/video/__init__.py +0 -0
- plexflow/utils/video/subtitle.py +73 -0
- plexflow-0.0.64.dist-info/METADATA +71 -0
- plexflow-0.0.64.dist-info/RECORD +256 -0
- plexflow-0.0.64.dist-info/WHEEL +4 -0
- plexflow-0.0.64.dist-info/entry_points.txt +24 -0
@@ -0,0 +1,221 @@
|
|
1
|
+
"""
|
2
|
+
Copyright (c) 2023 Omer Duskin.
|
3
|
+
|
4
|
+
This file is part of Opensubtitles API wrapper.
|
5
|
+
|
6
|
+
Opensubtitles API is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the MIT License as published by the Massachusetts
|
8
|
+
Institute of Technology.
|
9
|
+
|
10
|
+
For full details, please see the LICENSE file located in the root
|
11
|
+
directory of this project.
|
12
|
+
|
13
|
+
This module define base class of opensubtitles responses.
|
14
|
+
"""
|
15
|
+
|
16
|
+
import ast
|
17
|
+
import json
|
18
|
+
import functools
|
19
|
+
import logging
|
20
|
+
from typing import Any
|
21
|
+
from datetime import datetime, date
|
22
|
+
|
23
|
+
logger = logging.getLogger(__name__)
|
24
|
+
|
25
|
+
|
26
|
+
def rgetattr(obj, attr, *args):
|
27
|
+
"""Support getattr on nested subobjects / chained properties.
|
28
|
+
|
29
|
+
For example, for d = DotDict(aa={"bb": cc"})
|
30
|
+
rgetattr(d, "aa.bb") will return "cc"
|
31
|
+
"""
|
32
|
+
|
33
|
+
def _getattr(obj, attr):
|
34
|
+
return getattr(obj, attr, *args)
|
35
|
+
|
36
|
+
return functools.reduce(_getattr, [obj] + attr.split("."))
|
37
|
+
|
38
|
+
|
39
|
+
def rsetattr(obj, attr, val):
|
40
|
+
"""Support setattr on nested subobjects / chained properties."""
|
41
|
+
pre, _, post = attr.rpartition(".")
|
42
|
+
return setattr(rgetattr(obj, pre) if pre else obj, post, val)
|
43
|
+
|
44
|
+
|
45
|
+
class ResponseJSONEncoder(json.JSONEncoder):
|
46
|
+
"""JSON encoder class for handling custom serialization of Response objects.
|
47
|
+
|
48
|
+
Handle the SearchResponse that consists of Subtitle list.
|
49
|
+
"""
|
50
|
+
|
51
|
+
def default(self, o: object) -> Any:
|
52
|
+
"""Override the default method of the JSONEncoder class.
|
53
|
+
|
54
|
+
Serializes objects with a 'to_dict' method using 'to_dict'.
|
55
|
+
Falls back to the super class's default method for other objects.
|
56
|
+
|
57
|
+
Parameters:
|
58
|
+
o (object): The object to be serialized.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
Any: The serialized representation of the object.
|
62
|
+
"""
|
63
|
+
if hasattr(o, "to_dict"):
|
64
|
+
return o.to_dict()
|
65
|
+
return super().default(o)
|
66
|
+
|
67
|
+
|
68
|
+
class BaseResponse:
|
69
|
+
"""
|
70
|
+
Base class for API responses.
|
71
|
+
|
72
|
+
Attributes:
|
73
|
+
Meta (class): Meta class for BaseResponse.
|
74
|
+
"""
|
75
|
+
|
76
|
+
def __init__(self, **kwargs):
|
77
|
+
"""Initialize the BaseResponse object with optional keyword arguments."""
|
78
|
+
pass
|
79
|
+
|
80
|
+
class Meta:
|
81
|
+
"""Meta class for BaseResponse."""
|
82
|
+
|
83
|
+
abstract = True
|
84
|
+
|
85
|
+
def __str__(self):
|
86
|
+
"""Return a string representation of the BaseResponse object."""
|
87
|
+
return self.__repr__()
|
88
|
+
|
89
|
+
def __repr__(self):
|
90
|
+
"""Return a string representation of the BaseResponse object."""
|
91
|
+
main_field = getattr(self.Meta, "main_field", "")
|
92
|
+
main_field_value = f" {getattr(self, main_field)}" if main_field else ""
|
93
|
+
return f"<{self.__class__.__name__}{main_field_value}>"
|
94
|
+
|
95
|
+
def fields(self):
|
96
|
+
"""Return a dictionary of attributes and their values."""
|
97
|
+
attributes = vars(self)
|
98
|
+
return attributes
|
99
|
+
|
100
|
+
def attr(
|
101
|
+
self,
|
102
|
+
key,
|
103
|
+
default=None,
|
104
|
+
cast=None,
|
105
|
+
jsonify=False,
|
106
|
+
to_epoch=False,
|
107
|
+
to_date_str=None,
|
108
|
+
str_date_format=None,
|
109
|
+
auto_format=False,
|
110
|
+
):
|
111
|
+
"""Get and potentially format an attribute of the BaseResponse object.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
key (str): The name of the attribute to retrieve.
|
115
|
+
default: The default value to return if the attribute is not found.
|
116
|
+
cast (type): The type to which the attribute should be cast.
|
117
|
+
jsonify (bool): Whether to parse the attribute as JSON.
|
118
|
+
to_epoch (bool): Whether to convert the attribute to an epoch timestamp.
|
119
|
+
to_date_str (str): The date string format to use.
|
120
|
+
str_date_format (str): The date string format to use.
|
121
|
+
auto_format (bool): Whether to automatically format the attribute.
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
The formatted attribute value.
|
125
|
+
|
126
|
+
Raises:
|
127
|
+
Exception: If an error occurs while retrieving or formatting the attribute.
|
128
|
+
"""
|
129
|
+
try:
|
130
|
+
value = rgetattr(self, key, default)
|
131
|
+
value = value if value is not None else default
|
132
|
+
if not value:
|
133
|
+
return value
|
134
|
+
if auto_format:
|
135
|
+
if isinstance(value, (datetime, date)):
|
136
|
+
to_date_str = True
|
137
|
+
if isinstance(value, (list, dict)):
|
138
|
+
jsonify = True
|
139
|
+
if isinstance(value, str) and value.isdigit():
|
140
|
+
cast = int
|
141
|
+
if cast:
|
142
|
+
value = cast(value)
|
143
|
+
if jsonify:
|
144
|
+
value = ast.literal_eval(value)
|
145
|
+
if to_date_str:
|
146
|
+
str_date_format = str_date_format or "%Y-%m-%dT%H:%M:%S"
|
147
|
+
value = value.strftime(str_date_format)
|
148
|
+
if to_epoch:
|
149
|
+
value = value.timestamp()
|
150
|
+
return value
|
151
|
+
except Exception as ex:
|
152
|
+
raise Exception(f"Unable to get field attribute: `{key}` of {self} ex: {ex}")
|
153
|
+
|
154
|
+
def styling(self, value, camel_case=False, dotted_key_merge=False):
|
155
|
+
"""Style a value according to specified formatting options.
|
156
|
+
|
157
|
+
Args:
|
158
|
+
value (str): The value to be styled.
|
159
|
+
camel_case (bool): Whether to use camelCase formatting.
|
160
|
+
dotted_key_merge (bool): Whether to merge keys separated by dots.
|
161
|
+
|
162
|
+
Returns:
|
163
|
+
The styled value.
|
164
|
+
|
165
|
+
Raises:
|
166
|
+
Exception: If an error occurs while styling the value.
|
167
|
+
"""
|
168
|
+
try:
|
169
|
+
if dotted_key_merge:
|
170
|
+
value = value.replace(".", "_")
|
171
|
+
if camel_case:
|
172
|
+
tmp = value.replace("_", " ").title().replace(" ", "")
|
173
|
+
return tmp[0].lower() + tmp[1:]
|
174
|
+
return value
|
175
|
+
except Exception as ex:
|
176
|
+
raise Exception(f"Unable to set styling for value: {value}: {ex}")
|
177
|
+
|
178
|
+
def to_dict(self, ignore_none=False, dotted_key_to_dict=False) -> dict:
|
179
|
+
"""Convert the BaseResponse object to a dictionary.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
ignore_none (bool): Whether to exclude attributes with None values.
|
183
|
+
dotted_key_to_dict (bool): Whether to convert dotted keys to nested dictionaries.
|
184
|
+
|
185
|
+
Returns:
|
186
|
+
A dictionary representation of the BaseResponse object.
|
187
|
+
|
188
|
+
Raises:
|
189
|
+
Exception: If an error occurs while converting the object to a dictionary.
|
190
|
+
"""
|
191
|
+
returned_fields = self.fields()
|
192
|
+
key_styling = functools.partial(self.styling, camel_case=False, dotted_key_merge=False)
|
193
|
+
try:
|
194
|
+
fields_data = {}
|
195
|
+
for field in returned_fields:
|
196
|
+
try:
|
197
|
+
fields_data[key_styling(field)] = self.attr(key=field)
|
198
|
+
except Exception as ex:
|
199
|
+
logger.warning(f"Failed to get value for {field}: {ex}")
|
200
|
+
if ignore_none:
|
201
|
+
fields_data = {k: v for k, v in fields_data.items() if v is not None}
|
202
|
+
if dotted_key_to_dict:
|
203
|
+
new_fields_data = {}
|
204
|
+
for key, value in fields_data.items():
|
205
|
+
items = key.split(".")
|
206
|
+
if len(items) > 1:
|
207
|
+
new_fields_data.setdefault(items[0], {})[items[1]] = value
|
208
|
+
else:
|
209
|
+
new_fields_data[key] = value
|
210
|
+
fields_data = new_fields_data
|
211
|
+
except Exception as ex:
|
212
|
+
raise Exception(f"Unable to get fields: {returned_fields}, ex: {ex}")
|
213
|
+
return fields_data
|
214
|
+
|
215
|
+
def to_json(self) -> str:
|
216
|
+
"""Convert the object to a JSON-formatted string.
|
217
|
+
|
218
|
+
Returns:
|
219
|
+
str: The JSON-formatted string representation of the object.
|
220
|
+
"""
|
221
|
+
return json.dumps(self.to_dict(), cls=ResponseJSONEncoder)
|
@@ -0,0 +1,176 @@
|
|
1
|
+
import logging
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
from plexflow.core.subtitles.providers.oss.utils.response_base import BaseResponse
|
5
|
+
|
6
|
+
logger = logging.getLogger(__name__)
|
7
|
+
|
8
|
+
|
9
|
+
class LoginResponse(BaseResponse):
|
10
|
+
"""Response class for the login results."""
|
11
|
+
|
12
|
+
def __init__(self, user, token, status):
|
13
|
+
"""
|
14
|
+
Initialize a LoginResponse object with user data, token, and status.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
user (User): An dict representing the user data.
|
18
|
+
token (str): The authentication token.
|
19
|
+
status (int): The status code of the login response (e.g., 200 for success).
|
20
|
+
|
21
|
+
|
22
|
+
Example:
|
23
|
+
# Create a LoginResponse object
|
24
|
+
user_data = {allowed_translations=1, allowed_downloads=20, level='Sub leecher', user_id=502210,
|
25
|
+
ext_installed=False, vip=False}
|
26
|
+
login_response = LoginResponse(user=user_data, token='eyJ0eXAiOiJKV1Qi...', status=200)
|
27
|
+
"""
|
28
|
+
self.user = user
|
29
|
+
self.token = token
|
30
|
+
self.status = status
|
31
|
+
|
32
|
+
class Meta:
|
33
|
+
"""Meta class for LoginResponse."""
|
34
|
+
|
35
|
+
main_field = "status"
|
36
|
+
|
37
|
+
|
38
|
+
class SearchResponse(BaseResponse):
|
39
|
+
"""Response class for search results."""
|
40
|
+
|
41
|
+
def __init__(self, total_pages, total_count, per_page, page, data, query_string=None):
|
42
|
+
"""Initialize the SearchResponse object with search-related data.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
total_pages (int): The total number of pages in the search results.
|
46
|
+
total_count (int): The total number of search results.
|
47
|
+
per_page (int): The number of results per page.
|
48
|
+
page (int): The current page number.
|
49
|
+
data (list): A list of data items for each search result.
|
50
|
+
query_string (str): The search query string.
|
51
|
+
"""
|
52
|
+
self.total_pages = total_pages
|
53
|
+
self.total_count = total_count
|
54
|
+
self.per_page = per_page
|
55
|
+
self.page = page
|
56
|
+
self.data = [Subtitle(item) for item in data]
|
57
|
+
self.query_string = query_string
|
58
|
+
|
59
|
+
class Meta:
|
60
|
+
"""Meta class for SearchResponse."""
|
61
|
+
|
62
|
+
main_field = "query_string"
|
63
|
+
|
64
|
+
|
65
|
+
class DiscoverLatestResponse(BaseResponse):
|
66
|
+
"""Response class for discover latest results."""
|
67
|
+
|
68
|
+
def __init__(self, total_pages, total_count, page, data):
|
69
|
+
"""Initialize the DiscoverLatestResponse object with latest discovery data.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
total_pages (int): The total number of pages in the discovery results.
|
73
|
+
total_count (int): The total number of discovery results.
|
74
|
+
page (int): The current page number.
|
75
|
+
data (list): A list of data items for each discovery result.
|
76
|
+
"""
|
77
|
+
self.total_pages = total_pages
|
78
|
+
self.total_count = total_count
|
79
|
+
self.page = page
|
80
|
+
self.data = [Subtitle(item) for item in data]
|
81
|
+
self.created_at = datetime.now()
|
82
|
+
self.created_at_str = self.created_at.strftime("%Y-%m-%d %H:%M%z")
|
83
|
+
|
84
|
+
class Meta:
|
85
|
+
"""Meta class for DiscoverLatestResponse."""
|
86
|
+
|
87
|
+
main_field = "created_at_str"
|
88
|
+
|
89
|
+
|
90
|
+
class DiscoverMostDownloadedResponse(DiscoverLatestResponse):
|
91
|
+
"""Response class for the discovre most downloaded results."""
|
92
|
+
|
93
|
+
pass
|
94
|
+
|
95
|
+
|
96
|
+
class DownloadResponse(BaseResponse):
|
97
|
+
"""Response class for the download subtitles results."""
|
98
|
+
|
99
|
+
def __init__(self, response_data):
|
100
|
+
"""Initialize the DownloadResponse object with download-related data.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
response_data (dict): A dictionary containing download-related information.
|
104
|
+
"""
|
105
|
+
self.link = response_data.get("link")
|
106
|
+
self.file_name = response_data.get("file_name")
|
107
|
+
self.requests = response_data.get("requests")
|
108
|
+
self.remaining = response_data.get("remaining")
|
109
|
+
self.message = response_data.get("message")
|
110
|
+
self.reset_time = response_data.get("reset_time")
|
111
|
+
self.reset_time_utc = response_data.get("reset_time_utc")
|
112
|
+
self.uk = response_data.get("uk")
|
113
|
+
self.uid = response_data.get("uid")
|
114
|
+
self.ts = response_data.get("ts")
|
115
|
+
|
116
|
+
class Meta:
|
117
|
+
"""Meta class for DownloadResponse."""
|
118
|
+
|
119
|
+
main_field = "file_name"
|
120
|
+
|
121
|
+
|
122
|
+
class Subtitle(BaseResponse):
|
123
|
+
"""Object representing a subtitle data given from API."""
|
124
|
+
|
125
|
+
def __init__(self, data_dict):
|
126
|
+
"""Initialize the Subtitle object with subtitle-related data.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
data_dict (dict): A dictionary containing subtitle-related information.
|
130
|
+
"""
|
131
|
+
self.id = data_dict.get("id")
|
132
|
+
self.type = data_dict.get("type")
|
133
|
+
self.subtitle_id = data_dict.get("attributes", {}).get("subtitle_id")
|
134
|
+
self.language = data_dict.get("attributes", {}).get("language")
|
135
|
+
self.download_count = data_dict.get("attributes", {}).get("download_count")
|
136
|
+
self.new_download_count = data_dict.get("attributes", {}).get("new_download_count")
|
137
|
+
self.hearing_impaired = data_dict.get("attributes", {}).get("hearing_impaired")
|
138
|
+
self.hd = data_dict.get("attributes", {}).get("hd")
|
139
|
+
self.fps = data_dict.get("attributes", {}).get("fps")
|
140
|
+
self.votes = data_dict.get("attributes", {}).get("votes")
|
141
|
+
self.ratings = data_dict.get("attributes", {}).get("ratings")
|
142
|
+
self.from_trusted = data_dict.get("attributes", {}).get("from_trusted")
|
143
|
+
self.foreign_parts_only = data_dict.get("attributes", {}).get("foreign_parts_only")
|
144
|
+
self.upload_date = data_dict.get("attributes", {}).get("upload_date")
|
145
|
+
self.ai_translated = data_dict.get("attributes", {}).get("ai_translated")
|
146
|
+
self.machine_translated = data_dict.get("attributes", {}).get("machine_translated")
|
147
|
+
self.release = data_dict.get("attributes", {}).get("release")
|
148
|
+
self.comments = data_dict.get("attributes", {}).get("comments")
|
149
|
+
self.legacy_subtitle_id = data_dict.get("attributes", {}).get("legacy_subtitle_id")
|
150
|
+
self.uploader_id = data_dict.get("attributes", {}).get("uploader", {}).get("uploader_id")
|
151
|
+
self.uploader_name = data_dict.get("attributes", {}).get("uploader", {}).get("name")
|
152
|
+
self.uploader_rank = data_dict.get("attributes", {}).get("uploader", {}).get("rank")
|
153
|
+
self.feature_id = data_dict.get("attributes", {}).get("feature_details", {}).get("feature_id")
|
154
|
+
self.feature_type = data_dict.get("attributes", {}).get("feature_details", {}).get("feature_type")
|
155
|
+
self.year = data_dict.get("attributes", {}).get("feature_details", {}).get("year")
|
156
|
+
self.title = data_dict.get("attributes", {}).get("feature_details", {}).get("title")
|
157
|
+
self.movie_name = data_dict.get("attributes", {}).get("feature_details", {}).get("movie_name")
|
158
|
+
self.imdb_id = data_dict.get("attributes", {}).get("feature_details", {}).get("imdb_id")
|
159
|
+
self.tmdb_id = data_dict.get("attributes", {}).get("feature_details", {}).get("tmdb_id")
|
160
|
+
self.season_number = data_dict.get("attributes", {}).get("feature_details", {}).get("season_number")
|
161
|
+
self.episode_number = data_dict.get("attributes", {}).get("feature_details", {}).get("episode_number")
|
162
|
+
self.parent_imdb_id = data_dict.get("attributes", {}).get("feature_details", {}).get("parent_imdb_id")
|
163
|
+
self.parent_title = data_dict.get("attributes", {}).get("feature_details", {}).get("parent_title")
|
164
|
+
self.parent_tmdb_id = data_dict.get("attributes", {}).get("feature_details", {}).get("parent_tmdb_id")
|
165
|
+
self.parent_feature_id = data_dict.get("attributes", {}).get("feature_details", {}).get("parent_feature_id")
|
166
|
+
self.url = data_dict.get("attributes", {}).get("url")
|
167
|
+
self.related_links = data_dict.get("attributes", {}).get("related_links", [])
|
168
|
+
self.files = data_dict.get("attributes", {}).get("files", [])
|
169
|
+
|
170
|
+
self.file_id = self.files[0].get("file_id") if self.files else None
|
171
|
+
self.file_name = self.files[0].get("file_name") if self.files else None
|
172
|
+
|
173
|
+
class Meta:
|
174
|
+
"""Meta class for Subtitle."""
|
175
|
+
|
176
|
+
main_field = "release"
|