pymal-ng 1.0.0__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.
Files changed (35) hide show
  1. pymal_ng-1.0.0/LICENSE +27 -0
  2. pymal_ng-1.0.0/PKG-INFO +177 -0
  3. pymal_ng-1.0.0/README.md +152 -0
  4. pymal_ng-1.0.0/pymal/__init__.py +9 -0
  5. pymal_ng-1.0.0/pymal/account.py +270 -0
  6. pymal_ng-1.0.0/pymal/account_objects/__init__.py +1 -0
  7. pymal_ng-1.0.0/pymal/account_objects/account_animes.py +181 -0
  8. pymal_ng-1.0.0/pymal/account_objects/account_friends.py +88 -0
  9. pymal_ng-1.0.0/pymal/account_objects/account_mangas.py +160 -0
  10. pymal_ng-1.0.0/pymal/account_objects/my_anime.py +715 -0
  11. pymal_ng-1.0.0/pymal/account_objects/my_manga.py +738 -0
  12. pymal_ng-1.0.0/pymal/anime.py +640 -0
  13. pymal_ng-1.0.0/pymal/consts.py +19 -0
  14. pymal_ng-1.0.0/pymal/decorators.py +34 -0
  15. pymal_ng-1.0.0/pymal/exceptions/__init__.py +29 -0
  16. pymal_ng-1.0.0/pymal/exceptions/failed_to_parse_error.py +18 -0
  17. pymal_ng-1.0.0/pymal/exceptions/my_anime_list_api_error.py +27 -0
  18. pymal_ng-1.0.0/pymal/global_functions.py +215 -0
  19. pymal_ng-1.0.0/pymal/inner_objects/__init__.py +4 -0
  20. pymal_ng-1.0.0/pymal/inner_objects/recommendation.py +51 -0
  21. pymal_ng-1.0.0/pymal/inner_objects/review.py +52 -0
  22. pymal_ng-1.0.0/pymal/inner_objects/season.py +102 -0
  23. pymal_ng-1.0.0/pymal/manga.py +587 -0
  24. pymal_ng-1.0.0/pymal/searches/README.md +15 -0
  25. pymal_ng-1.0.0/pymal/searches/__init__.py +47 -0
  26. pymal_ng-1.0.0/pymal/searches/search_accounts_provider.py +23 -0
  27. pymal_ng-1.0.0/pymal/searches/search_animes_provider.py +24 -0
  28. pymal_ng-1.0.0/pymal/searches/search_mangas_provider.py +24 -0
  29. pymal_ng-1.0.0/pymal/searches/search_provider.py +96 -0
  30. pymal_ng-1.0.0/pymal/seasons.py +73 -0
  31. pymal_ng-1.0.0/pymal/types/README.md +13 -0
  32. pymal_ng-1.0.0/pymal/types/ReloadedSet.py +110 -0
  33. pymal_ng-1.0.0/pymal/types/__init__.py +4 -0
  34. pymal_ng-1.0.0/pyproject.toml +115 -0
  35. pymal_ng-1.0.0/pyproject.toml.orig +80 -0
pymal_ng-1.0.0/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2014, tomergelber
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither the name of the {organization} nor the names of its
15
+ contributors may be used to endorse or promote products derived from
16
+ this software without specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.4
2
+ Name: pymal-ng
3
+ Version: 1.0.0
4
+ Summary: A python api for the website MyAnimeList (or MAL).
5
+ Keywords: myanimelist,mal,anime,manga,api
6
+ Author: varyg
7
+ License-Expression: BSD-3-Clause
8
+ License-File: LICENSE
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Dist: niquests[socks,utls]>=3.13.1,<4.0.0
16
+ Requires-Dist: beautifulsoup4>=4.12.3,<5.0.0
17
+ Requires-Dist: lxml>=6.0.0,<7.0.0
18
+ Requires-Dist: pillow>=12.3.0
19
+ Requires-Dist: singleton-factory>=0.1
20
+ Requires-Python: >=3.10, <3.15
21
+ Project-URL: Homepage, https://github.com/varyg1001/pymal
22
+ Project-URL: Source, https://github.com/varyg1001/pymal
23
+ Project-URL: Upstream, https://github.com/pymal-developers/pymal
24
+ Description-Content-Type: text/markdown
25
+
26
+ # pymal
27
+
28
+ Programmatic access to [MyAnimeList](https://myanimelist.net) data in Python.
29
+ Objects are **lazy-loading** — MAL is only contacted when you first access a property.
30
+
31
+ ---
32
+
33
+ ## Quick start
34
+
35
+ ```python
36
+ from pymal import anime
37
+
38
+ # Lazy — no network call yet
39
+ a = anime.Anime(52991)
40
+
41
+ # First property access triggers the MAL fetch
42
+ print(a.title) # "Sousou no Frieren"
43
+ print(a.score) # 9.07
44
+ print(a.episodes) # 28
45
+ print(a.genres) # {'Adventure': '...', 'Drama': '...', 'Fantasy': '...'}
46
+ print(a.english) # "Frieren: Beyond Journey's End"
47
+ print(a.japanese) # "葬送のフリーレン"
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ ### With uv (recommended)
55
+
56
+ ```bash
57
+ curl -LsSf https://astral.sh/uv/install.sh | sh # install uv once
58
+
59
+ git clone https://github.com/varyg1001/pymal
60
+ cd pymal
61
+ uv sync
62
+ ```
63
+
64
+ ### With pip
65
+
66
+ ```bash
67
+ git clone https://github.com/varyg1001/pymal
68
+ cd pymal
69
+ pip install -e .
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Requirements
75
+
76
+ - Python `>=3.10, <3.15`
77
+ - [`uv`](https://github.com/astral-sh/uv) (recommended) or pip
78
+
79
+ Key runtime dependencies (full list in `pyproject.toml`):
80
+
81
+ | Package | Purpose |
82
+ |---------|---------|
83
+ | `niquests[socks,utls]` | HTTP client (modern drop-in for `requests`) |
84
+ | `beautifulsoup4` + `lxml` | HTML parsing |
85
+ | `singleton-factory` | Object singleton lifecycle |
86
+
87
+ ---
88
+
89
+ ## Search & Endpoint Features
90
+
91
+ - **Search API**: Search providers (`SearchAnimesProvider`, `SearchMangasProvider`) automatically query MyAnimeList's modern `prefix.json` API endpoint (`/search/prefix.json`), falling back gracefully if needed.
92
+ - **Seasons**: Seasonal anime lookups (`Season`, `Seasons`) scrape directly from native MyAnimeList season pages (`/anime/season/<year>/<season>`) and the archive listing (`/anime/season/archive`).
93
+ - **User Lists**: Account anime and manga lists (`AccountAnimes`, `AccountMangas`) utilize MAL's modern JSON list endpoints (`load.json`).
94
+
95
+ ---
96
+
97
+ ## Running tests
98
+
99
+ ```bash
100
+ # Offline suite — no network or account needed (40 tests)
101
+ uv run pytest tests/test_fixes.py -v
102
+
103
+ # Full suite — account tests auto-skip unless credentials are set
104
+ MAL_USERNAME=you MAL_PASSWORD=secret uv run pytest -v
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Project structure
110
+
111
+ ```
112
+ pymal/
113
+ ├── pymal/
114
+ │ ├── anime.py # Anime object
115
+ │ ├── manga.py # Manga object
116
+ │ ├── account.py # MAL account & auth
117
+ │ ├── global_functions.py # HTTP + HTML parsing helpers
118
+ │ ├── account_objects/ # MyAnime, MyManga, lists, friends
119
+ │ ├── searches/ # Search providers
120
+ │ └── inner_objects/ # Season, recommendation, etc.
121
+ ├── tests/
122
+ │ ├── test_fixes.py # Offline test suite (40 tests)
123
+ │ └── account_objects/ # Live account tests (skipped without creds)
124
+ ├── docs/ # MkDocs documentation source
125
+ ├── mkdocs.yml # Docs config (GitHub Pages)
126
+ ├── pyproject.toml # Build config & pinned deps (uv_build)
127
+ └── uv.lock # Lockfile — commit this
128
+ ```
129
+
130
+ ---
131
+
132
+ ## What changed in v1.0.0
133
+
134
+ The original project (v0.5b4–v0.6) had broken against MAL's HTML updates and
135
+ modern Python. v1.0.0 brings it back to life:
136
+
137
+ | # | Area | Fix |
138
+ |---|------|-----|
139
+ | 1 | Parser | `html5lib` → `lxml` (no more `six` conflict) |
140
+ | 2 | HTTP | `requests` → `niquests[socks,utls]` (HTTP/2, QUIC, modern TLS) |
141
+ | 3 | Sidebar | Full rewrite from brittle index-based to label-based lookup (`span.dark_text`) |
142
+ | 4 | Title | Fixed `IndexError` — MAL now wraps `<h1>` content in `<strong>` |
143
+ | 5 | Table | Fixed `AttributeError` — MAL removed `<tbody>` from tables |
144
+ | 6 | Search | Migrated from legacy `anime.php` to MAL's modern `prefix.json` API |
145
+ | 7 | Seasons | Migrated from dead `malupdater.com` to native MAL `/anime/season` pages |
146
+ | 8 | User Lists | Updated `AccountAnimes` & `AccountMangas` to MAL's `load.json` API |
147
+ | 9 | Build | `setup.py` → `uv_build` + `pyproject.toml` |
148
+ | 10 | Tests | New offline suite with 40 tests; account tests skip without credentials |
149
+ | 11 | Docs | Migrated from Sphinx/RST to MkDocs Material (GitHub Pages) |
150
+ | 12 | Cleanup | Removed `.travis.yml`, `MANIFEST.in`, `upload_new_version.bat`, dead HTML fixtures |
151
+
152
+ Also fixed latent bugs: `==` used instead of `=` in `my_anime.py` and
153
+ `my_manga.py` meant `download_episodes` and `times_rewatched` were never
154
+ actually stored.
155
+
156
+ ---
157
+
158
+ ## Credits
159
+
160
+ ### Original authors
161
+
162
+ pymal was created and developed by:
163
+
164
+ - **tomer gelber** — [tomergelber@gmail.com](mailto:tomergelber@gmail.com)
165
+ - **Aur Saraf** — [sonoflilit@gmail.com](mailto:sonoflilit@gmail.com)
166
+
167
+ Original repository: [pymal-developers/pymal](https://github.com/pymal-developers/pymal)
168
+
169
+ ### v1.0.0
170
+
171
+ The update was made with mostly AI. The main reason for the update is the errors cause by the old things used in the last released version. I still feel that this is the easiest way to get info from MAL, even with the original version, since MAL has not changed much.
172
+
173
+ ---
174
+
175
+ ## License
176
+
177
+ BSD License — see original repository for full text.
@@ -0,0 +1,152 @@
1
+ # pymal
2
+
3
+ Programmatic access to [MyAnimeList](https://myanimelist.net) data in Python.
4
+ Objects are **lazy-loading** — MAL is only contacted when you first access a property.
5
+
6
+ ---
7
+
8
+ ## Quick start
9
+
10
+ ```python
11
+ from pymal import anime
12
+
13
+ # Lazy — no network call yet
14
+ a = anime.Anime(52991)
15
+
16
+ # First property access triggers the MAL fetch
17
+ print(a.title) # "Sousou no Frieren"
18
+ print(a.score) # 9.07
19
+ print(a.episodes) # 28
20
+ print(a.genres) # {'Adventure': '...', 'Drama': '...', 'Fantasy': '...'}
21
+ print(a.english) # "Frieren: Beyond Journey's End"
22
+ print(a.japanese) # "葬送のフリーレン"
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Installation
28
+
29
+ ### With uv (recommended)
30
+
31
+ ```bash
32
+ curl -LsSf https://astral.sh/uv/install.sh | sh # install uv once
33
+
34
+ git clone https://github.com/varyg1001/pymal
35
+ cd pymal
36
+ uv sync
37
+ ```
38
+
39
+ ### With pip
40
+
41
+ ```bash
42
+ git clone https://github.com/varyg1001/pymal
43
+ cd pymal
44
+ pip install -e .
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Requirements
50
+
51
+ - Python `>=3.10, <3.15`
52
+ - [`uv`](https://github.com/astral-sh/uv) (recommended) or pip
53
+
54
+ Key runtime dependencies (full list in `pyproject.toml`):
55
+
56
+ | Package | Purpose |
57
+ |---------|---------|
58
+ | `niquests[socks,utls]` | HTTP client (modern drop-in for `requests`) |
59
+ | `beautifulsoup4` + `lxml` | HTML parsing |
60
+ | `singleton-factory` | Object singleton lifecycle |
61
+
62
+ ---
63
+
64
+ ## Search & Endpoint Features
65
+
66
+ - **Search API**: Search providers (`SearchAnimesProvider`, `SearchMangasProvider`) automatically query MyAnimeList's modern `prefix.json` API endpoint (`/search/prefix.json`), falling back gracefully if needed.
67
+ - **Seasons**: Seasonal anime lookups (`Season`, `Seasons`) scrape directly from native MyAnimeList season pages (`/anime/season/<year>/<season>`) and the archive listing (`/anime/season/archive`).
68
+ - **User Lists**: Account anime and manga lists (`AccountAnimes`, `AccountMangas`) utilize MAL's modern JSON list endpoints (`load.json`).
69
+
70
+ ---
71
+
72
+ ## Running tests
73
+
74
+ ```bash
75
+ # Offline suite — no network or account needed (40 tests)
76
+ uv run pytest tests/test_fixes.py -v
77
+
78
+ # Full suite — account tests auto-skip unless credentials are set
79
+ MAL_USERNAME=you MAL_PASSWORD=secret uv run pytest -v
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Project structure
85
+
86
+ ```
87
+ pymal/
88
+ ├── pymal/
89
+ │ ├── anime.py # Anime object
90
+ │ ├── manga.py # Manga object
91
+ │ ├── account.py # MAL account & auth
92
+ │ ├── global_functions.py # HTTP + HTML parsing helpers
93
+ │ ├── account_objects/ # MyAnime, MyManga, lists, friends
94
+ │ ├── searches/ # Search providers
95
+ │ └── inner_objects/ # Season, recommendation, etc.
96
+ ├── tests/
97
+ │ ├── test_fixes.py # Offline test suite (40 tests)
98
+ │ └── account_objects/ # Live account tests (skipped without creds)
99
+ ├── docs/ # MkDocs documentation source
100
+ ├── mkdocs.yml # Docs config (GitHub Pages)
101
+ ├── pyproject.toml # Build config & pinned deps (uv_build)
102
+ └── uv.lock # Lockfile — commit this
103
+ ```
104
+
105
+ ---
106
+
107
+ ## What changed in v1.0.0
108
+
109
+ The original project (v0.5b4–v0.6) had broken against MAL's HTML updates and
110
+ modern Python. v1.0.0 brings it back to life:
111
+
112
+ | # | Area | Fix |
113
+ |---|------|-----|
114
+ | 1 | Parser | `html5lib` → `lxml` (no more `six` conflict) |
115
+ | 2 | HTTP | `requests` → `niquests[socks,utls]` (HTTP/2, QUIC, modern TLS) |
116
+ | 3 | Sidebar | Full rewrite from brittle index-based to label-based lookup (`span.dark_text`) |
117
+ | 4 | Title | Fixed `IndexError` — MAL now wraps `<h1>` content in `<strong>` |
118
+ | 5 | Table | Fixed `AttributeError` — MAL removed `<tbody>` from tables |
119
+ | 6 | Search | Migrated from legacy `anime.php` to MAL's modern `prefix.json` API |
120
+ | 7 | Seasons | Migrated from dead `malupdater.com` to native MAL `/anime/season` pages |
121
+ | 8 | User Lists | Updated `AccountAnimes` & `AccountMangas` to MAL's `load.json` API |
122
+ | 9 | Build | `setup.py` → `uv_build` + `pyproject.toml` |
123
+ | 10 | Tests | New offline suite with 40 tests; account tests skip without credentials |
124
+ | 11 | Docs | Migrated from Sphinx/RST to MkDocs Material (GitHub Pages) |
125
+ | 12 | Cleanup | Removed `.travis.yml`, `MANIFEST.in`, `upload_new_version.bat`, dead HTML fixtures |
126
+
127
+ Also fixed latent bugs: `==` used instead of `=` in `my_anime.py` and
128
+ `my_manga.py` meant `download_episodes` and `times_rewatched` were never
129
+ actually stored.
130
+
131
+ ---
132
+
133
+ ## Credits
134
+
135
+ ### Original authors
136
+
137
+ pymal was created and developed by:
138
+
139
+ - **tomer gelber** — [tomergelber@gmail.com](mailto:tomergelber@gmail.com)
140
+ - **Aur Saraf** — [sonoflilit@gmail.com](mailto:sonoflilit@gmail.com)
141
+
142
+ Original repository: [pymal-developers/pymal](https://github.com/pymal-developers/pymal)
143
+
144
+ ### v1.0.0
145
+
146
+ The update was made with mostly AI. The main reason for the update is the errors cause by the old things used in the last released version. I still feel that this is the easiest way to get info from MAL, even with the original version, since MAL has not changed much.
147
+
148
+ ---
149
+
150
+ ## License
151
+
152
+ BSD License — see original repository for full text.
@@ -0,0 +1,9 @@
1
+ __authors__ = ""
2
+ __copyright__ = "(c) 2014, pymal"
3
+ __license__ = "BSD License"
4
+ __contact__ = "Name Of Current Guardian of this file <email@address>"
5
+
6
+ __all__ = ["account", "anime", "manga", "seasons"]
7
+
8
+
9
+ __version__ = "1.0.0"
@@ -0,0 +1,270 @@
1
+ __authors__ = ""
2
+ __copyright__ = "(c) 2014, pymal"
3
+ __license__ = "BSD License"
4
+ __contact__ = "Name Of Current Guardian of this file <email@address>"
5
+
6
+ from urllib import request
7
+
8
+ import niquests
9
+ import singleton_factory
10
+
11
+ from pymal import global_functions
12
+ from pymal.consts import HOST_NAME
13
+ from pymal.decorators import load
14
+
15
+
16
+ __all__ = ["Account"]
17
+
18
+
19
+ class Account(metaclass=singleton_factory.SingletonFactory):
20
+ """
21
+ Object that keeps all the account data in MAL.
22
+ """
23
+
24
+ __AUTH_CHECKER_URL = request.urljoin(HOST_NAME, r"api/account/verify_credentials.xml")
25
+
26
+ __MY_LOGIN_URL = request.urljoin(HOST_NAME, "login.php")
27
+ __DATA_FORM = "user_name={0:s}&password={1:s}&cookie=1&sublogin=Login&submit=1"
28
+
29
+ def __init__(self, username: str, password: str | None = None):
30
+ """
31
+ :param username: The account username.
32
+ :type username: str
33
+ :param password: Required for quick connection, instead of calling later change_password.
34
+ :type password: str or None
35
+ """
36
+ from pymal.account_objects import account_animes, account_mangas
37
+
38
+ self.__username = username
39
+ self.__password = password
40
+ self.connect = global_functions.connect
41
+ self.__user_id = None
42
+ self.__auth_object = None
43
+
44
+ self._main_profile_url = request.urljoin(HOST_NAME, f"profile/{self.username:s}")
45
+
46
+ self.__animes = account_animes.AccountAnimes(self)
47
+ self.__mangas = account_mangas.AccountMangas(self)
48
+ self.__friends = None
49
+ self.__image_url = ""
50
+
51
+ self.__session = global_functions.generate_session()
52
+
53
+ self._is_loaded = False
54
+
55
+ if password is not None:
56
+ self.change_password(password)
57
+
58
+ @property
59
+ def username(self) -> str:
60
+ """
61
+
62
+ :returns: the user name.
63
+ :rtype: str
64
+ """
65
+ return self.__username
66
+
67
+ @property
68
+ def user_id(self) -> int:
69
+ """
70
+ :returns: the user id. If unknown loading it.
71
+ :rtype: int
72
+ """
73
+ if self.__user_id is None:
74
+ import bs4
75
+
76
+ ret = self.connect(self._main_profile_url)
77
+ html = bs4.BeautifulSoup(ret)
78
+ bla = html.find(name="input", attrs={"name": "profileMemId"})
79
+ self.__user_id = int(bla["value"])
80
+ return self.__user_id
81
+
82
+ @property
83
+ def mangas(self):
84
+ """
85
+ :returns: list of account's mangas.
86
+ :rtype: :class:`account_objects.account_mangas.AccountMangas`
87
+ """
88
+ return self.__mangas
89
+
90
+ @property
91
+ def animes(self):
92
+ """
93
+ :returns: list of account's animes.
94
+ :rtype: :class:`account_objects.account_animes.AccountAnimes`
95
+ """
96
+ return self.__animes
97
+
98
+ @property
99
+ def friends(self) -> set:
100
+ """
101
+ :returns: list of account's friends.
102
+ :rtype: :class:`account_objects.account_friends.AccountFriends`
103
+ """
104
+ from pymal.account_objects import account_friends
105
+
106
+ if self.__friends is None:
107
+ self.__friends = account_friends.AccountFriends(self)
108
+ return self.__friends
109
+
110
+ def search(self, search_line: str, is_anime: bool = True) -> map:
111
+ """
112
+ Searching like regular search but switching all the object in "my" lists to the "my" objects.
113
+
114
+ :param search_line: the search line.
115
+ :type search_line: str
116
+ :param is_anime: True is searching for anime, False for manga.
117
+ :type is_anime: bool
118
+ :returns: searched objects
119
+ :rtype: map
120
+ """
121
+ from pymal import searches
122
+
123
+ if is_anime:
124
+ results = searches.search_animes(search_line)
125
+ account_object_list = self.animes
126
+ else:
127
+ results = searches.search_mangas(search_line)
128
+ account_object_list = self.mangas
129
+
130
+ def get_object(result):
131
+ if result not in account_object_list:
132
+ return result
133
+ # if account_object_list was set:
134
+ # return account_object_list.intersection([result]).pop()
135
+ return list(filter(lambda x: x == result, account_object_list))[0]
136
+
137
+ return map(get_object, results)
138
+
139
+ def change_password(self, password: str) -> bool:
140
+ """
141
+ Checking if the new password is valid
142
+
143
+ :param password: password
144
+ :type password: str
145
+ :returns: True if password is right.
146
+ :rtype: bool
147
+ :exception exceptions.FailedToParseError: when failed
148
+ """
149
+ from xml.etree import ElementTree
150
+
151
+ from niquests.auth import HTTPBasicAuth
152
+
153
+ from pymal import exceptions
154
+
155
+ self.__auth_object = HTTPBasicAuth(self.username, password)
156
+ data = self.auth_connect(self.__AUTH_CHECKER_URL)
157
+ if data == "Invalid credentials":
158
+ self.__auth_object = None
159
+ self.__password = None
160
+ return False
161
+ xml_user = ElementTree.fromstring(data)
162
+
163
+ if xml_user.tag != "user":
164
+ raise exceptions.FailedToParseError(f"user == {xml_user.tag:s}")
165
+ xml_children = list(xml_user)
166
+ xml_username = xml_children[1]
167
+ if xml_username.tag != "username":
168
+ raise exceptions.FailedToParseError(f"username == {xml_username.tag:s}")
169
+ if self.username != xml_username.text.strip():
170
+ raise exceptions.FailedToParseError(
171
+ f"username = {xml_username.text.strip():s}"
172
+ )
173
+
174
+ xml_id = xml_children[0]
175
+ if xml_id.tag != "id":
176
+ raise exceptions.FailedToParseError(f"id == {xml_id.tag:s}")
177
+ if self.user_id != int(xml_id.text):
178
+ raise exceptions.FailedToParseError()
179
+
180
+ self.__password = password
181
+
182
+ data_form = self.__DATA_FORM.format(self.username, password).encode("utf-8")
183
+ headers = {
184
+ "content-type": "application/x-www-form-urlencoded",
185
+ "name": "loginForm",
186
+ }
187
+
188
+ self.auth_connect(self.__MY_LOGIN_URL, data=data_form, headers=headers)
189
+
190
+ return True
191
+
192
+ def auth_connect(
193
+ self, url: str, data: str | None = None, headers: dict | None = None
194
+ ) -> str:
195
+ """
196
+ :param url: The url to get.
197
+ :type url: str
198
+ :param data: The data to pass (will change the request to "POST")
199
+ :type data: str or None
200
+ :param headers: Headers for the request.
201
+ :type headers: dict or None
202
+ :returns: data after using the account authenticate to get the data.
203
+ :rtype: str
204
+ """
205
+ from pymal import exceptions
206
+
207
+ if not self.is_auth:
208
+ raise exceptions.UnauthenticatedAccountError(self.username)
209
+ return global_functions._connect(
210
+ url,
211
+ data=data,
212
+ headers=headers,
213
+ auth=self.__auth_object,
214
+ session=self.__session,
215
+ ).text.strip()
216
+
217
+ @property
218
+ @load
219
+ def image_url(self):
220
+ """
221
+ :return: path for the image of the avatar of the account.
222
+ :rtype: str
223
+ """
224
+ return self.__image_url
225
+
226
+ def get_image(self):
227
+ """
228
+ :return: image of the account's avatar
229
+ :rtype: :class:`PIL.Image.Image`
230
+ """
231
+ import io
232
+
233
+ from PIL import Image
234
+
235
+ sock = niquests.get(self.image_url)
236
+ data = io.BytesIO(sock.content)
237
+ return Image.open(data)
238
+
239
+ def reload(self):
240
+ """
241
+ reloading account image (all the other things are already lazy load!
242
+ """
243
+ div = global_functions.get_content_wrapper_div(
244
+ self._main_profile_url, self.connect
245
+ )
246
+ profile_leftcell = div.table.tbody.tr.find(
247
+ name="td", attrs={"class": "profile_leftcell"}, recursive=False
248
+ )
249
+ self.__image_url = profile_leftcell.div.img["src"]
250
+
251
+ @property
252
+ def is_auth(self) -> bool:
253
+ """
254
+ :returns: True if the password is right (and able to authenticate).
255
+ :rtype: bool
256
+ """
257
+ return self.__auth_object is not None
258
+
259
+ def __repr__(self):
260
+ return f"<Account username: {self.username:s}>"
261
+
262
+ def __hash__(self):
263
+ import hashlib
264
+
265
+ hash_md5 = hashlib.md5()
266
+ hash_md5.update(self.username.encode())
267
+ return int(hash_md5.hexdigest(), 16)
268
+
269
+ def __format__(self, format_spec):
270
+ return str(self).__format__(format_spec)
@@ -0,0 +1 @@
1
+ __author__ = "user"