niconico.py-ma 2.1.0.post1__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 (32) hide show
  1. niconico_py_ma-2.1.0.post1/LICENSE +21 -0
  2. niconico_py_ma-2.1.0.post1/PKG-INFO +83 -0
  3. niconico_py_ma-2.1.0.post1/README.md +59 -0
  4. niconico_py_ma-2.1.0.post1/niconico/__init__.py +6 -0
  5. niconico_py_ma-2.1.0.post1/niconico/__main__.py +126 -0
  6. niconico_py_ma-2.1.0.post1/niconico/base/__init__.py +0 -0
  7. niconico_py_ma-2.1.0.post1/niconico/base/client.py +22 -0
  8. niconico_py_ma-2.1.0.post1/niconico/channel/__init__.py +35 -0
  9. niconico_py_ma-2.1.0.post1/niconico/channel/search.py +55 -0
  10. niconico_py_ma-2.1.0.post1/niconico/decorators.py +30 -0
  11. niconico_py_ma-2.1.0.post1/niconico/exceptions.py +71 -0
  12. niconico_py_ma-2.1.0.post1/niconico/niconico.py +215 -0
  13. niconico_py_ma-2.1.0.post1/niconico/objects/__init__.py +0 -0
  14. niconico_py_ma-2.1.0.post1/niconico/objects/channel/__init__.py +98 -0
  15. niconico_py_ma-2.1.0.post1/niconico/objects/channel/search.py +63 -0
  16. niconico_py_ma-2.1.0.post1/niconico/objects/common.py +37 -0
  17. niconico_py_ma-2.1.0.post1/niconico/objects/nvapi.py +460 -0
  18. niconico_py_ma-2.1.0.post1/niconico/objects/user/__init__.py +210 -0
  19. niconico_py_ma-2.1.0.post1/niconico/objects/user/search.py +28 -0
  20. niconico_py_ma-2.1.0.post1/niconico/objects/video/__init__.py +174 -0
  21. niconico_py_ma-2.1.0.post1/niconico/objects/video/ranking.py +12 -0
  22. niconico_py_ma-2.1.0.post1/niconico/objects/video/search.py +150 -0
  23. niconico_py_ma-2.1.0.post1/niconico/objects/video/watch.py +679 -0
  24. niconico_py_ma-2.1.0.post1/niconico/py.typed +0 -0
  25. niconico_py_ma-2.1.0.post1/niconico/user/__init__.py +569 -0
  26. niconico_py_ma-2.1.0.post1/niconico/user/search.py +50 -0
  27. niconico_py_ma-2.1.0.post1/niconico/utils.py +25 -0
  28. niconico_py_ma-2.1.0.post1/niconico/video/__init__.py +191 -0
  29. niconico_py_ma-2.1.0.post1/niconico/video/ranking.py +120 -0
  30. niconico_py_ma-2.1.0.post1/niconico/video/search.py +402 -0
  31. niconico_py_ma-2.1.0.post1/niconico/video/watch.py +364 -0
  32. niconico_py_ma-2.1.0.post1/pyproject.toml +59 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Negima1072
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: niconico.py-ma
3
+ Version: 2.1.0.post1
4
+ Summary: API wrapper for NicoNico services (Music Assistant fork)
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: Shi-553
8
+ Author-email: 58556078+Shi-553@users.noreply.github.com
9
+ Requires-Python: >=3.8
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.8
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Dist: pydantic (>=2.8.2,<3.0.0)
20
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
21
+ Project-URL: Repository, https://github.com/Shi-553/niconico.py
22
+ Description-Content-Type: text/markdown
23
+
24
+ ![niconico](https://img.shields.io/badge/niconico-(%E5%B8%B0%E3%81%A3%E3%81%A6%E3%81%8D%E3%81%9F)-auto?logo=niconico&logoColor=%23e6e6e6&color=%23252525)
25
+
26
+ [![PyPI](https://img.shields.io/pypi/v/niconico.py-ma?logo=pypi)](https://pypi.org/project/niconico.py-ma/)
27
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/niconico.py-ma?logo=pypi)
28
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/niconico.py-ma?logo=pypi)
29
+ ![PyPI - License](https://img.shields.io/pypi/l/niconico.py-ma?logo=pypi)
30
+
31
+ [![Test](https://github.com/Shi-553/niconico.py/actions/workflows/release.yml/badge.svg)](https://github.com/Shi-553/niconico.py/actions/workflows/release.yml)
32
+ [![Test](https://github.com/Shi-553/niconico.py/actions/workflows/pypi.yml/badge.svg)](https://github.com/Shi-553/niconico.py/actions/workflows/pypi.yml)
33
+
34
+ # <img src="https://avatars.githubusercontent.com/u/113749892" height="30" /> niconico.py-ma
35
+
36
+ niconico.py-ma is a Python library for retrieving Niconico video content and information, and is compatible with the latest version of Niconico.
37
+ This is a Music Assistant fork with additional features and improvements.
38
+ It allows you to download videos, retrieve information, get comments, and more.
39
+
40
+ ## Requirement
41
+
42
+ To use the video download function, you need to install [FFmpeg](https://www.ffmpeg.org/) and set the path.
43
+
44
+ ## Installation
45
+
46
+ You can install it using pip:
47
+ ```bash
48
+ pip install niconico.py-ma
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ```python
54
+ from niconico import NicoNico
55
+ client = NicoNico()
56
+ ```
57
+
58
+ ### Examples
59
+
60
+ All examples can be found [here](https://github.com/Shi-553/niconico.py/tree/main/examples).
61
+
62
+ ## Original Project
63
+
64
+ This is a fork of [niconico.py](https://github.com/niconicolibs/niconico.py) by Negima1072, enhanced for Music Assistant integration.
65
+
66
+ ## Command
67
+
68
+ Console commands are available, see the following commands for usage:
69
+ ```bash
70
+ niconico -h
71
+ ```
72
+
73
+ ## Contributing
74
+
75
+ All contributions are welcome, but please:
76
+ - Use the `develop` branch.
77
+ - Follow PEP8.
78
+ - Be clear about the meaning and purpose of your issue or PR.
79
+
80
+ ## License
81
+
82
+ [MIT License](LICENSE)
83
+
@@ -0,0 +1,59 @@
1
+ ![niconico](https://img.shields.io/badge/niconico-(%E5%B8%B0%E3%81%A3%E3%81%A6%E3%81%8D%E3%81%9F)-auto?logo=niconico&logoColor=%23e6e6e6&color=%23252525)
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/niconico.py-ma?logo=pypi)](https://pypi.org/project/niconico.py-ma/)
4
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/niconico.py-ma?logo=pypi)
5
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/niconico.py-ma?logo=pypi)
6
+ ![PyPI - License](https://img.shields.io/pypi/l/niconico.py-ma?logo=pypi)
7
+
8
+ [![Test](https://github.com/Shi-553/niconico.py/actions/workflows/release.yml/badge.svg)](https://github.com/Shi-553/niconico.py/actions/workflows/release.yml)
9
+ [![Test](https://github.com/Shi-553/niconico.py/actions/workflows/pypi.yml/badge.svg)](https://github.com/Shi-553/niconico.py/actions/workflows/pypi.yml)
10
+
11
+ # <img src="https://avatars.githubusercontent.com/u/113749892" height="30" /> niconico.py-ma
12
+
13
+ niconico.py-ma is a Python library for retrieving Niconico video content and information, and is compatible with the latest version of Niconico.
14
+ This is a Music Assistant fork with additional features and improvements.
15
+ It allows you to download videos, retrieve information, get comments, and more.
16
+
17
+ ## Requirement
18
+
19
+ To use the video download function, you need to install [FFmpeg](https://www.ffmpeg.org/) and set the path.
20
+
21
+ ## Installation
22
+
23
+ You can install it using pip:
24
+ ```bash
25
+ pip install niconico.py-ma
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```python
31
+ from niconico import NicoNico
32
+ client = NicoNico()
33
+ ```
34
+
35
+ ### Examples
36
+
37
+ All examples can be found [here](https://github.com/Shi-553/niconico.py/tree/main/examples).
38
+
39
+ ## Original Project
40
+
41
+ This is a fork of [niconico.py](https://github.com/niconicolibs/niconico.py) by Negima1072, enhanced for Music Assistant integration.
42
+
43
+ ## Command
44
+
45
+ Console commands are available, see the following commands for usage:
46
+ ```bash
47
+ niconico -h
48
+ ```
49
+
50
+ ## Contributing
51
+
52
+ All contributions are welcome, but please:
53
+ - Use the `develop` branch.
54
+ - Follow PEP8.
55
+ - Be clear about the meaning and purpose of your issue or PR.
56
+
57
+ ## License
58
+
59
+ [MIT License](LICENSE)
@@ -0,0 +1,6 @@
1
+ """niconico package."""
2
+
3
+ from niconico.niconico import NicoNico
4
+
5
+ __all__ = ("NicoNico",)
6
+ __version__ = "2.1.0"
@@ -0,0 +1,126 @@
1
+ """Main module for niconico package."""
2
+
3
+ import argparse
4
+ import logging
5
+ from pathlib import Path
6
+
7
+ from niconico import NicoNico, __version__
8
+ from niconico.exceptions import LoginFailureError, WatchAPIError
9
+
10
+ logger = logging.getLogger("niconico.py")
11
+
12
+
13
+ def command_details(args: argparse.Namespace) -> None:
14
+ """Show video details."""
15
+ logger.debug("Starting command: details with args: %s", args)
16
+ client = NicoNico()
17
+ video = client.video.get_video(args.id)
18
+ if video is None:
19
+ logger.error("Video not found")
20
+ return
21
+ logger.debug("Video: %s", video)
22
+ if args.output is None:
23
+ logger.info(video.model_dump_json(indent=2, by_alias=True))
24
+ else:
25
+ with args.output.open("w") as f:
26
+ f.write(video.model_dump_json(by_alias=True))
27
+ logger.info("Saved to %s", args.output)
28
+
29
+
30
+ def command_quality(args: argparse.Namespace) -> None:
31
+ """Show video quality."""
32
+ logger.debug("Starting command: quality with args: %s", args)
33
+ client = NicoNico()
34
+ if args.session is not None:
35
+ try:
36
+ client.login_with_session(args.session)
37
+ logger.debug("Logged in with session")
38
+ except LoginFailureError:
39
+ logger.exception("Login failed")
40
+ logger.debug("is_premium: %s", client.premium)
41
+ try:
42
+ watch_data = client.video.watch.get_watch_data(args.id)
43
+ except WatchAPIError:
44
+ logger.exception("An error has occurred")
45
+ return
46
+ logger.debug("Watch data: %s", watch_data)
47
+ outputs = client.video.watch.get_outputs(watch_data)
48
+ logger.info("Label\tVideo, Audio")
49
+ for output_key, output in outputs.items():
50
+ logger.info("%s:\t%s", output_key, ", ".join(output))
51
+
52
+
53
+ def command_download(args: argparse.Namespace) -> None:
54
+ """Download video."""
55
+ logger.debug("Starting command: download with args: %s", args)
56
+ client = NicoNico()
57
+ if args.session is not None:
58
+ try:
59
+ client.login_with_session(args.session)
60
+ logger.debug("Logged in with session")
61
+ except LoginFailureError:
62
+ logger.exception("Login failed")
63
+ logger.debug("is_premium: %s", client.premium)
64
+ try:
65
+ watch_data = client.video.watch.get_watch_data(args.id)
66
+ except WatchAPIError:
67
+ logger.exception("An error has occurred")
68
+ return
69
+ logger.debug("Watch data: %s", watch_data)
70
+ outputs = client.video.watch.get_outputs(watch_data, audio_only=args.audio)
71
+ logger.debug("Outputs: %s", outputs)
72
+ if args.quality is not None:
73
+ if args.quality not in outputs:
74
+ logger.error("This quality is not supported")
75
+ return
76
+ quality = args.quality
77
+ logger.debug("Selected quality: %s", quality)
78
+ else:
79
+ quality = next(iter(outputs))
80
+ logger.debug("Selected quality: %s(best)", quality)
81
+ logger.debug("Downloading video...")
82
+ downloaded_path = client.video.watch.download_video(watch_data, quality, args.output, audio_only=args.audio)
83
+ logger.info("Downloaded to %s", downloaded_path)
84
+
85
+
86
+ def main() -> None:
87
+ """Main function for niconico package."""
88
+ parser = argparse.ArgumentParser(
89
+ description="download video from niconico",
90
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
91
+ )
92
+ parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
93
+ parser.add_argument("--debug", help="enable debug logging", action="store_true")
94
+
95
+ subparsers = parser.add_subparsers(title="commands")
96
+
97
+ parser_details = subparsers.add_parser("details", help="show video details")
98
+ parser_details.add_argument("id", help="video id", type=str)
99
+ parser_details.add_argument("-o", "--output", help="output file (default: stdout)", default=None, type=Path)
100
+ parser_details.set_defaults(func=command_details)
101
+
102
+ parser_quality = subparsers.add_parser("quality", help="show video quality")
103
+ parser_quality.add_argument("id", help="video id", type=str)
104
+ parser_quality.add_argument("-s", "--session", help="user_session cookie", default=None, type=str)
105
+ parser_quality.set_defaults(func=command_quality)
106
+
107
+ parser_download = subparsers.add_parser("download", help="download video")
108
+ parser_download.add_argument("id", help="video id", type=str)
109
+ parser_download.add_argument("-o", "--output", help="output file path", default=".", type=str)
110
+ parser_download.add_argument("-s", "--session", help="user_session cookie", default=None, type=str)
111
+ parser_download.add_argument("-q", "--quality", help="video quality (default: best)", default=None, type=str)
112
+ parser_download.add_argument("-a", "--audio", help="download audio only", action="store_true")
113
+ parser_download.set_defaults(func=command_download)
114
+
115
+ args = parser.parse_args()
116
+
117
+ logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO, format="%(message)s")
118
+
119
+ if hasattr(args, "func"):
120
+ args.func(args)
121
+ else:
122
+ parser.print_help()
123
+
124
+
125
+ if __name__ == "__main__":
126
+ main()
File without changes
@@ -0,0 +1,22 @@
1
+ """This module contains the base client class."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from niconico.niconico import NicoNico
9
+
10
+
11
+ class BaseClient:
12
+ """A class that represents a base client."""
13
+
14
+ niconico: NicoNico
15
+
16
+ def __init__(self, niconico: NicoNico) -> None:
17
+ """Initialize the base client."""
18
+ self.niconico = niconico
19
+
20
+ def log(self, type_: str, message: str) -> None:
21
+ """Log a message."""
22
+ return getattr(self.niconico.logger, type_)(message)
@@ -0,0 +1,35 @@
1
+ """This module provides a class that represents a channel client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import requests
8
+
9
+ from niconico.base.client import BaseClient
10
+ from niconico.channel.search import ChannelSearchClient
11
+ from niconico.objects.channel import ChannelData, ChAPIResponse
12
+
13
+ if TYPE_CHECKING:
14
+ from niconico.niconico import NicoNico
15
+
16
+
17
+ class ChannelClient(BaseClient):
18
+ """A client that represents a channel client."""
19
+
20
+ search: ChannelSearchClient
21
+
22
+ def __init__(self, niconico: NicoNico) -> None:
23
+ """Initialize the client."""
24
+ super().__init__(niconico)
25
+ self.search = ChannelSearchClient(niconico)
26
+
27
+ def get_channel(self, channel_id: str) -> ChannelData | None:
28
+ """Get a channel."""
29
+ channel_id = channel_id.replace("ch", "")
30
+ res = self.niconico.get(f"https://public-api.ch.nicovideo.jp/v2/open/channels/{channel_id}")
31
+ if res.status_code == requests.codes.ok:
32
+ res_cls = ChAPIResponse[ChannelData](**res.json())
33
+ if res_cls.data is not None:
34
+ return res_cls.data
35
+ return None
@@ -0,0 +1,55 @@
1
+ """Module for channel search client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Literal
6
+
7
+ import requests
8
+
9
+ from niconico.base.client import BaseClient
10
+ from niconico.objects.channel.search import ChannelSearchItem, ChSearchAPIResponse
11
+
12
+
13
+ class ChannelSearchClient(BaseClient):
14
+ """Client for channel search."""
15
+
16
+ def search_channels(
17
+ self,
18
+ query: str,
19
+ *,
20
+ search_type: Literal["keyword", "tag"] = "keyword",
21
+ limit: int = 20,
22
+ offset: int = 0,
23
+ order: Literal["desc", "asc"] = "desc",
24
+ sort: Literal["updateTime"] | None = None,
25
+ ) -> list[ChannelSearchItem]:
26
+ """Search channels by query.
27
+
28
+ Args:
29
+ query (str): Query to search.
30
+ search_type (Literal["keyword", "tag"], optional): Search type. Defaults to "keyword".
31
+ limit (int, optional): Limit. Defaults to 20.
32
+ offset (int, optional): Offset. Defaults to 0.
33
+ order (Literal["desc", "asc"], optional): Order. Defaults to "desc".
34
+ sort (Literal["updateTime"], optional): Sort. Default is created time.
35
+
36
+ Returns:
37
+ list[ChannelSearchItem]: Channel search item list.
38
+ """
39
+ query_dict = {
40
+ "query": query,
41
+ "searchType": search_type,
42
+ "limit": str(limit),
43
+ "offset": str(offset),
44
+ "order": order,
45
+ "responseGroup": "detail",
46
+ }
47
+ if sort is not None:
48
+ query_dict["sort"] = sort
49
+ query_str = "&".join(f"{key}={value}" for key, value in query_dict.items())
50
+ res = self.niconico.get(f"https://public-api.ch.nicovideo.jp/v1/open/search/channels?{query_str}")
51
+ if res.status_code == requests.codes.ok:
52
+ res_cls = ChSearchAPIResponse(**res.json())
53
+ if res_cls.data is not None:
54
+ return res_cls.data
55
+ return []
@@ -0,0 +1,30 @@
1
+ """Decorators for the niconico package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import wraps
6
+ from typing import TYPE_CHECKING, Any, Callable, TypeVar
7
+
8
+ from niconico.exceptions import LoginRequiredError, PremiumRequiredError
9
+
10
+ if TYPE_CHECKING:
11
+ from niconico.base.client import BaseClient
12
+
13
+ F = TypeVar("F", bound=Callable[..., Any])
14
+
15
+
16
+ def login_required(*, premium: bool = False) -> Callable[[F], F]:
17
+ """A decorator that requires a login to be performed."""
18
+
19
+ def decorator(func: F) -> F:
20
+ @wraps(func)
21
+ def wrapper(self: BaseClient, *args: Any, **kwargs: Any) -> F: # noqa: ANN401
22
+ if not self.niconico.logined:
23
+ raise LoginRequiredError(message="Login is required to use this function.")
24
+ if premium and not self.niconico.premium:
25
+ raise PremiumRequiredError(message="Premium account is required to use this function.")
26
+ return func(self, *args, **kwargs)
27
+
28
+ return wrapper # type: ignore # noqa: PGH003
29
+
30
+ return decorator
@@ -0,0 +1,71 @@
1
+ """This module contains exceptions for the niconico package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from niconico.objects.video.watch import WatchResponseError
9
+
10
+
11
+ class LoginFailureError(Exception):
12
+ """An exception raised when login fails."""
13
+
14
+ def __init__(self, message: str) -> None:
15
+ """Initialize the exception."""
16
+ self.message = message
17
+ super().__init__(message)
18
+
19
+
20
+ class DownloadError(Exception):
21
+ """An exception raised when download fails."""
22
+
23
+ def __init__(self, message: str) -> None:
24
+ """Initialize the exception."""
25
+ self.message = message
26
+ super().__init__(message)
27
+
28
+
29
+ class NicoAPIError(Exception):
30
+ """An exception raised when an error occurs in the Nico API."""
31
+
32
+ def __init__(self, message: str) -> None:
33
+ """Initialize the exception."""
34
+ self.message = message
35
+ super().__init__(message)
36
+
37
+
38
+ class LoginRequiredError(Exception):
39
+ """An exception raised when login is required."""
40
+
41
+ def __init__(self, message: str) -> None:
42
+ """Initialize the exception."""
43
+ self.message = message
44
+ super().__init__(message)
45
+
46
+
47
+ class PremiumRequiredError(Exception):
48
+ """An exception raised when a premium account is required."""
49
+
50
+ def __init__(self, message: str) -> None:
51
+ """Initialize the exception."""
52
+ self.message = message
53
+ super().__init__(message)
54
+
55
+
56
+ class WatchAPIError(Exception):
57
+ """An exception raised when an error occurs in the watch API."""
58
+
59
+ def __init__(self, response: WatchResponseError) -> None:
60
+ """Initialize the exception."""
61
+ self.response = response
62
+ super().__init__(response.error_code)
63
+
64
+
65
+ class CommentAPIError(Exception):
66
+ """An exception raised when an error occurs in the comment API."""
67
+
68
+ def __init__(self, message: str | None) -> None:
69
+ """Initialize the exception."""
70
+ self.message = message
71
+ super().__init__(message)