yutipy 2.2.9__tar.gz → 2.2.11__tar.gz
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.
Potentially problematic release.
This version of yutipy might be problematic. Click here for more details.
- {yutipy-2.2.9 → yutipy-2.2.11}/MANIFEST.in +0 -2
- {yutipy-2.2.9 → yutipy-2.2.11}/PKG-INFO +4 -6
- {yutipy-2.2.9 → yutipy-2.2.11}/README.md +1 -1
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/index.rst +1 -1
- {yutipy-2.2.9 → yutipy-2.2.11}/pyproject.toml +2 -7
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/base_clients.py +4 -4
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/deezer.py +2 -2
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/itunes.py +1 -1
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/utils/helpers.py +80 -13
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy.egg-info/PKG-INFO +4 -6
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy.egg-info/requires.txt +0 -3
- {yutipy-2.2.9 → yutipy-2.2.11}/.gitattributes +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/.github/FUNDING.yml +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/.github/dependabot.yml +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/.github/workflows/pytest-unit-testing.yml +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/.github/workflows/release.yml +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/.gitignore +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/.readthedocs.yaml +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/LICENSE +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/Makefile +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/_static/yutipy_header.png +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/_static/yutipy_logo.png +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/api_reference.rst +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/available_platforms.rst +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/cli.rst +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/conf.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/faq.rst +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/installation.rst +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/make.bat +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/requirements.txt +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/docs/usage_examples.rst +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/requirements-dev.txt +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/requirements.txt +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/setup.cfg +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/__init__.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/test_deezer.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/test_itunes.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/test_kkbox.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/test_lastfm.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/test_models.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/test_musicyt.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/test_spotify.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/tests/test_utils.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/__init__.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/cli/__init__.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/cli/config.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/cli/search.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/exceptions.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/kkbox.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/lastfm.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/logger.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/models.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/musicyt.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/spotify.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/utils/__init__.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy/yutipy_music.py +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy.egg-info/SOURCES.txt +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy.egg-info/dependency_links.txt +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy.egg-info/entry_points.txt +0 -0
- {yutipy-2.2.9 → yutipy-2.2.11}/yutipy.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yutipy
|
|
3
|
-
Version: 2.2.
|
|
4
|
-
Summary: A simple package
|
|
3
|
+
Version: 2.2.11
|
|
4
|
+
Summary: A simple Python package to interact with various music platforms APIs.
|
|
5
5
|
Author: Cheap Nightbot
|
|
6
6
|
Author-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
7
7
|
Maintainer-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
@@ -12,7 +12,7 @@ Project-URL: Repository, https://github.com/CheapNightbot/yutipy.git
|
|
|
12
12
|
Project-URL: Issues, https://github.com/CheapNightbot/yutipy/issues
|
|
13
13
|
Project-URL: Changelog, https://github.com/CheapNightbot/yutipy/blob/master/CHANGELOG.md
|
|
14
14
|
Project-URL: funding, https://ko-fi.com/cheapnightbot
|
|
15
|
-
Keywords: music,API,Deezer,iTunes,KKBox,Spotify,YouTube Music,search,retrieve,information,yutify
|
|
15
|
+
Keywords: music,API,Deezer,iTunes,KKBox,Lastfm,Spotify,YouTube Music,search,retrieve,information,yutify
|
|
16
16
|
Classifier: Development Status :: 4 - Beta
|
|
17
17
|
Classifier: Intended Audience :: Developers
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries
|
|
@@ -30,8 +30,6 @@ Requires-Dist: python-dotenv==1.1.0
|
|
|
30
30
|
Requires-Dist: rapidfuzz==3.13.0
|
|
31
31
|
Requires-Dist: requests==2.32.3
|
|
32
32
|
Requires-Dist: ytmusicapi==1.10.3
|
|
33
|
-
Provides-Extra: dev
|
|
34
|
-
Requires-Dist: pytest; extra == "dev"
|
|
35
33
|
Dynamic: license-file
|
|
36
34
|
|
|
37
35
|
<p align="center">
|
|
@@ -59,7 +57,7 @@ Dynamic: license-file
|
|
|
59
57
|
</a>
|
|
60
58
|
</p>
|
|
61
59
|
|
|
62
|
-
A _**simple**_ Python package
|
|
60
|
+
A _**simple**_ Python package to interact with various music platforms APIs.
|
|
63
61
|
|
|
64
62
|
## Table of Contents
|
|
65
63
|
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</a>
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
|
-
A _**simple**_ Python package
|
|
26
|
+
A _**simple**_ Python package to interact with various music platforms APIs.
|
|
27
27
|
|
|
28
28
|
## Table of Contents
|
|
29
29
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
:target: https://github.com/CheapNightbot/yutipy
|
|
4
4
|
:align: center
|
|
5
5
|
|
|
6
|
-
**yutipy** is a Python package
|
|
6
|
+
**yutipy** is a Python package to interact and retrieving music information from various music platforms (see list of :doc:`available muisc platforms <available_platforms>`).
|
|
7
7
|
This documentation will help you get started with yutipy and provide detailed information about its features and usage.
|
|
8
8
|
|
|
9
9
|
.. raw:: html
|
|
@@ -20,10 +20,10 @@ authors = [
|
|
|
20
20
|
maintainers = [
|
|
21
21
|
{name = "Cheap Nightbot", email = "hi@cheapnightbot.slmail.me"}
|
|
22
22
|
]
|
|
23
|
-
description = "A simple package
|
|
23
|
+
description = "A simple Python package to interact with various music platforms APIs."
|
|
24
24
|
readme = "README.md"
|
|
25
25
|
license = "MIT"
|
|
26
|
-
keywords = ["music", "API", "Deezer", "iTunes", "KKBox", "Spotify", "YouTube Music", "search", "retrieve", "information", "yutify"]
|
|
26
|
+
keywords = ["music", "API", "Deezer", "iTunes", "KKBox", "Lastfm", "Spotify", "YouTube Music", "search", "retrieve", "information", "yutify"]
|
|
27
27
|
classifiers = [
|
|
28
28
|
"Development Status :: 4 - Beta",
|
|
29
29
|
"Intended Audience :: Developers",
|
|
@@ -36,11 +36,6 @@ classifiers = [
|
|
|
36
36
|
"Operating System :: OS Independent",
|
|
37
37
|
]
|
|
38
38
|
|
|
39
|
-
[project.optional-dependencies]
|
|
40
|
-
dev = [
|
|
41
|
-
"pytest",
|
|
42
|
-
]
|
|
43
|
-
|
|
44
39
|
[project.urls]
|
|
45
40
|
Homepage = "https://github.com/CheapNightbot/yutipy"
|
|
46
41
|
Documentation = "https://yutipy.readthedocs.io/"
|
|
@@ -87,7 +87,7 @@ class BaseClient:
|
|
|
87
87
|
token_info = None
|
|
88
88
|
try:
|
|
89
89
|
token_info = self.load_access_token()
|
|
90
|
-
if not isinstance(token_info, dict):
|
|
90
|
+
if token_info and not isinstance(token_info, dict):
|
|
91
91
|
raise InvalidValueException(
|
|
92
92
|
"`load_access_token()` should return a dict."
|
|
93
93
|
)
|
|
@@ -172,7 +172,7 @@ class BaseClient:
|
|
|
172
172
|
self._token_expires_in = token_info.get("expires_in")
|
|
173
173
|
self._token_requested_at = token_info.get("requested_at")
|
|
174
174
|
else:
|
|
175
|
-
logger.
|
|
175
|
+
logger.debug("The access token is still valid, no need to refresh.")
|
|
176
176
|
except TypeError:
|
|
177
177
|
logger.debug(
|
|
178
178
|
f"token requested at: {self._token_requested_at} | token expires in: {self._token_expires_in}"
|
|
@@ -316,7 +316,7 @@ class BaseAuthClient:
|
|
|
316
316
|
token_info = None
|
|
317
317
|
try:
|
|
318
318
|
token_info = self.load_access_token()
|
|
319
|
-
if not isinstance(token_info, dict):
|
|
319
|
+
if token_info and not isinstance(token_info, dict):
|
|
320
320
|
raise InvalidValueException(
|
|
321
321
|
"`load_access_token()` should return a dict."
|
|
322
322
|
)
|
|
@@ -419,7 +419,7 @@ class BaseAuthClient:
|
|
|
419
419
|
self._token_requested_at = token_info.get("requested_at")
|
|
420
420
|
|
|
421
421
|
else:
|
|
422
|
-
logger.
|
|
422
|
+
logger.debug("The access token is still valid, no need to refresh.")
|
|
423
423
|
except TypeError:
|
|
424
424
|
logger.debug(
|
|
425
425
|
f"token requested at: {self._token_requested_at} | token expires in: {self._token_expires_in}"
|
|
@@ -95,7 +95,7 @@ class Deezer:
|
|
|
95
95
|
return None
|
|
96
96
|
|
|
97
97
|
try:
|
|
98
|
-
logger.debug(f"Parsing response JSON
|
|
98
|
+
logger.debug(f"Parsing response JSON.")
|
|
99
99
|
result = response.json()["data"]
|
|
100
100
|
except (IndexError, KeyError, ValueError) as e:
|
|
101
101
|
logger.warning(f"Invalid response structure from Deezer: {e}")
|
|
@@ -159,7 +159,7 @@ class Deezer:
|
|
|
159
159
|
return None
|
|
160
160
|
|
|
161
161
|
try:
|
|
162
|
-
logger.debug(f"Response JSON
|
|
162
|
+
logger.debug(f"Parsing Response JSON.")
|
|
163
163
|
result = response.json()
|
|
164
164
|
except ValueError as e:
|
|
165
165
|
logger.warning(f"Invalid response received from Deezer: {e}")
|
|
@@ -95,7 +95,7 @@ class Itunes:
|
|
|
95
95
|
return None
|
|
96
96
|
|
|
97
97
|
try:
|
|
98
|
-
logger.debug(f"Parsing response JSON
|
|
98
|
+
logger.debug(f"Parsing response JSON.")
|
|
99
99
|
result = response.json()["results"]
|
|
100
100
|
except (IndexError, KeyError, ValueError) as e:
|
|
101
101
|
logger.warning(f"Invalid response structure from iTunes: {e}")
|
|
@@ -5,6 +5,13 @@ from rapidfuzz.utils import default_process
|
|
|
5
5
|
|
|
6
6
|
kakasi = pykakasi.kakasi()
|
|
7
7
|
|
|
8
|
+
TRANSLATION_CACHE = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def similarity(str1: str, str2: str, threshold: int = 80):
|
|
12
|
+
similarity_score = fuzz.WRatio(str1, str2, processor=default_process)
|
|
13
|
+
return similarity_score > threshold
|
|
14
|
+
|
|
8
15
|
|
|
9
16
|
def translate_text(
|
|
10
17
|
text: str,
|
|
@@ -76,30 +83,90 @@ def are_strings_similar(
|
|
|
76
83
|
bool: True if the strings are similar, otherwise False.
|
|
77
84
|
"""
|
|
78
85
|
|
|
86
|
+
"""
|
|
87
|
+
note for myself so that it make sense later ~ _(:з)∠)_
|
|
88
|
+
0. Check cached strings for comparision
|
|
89
|
+
a. if found and same, return True.
|
|
90
|
+
1. normalize original strings.
|
|
91
|
+
a. if both same, return True.
|
|
92
|
+
2. translate original string.
|
|
93
|
+
a. if both same, return True.
|
|
94
|
+
3. translate normalized string.
|
|
95
|
+
a. if both same, return True.
|
|
96
|
+
4. return False.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
# ### Step 0 ####
|
|
100
|
+
cached_str1 = TRANSLATION_CACHE.get(str1, str1)
|
|
101
|
+
cached_str2 = TRANSLATION_CACHE.get(str2, str2)
|
|
102
|
+
similar = similarity(cached_str1, cached_str2, threshold=threshold)
|
|
103
|
+
if similar:
|
|
104
|
+
return True
|
|
105
|
+
# ###############
|
|
106
|
+
|
|
107
|
+
# ### Step 1 ####
|
|
108
|
+
# Transliterate / Normalize Strings
|
|
109
|
+
normalized_str1 = (
|
|
110
|
+
TRANSLATION_CACHE.get(str1)
|
|
111
|
+
or "".join(item["hepburn"] for item in kakasi.convert(str1))
|
|
112
|
+
or str1
|
|
113
|
+
)
|
|
114
|
+
normalized_str2 = (
|
|
115
|
+
TRANSLATION_CACHE.get(str2)
|
|
116
|
+
or "".join(item["hepburn"] for item in kakasi.convert(str2))
|
|
117
|
+
or str2
|
|
118
|
+
)
|
|
119
|
+
similar = similarity(normalized_str1, normalized_str2, threshold=threshold)
|
|
120
|
+
if similar:
|
|
121
|
+
TRANSLATION_CACHE[str1] = normalized_str1
|
|
122
|
+
TRANSLATION_CACHE[str2] = normalized_str2
|
|
123
|
+
return True
|
|
124
|
+
# ###############
|
|
125
|
+
|
|
79
126
|
if use_translation:
|
|
80
|
-
|
|
81
|
-
|
|
127
|
+
# ### Step 2 ####
|
|
128
|
+
original_translated_str1 = (
|
|
129
|
+
TRANSLATION_CACHE.get(str1)
|
|
130
|
+
or translate_text(str1, session=translation_session)["destination-text"]
|
|
82
131
|
if translation_session
|
|
83
132
|
else translate_text(str1)["destination-text"]
|
|
84
133
|
)
|
|
85
|
-
|
|
86
|
-
|
|
134
|
+
original_translated_str2 = (
|
|
135
|
+
TRANSLATION_CACHE.get(str2)
|
|
136
|
+
or translate_text(str2, session=translation_session)["destination-text"]
|
|
87
137
|
if translation_session
|
|
88
138
|
else translate_text(str2)["destination-text"]
|
|
89
139
|
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
translated_str1, translated_str2, processor=default_process
|
|
140
|
+
similar = similarity(
|
|
141
|
+
original_translated_str1, original_translated_str2, threshold=threshold
|
|
93
142
|
)
|
|
94
|
-
if
|
|
143
|
+
if similar:
|
|
144
|
+
TRANSLATION_CACHE[str1] = original_translated_str1
|
|
145
|
+
TRANSLATION_CACHE[str2] = original_translated_str2
|
|
95
146
|
return True
|
|
147
|
+
# ###############
|
|
96
148
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
149
|
+
normalized_translated_str1 = (
|
|
150
|
+
TRANSLATION_CACHE.get(str1)
|
|
151
|
+
or translate_text(str1, session=translation_session)["destination-text"]
|
|
152
|
+
if translation_session
|
|
153
|
+
else translation_session(str1)["destination-text"]
|
|
154
|
+
)
|
|
155
|
+
normalized_translated_str2 = (
|
|
156
|
+
TRANSLATION_CACHE.get(str2)
|
|
157
|
+
or translate_text(str2, session=translation_session)["destination-text"]
|
|
158
|
+
if translation_session
|
|
159
|
+
else translate_text(str2)["destination-text"]
|
|
160
|
+
)
|
|
161
|
+
similar = similarity(
|
|
162
|
+
normalized_translated_str1, normalized_translated_str2, threshold=threshold
|
|
163
|
+
)
|
|
164
|
+
if similar:
|
|
165
|
+
TRANSLATION_CACHE[str1] = normalized_translated_str1
|
|
166
|
+
TRANSLATION_CACHE[str2] = normalized_translated_str2
|
|
167
|
+
return True
|
|
100
168
|
|
|
101
|
-
|
|
102
|
-
return similarity_score > threshold
|
|
169
|
+
return False
|
|
103
170
|
|
|
104
171
|
|
|
105
172
|
def separate_artists(artists: str, custom_separator: str = None) -> list[str]:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: yutipy
|
|
3
|
-
Version: 2.2.
|
|
4
|
-
Summary: A simple package
|
|
3
|
+
Version: 2.2.11
|
|
4
|
+
Summary: A simple Python package to interact with various music platforms APIs.
|
|
5
5
|
Author: Cheap Nightbot
|
|
6
6
|
Author-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
7
7
|
Maintainer-email: Cheap Nightbot <hi@cheapnightbot.slmail.me>
|
|
@@ -12,7 +12,7 @@ Project-URL: Repository, https://github.com/CheapNightbot/yutipy.git
|
|
|
12
12
|
Project-URL: Issues, https://github.com/CheapNightbot/yutipy/issues
|
|
13
13
|
Project-URL: Changelog, https://github.com/CheapNightbot/yutipy/blob/master/CHANGELOG.md
|
|
14
14
|
Project-URL: funding, https://ko-fi.com/cheapnightbot
|
|
15
|
-
Keywords: music,API,Deezer,iTunes,KKBox,Spotify,YouTube Music,search,retrieve,information,yutify
|
|
15
|
+
Keywords: music,API,Deezer,iTunes,KKBox,Lastfm,Spotify,YouTube Music,search,retrieve,information,yutify
|
|
16
16
|
Classifier: Development Status :: 4 - Beta
|
|
17
17
|
Classifier: Intended Audience :: Developers
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries
|
|
@@ -30,8 +30,6 @@ Requires-Dist: python-dotenv==1.1.0
|
|
|
30
30
|
Requires-Dist: rapidfuzz==3.13.0
|
|
31
31
|
Requires-Dist: requests==2.32.3
|
|
32
32
|
Requires-Dist: ytmusicapi==1.10.3
|
|
33
|
-
Provides-Extra: dev
|
|
34
|
-
Requires-Dist: pytest; extra == "dev"
|
|
35
33
|
Dynamic: license-file
|
|
36
34
|
|
|
37
35
|
<p align="center">
|
|
@@ -59,7 +57,7 @@ Dynamic: license-file
|
|
|
59
57
|
</a>
|
|
60
58
|
</p>
|
|
61
59
|
|
|
62
|
-
A _**simple**_ Python package
|
|
60
|
+
A _**simple**_ Python package to interact with various music platforms APIs.
|
|
63
61
|
|
|
64
62
|
## Table of Contents
|
|
65
63
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|