├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── .nojekyll
├── coverpage.md
├── exceptions.md
├── index.html
├── introduction.md
├── methods.md
├── models.md
├── quick-start.md
└── sidebar.md
├── radiojavanapi
├── __init__.py
├── constants.py
├── exceptions.py
├── extractors.py
├── helper.py
├── mixins
│ ├── __init__.py
│ ├── account.py
│ ├── album.py
│ ├── artist.py
│ ├── auth.py
│ ├── browse.py
│ ├── playlist.py
│ ├── podcast.py
│ ├── private.py
│ ├── search.py
│ ├── song.py
│ ├── story.py
│ ├── user.py
│ └── video.py
└── models.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
131 | tests/
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/)
6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7 |
8 | ## 0.5.0
9 | *2023-03-19*
10 |
11 | - Fixed media-id extractor from url
12 | - Removed get_popular_artists() method
13 |
14 | ## 0.4.0
15 | *2023-01-14*
16 |
17 | - Added account_notifications() method
18 | - Added account_notifications_update() method
19 | - Updated headers & domain based on latest version of app
20 |
21 | ## 0.3.0
22 | *2022-02-04*
23 |
24 | - Added signup() method
25 | - Added save_session() method
26 | - Added load_session() method
27 | - Updated headers & domain based on latest version of app
28 |
29 | ## 0.2.2
30 | *2021-08-26*
31 |
32 | - Added User model
33 | - Added get_user_by_url() method
34 | - Added get_user_by_username() method
35 | - Added get_user_followers() method
36 | - Added get_user_following() method
37 | - Added follow_user() method
38 | - Added unfollow_user() method
39 | - Added my_followers() method
40 | - Added my_following() method
41 | - Added bio argument to account_edit() method
42 | - Changed name of Profile model to ShortUser
43 | - Changed name of attribute from profiles to users in SearchResults model
44 | - Changed name of my_following() [old method] to following_artists() method
45 | - Fixed unclosed file in upload_photo() by using context manager
46 |
47 | ## 0.2.0
48 | *2021-08-24*
49 |
50 | - Added ShortData and MyPlaylists models
51 | - Added logout() method for clean logout
52 | - Added new exceptions
53 | - Removed ComingSoon model and get_coming_soon() method
54 | - Changed name of some exceptions
55 | - Changed headers to latest app version
56 | - Changed SearchResults structure by adding ShortData
57 | - Changed models to latest RadioJavan API.
58 | - Changed account_edit() arguments from dict to separate str
59 | - Changed my_playlists() return object from dict to MyPlaylists
60 | - Changed arguments of actions methods (like, follow, ...) from Object to media id or artist name
61 | - Changed arguments of create, rename and delete playlists methods from Object to ids
62 | - Changed arguments of add or remove a song/video methods from Object to ids
63 | - Fixed bug in url_to_id() [ When url contains "(" or ")" ]
64 | - Fixed bug in change_password() [ Now updates cookies ]
65 | - Fixed bug in change_photo()
66 | - Fixed bug in remove_photo()
67 | - Fixed bug in get_artist_by_name() by encoding url params via quote_plus
68 |
69 | ## 0.1.1
70 | *2021-02-21*
71 |
72 | - Added RadioJavan tv and radio.
73 |
74 | ## 0.1.0
75 | *2021-02-08*
76 |
77 | - Initial release
78 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 xHossein
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://badge.fury.io/py/radiojavanapi)
2 | [](https://github.com/xHossein/radiojavanapi/blob/master/LICENSE)
3 | [](https://pepy.tech/project/radiojavanapi)
4 |
5 | # radiojavanapi
6 | **radiojavanapi** is a Python library for accessing RadioJavan's features. With this library you can create Telegram Bots with ease and simplicity.
7 |
8 | Support Python >= 3.7
9 |
10 | RadioJavan API valid for 19 March 2023 (last reverse-engineering check)
11 |
12 | ## Features
13 | * Get full info of a Song, Video, Podcast, Story, Playlist, Artist, Album, User and your Account
14 | * Login by email and password
15 | * Sign up to RadioJavan
16 | * Like and Unlike a Song, Video, Podcast and Story
17 | * Follow and Unfollow a Artist, User or MusicPlaylist
18 | * Get followers and following of a user
19 | * Create, Rename and Delete a playlist
20 | * Add song or video to playlist or Remove from it
21 | * Edit and Deactive account
22 | * Upload and Remove profile photo
23 | * Search and get trending, popular and ... medias\
24 | and much more else
25 |
26 | ## Installation
27 | **From PyPI**
28 | ```
29 | pip install radiojavanapi
30 | ```
31 |
32 | **From Github**
33 | ```
34 | pip install git+https://github.com/xHossein/radiojavanapi@master
35 | ```
36 |
37 | ## Basic Usage
38 |
39 | ```python
40 | from radiojavanapi import Client
41 |
42 | # Create a Client instance and get a song info.
43 | client = Client()
44 | song = client.get_song_by_url(
45 | 'https://www.radiojavan.com/mp3s/mp3/Sijal-Baz-Mirim-Baham-(Ft-Sami-Low)')
46 |
47 | print(f"""
48 | Name: {song.name}
49 | Artist: {song.artist}
50 | HQ-Link: {song.hq_link}
51 | """)
52 |
53 | ```
54 |
55 | Show Output
56 |
57 | ```
58 | Name: Baz Mirim Baham (Ft Sami Low)
59 | Artist: Sijal
60 | HQ-Link: https://host2.mediacon-rj.app/media/mp3/aac-256/99926-cf9dd3814907dbb.m4a
61 | ```
62 |
63 |
64 | ## Documentation
65 | You can find the documentation [here](https://xhossein.github.io/radiojavanapi/).
66 |
67 | ## Support
68 |
69 | - Create a [GitHub issue](https://github.com/xHossein/radiojavanapi/issues) for bug reports, feature requests, or questions
70 | - Add a ⭐️ [star on GitHub](https://github.com/xHossein/radiojavanapi) to support the project!
71 |
72 |
73 | ## Contributing
74 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
75 |
76 | ## Changelog
77 | You can find this repository's changelog [here](https://github.com/xHossein/radiojavanapi/blob/master/CHANGELOG.md).
78 |
79 | ## License
80 | This project is licensed under the [MIT license](https://choosealicense.com/licenses/mit/).
81 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xHossein/radiojavanapi/29897da1dab3caf5735d663968310ef29a02adab/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/coverpage.md:
--------------------------------------------------------------------------------
1 | # radiojavanapi 0.5.0
2 |
3 | > Fast and effective [RadioJavan](https://radiojavan.com/) API wrapper in python
4 |
5 | - Based on android & desktop applications
6 |
7 | [Get Started](introduction)
8 | [GitHub](https://github.com/xHossein/radiojavanapi/)
--------------------------------------------------------------------------------
/docs/exceptions.md:
--------------------------------------------------------------------------------
1 | # Exceptions
2 |
3 | ## Common Exceptions
4 | Exception | Base | Description
5 | ----------|------|------------
6 | ClientError | Exception | Base Exception for RadioJavan calls
7 | ClientJSONDecodeError | ClientError | JSON Exception
8 | ClientConnectionError | ClientError | Raised due to network connectivity-related issues
9 | ClientLoginRequired | ClientError | Raised when RadioJavan required Login
10 |
11 |
12 | ## Private Exceptions
13 | Exception | Base | Description
14 | ----------|------|------------
15 | PrivateError | ClientError | Base Exception for Private calls
16 | BadCredentials | PrivateError | Raised when email or password is wrong (login)
17 | InvalidName | PrivateError | Raised when firstname or lastname contains digits (edit account)
18 | LongString | PrivateError | Raised when string is too long (edit account)
19 | EmailExists | PrivateError | Raised when email used by another account (edit account)
20 | UsernameExists | PrivateError | Raised when username used by another account (edit account)
21 | DuplicatePassword | PrivateError | Raised when a duplicate password is provided (edit account)
22 | DuplicateName | PrivateError | Raised when a duplicate name is provided for playlist (create or rename playlist)
23 | InvalidMediaId | PrivateError | Raised when an invalid media id is provided (like, follow, ...)
24 | InvalidEmail | PrivateError | Raised when an invalid email is provided
25 | UnknownError | PrivateError | Raised when get unknown message (new message from radiojavan)
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | RadioJavan API Wrapper Document
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | [](https://badge.fury.io/py/radiojavanapi)
4 | [](https://github.com/xHossein/radiojavanapi/blob/master/LICENSE)
5 | [](https://pepy.tech/project/radiojavanapi)
6 | Star
7 |
8 | **radiojavanapi** is a Python library for accessing RadioJavan's features. With this library you can create Telegram Bots with ease and simplicity.
9 |
10 | ## Features
11 |
12 | * Get full info of a Song, Video, Podcast, Story, Playlist, Artist, Album, User and your Account
13 | * Login by email and password
14 | * Sign up to RadioJavan
15 | * Like and Unlike a Song, Video, Podcast and Story
16 | * Follow and Unfollow a Artist, User or MusicPlaylist
17 | * Get followers and following of a user
18 | * Create, Rename and Delete a playlist
19 | * Add song or video to playlist or Remove from it
20 | * Edit and Deactive account
21 | * Upload and Remove profile photo
22 | * Search and get trending, popular and ... medias\
23 | and much more else
24 |
25 | ## Support
26 |
27 | - Create a [GitHub issue](https://github.com/xHossein/radiojavanapi/issues) for bug reports, feature requests, or questions
28 | - Add a ⭐️ [star on GitHub](https://github.com/xHossein/radiojavanapi) to support the project!
29 |
30 | ## License
31 |
32 | This project is licensed under the [MIT license](https://choosealicense.com/licenses/mit/).
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/docs/methods.md:
--------------------------------------------------------------------------------
1 | # Methods
2 |
3 | ## Authentication
4 | For [Actions](methods?id=actions) and [Account](methods?id=account) methods, you must have an account.
5 |
6 | ### Login
7 | Arguments:
8 |
9 | | Name | Type | Description
10 | -------|------|------------
11 | `email` | `str` | Your account email
12 | `password` | `str` | Your account password
13 |
14 | Example:
15 | ```python
16 | from radiojavanapi import Client
17 | from radiojavanapi.exceptions import BadCredentials
18 |
19 | client = Client()
20 | try:
21 | client.login("YOUR EMAIL", "YOUR PASSWORD")
22 | except BadCredentials:
23 | # raise due to wrong email or password
24 | # do something
25 | pass
26 |
27 | song = client.get_song_by_url(
28 | 'https://www.radiojavan.com/mp3s/mp3/Sijal-Baz-Mirim-Baham-(Ft-Sami-Low)')
29 |
30 | print(client.like_song(song.id))
31 | print(client.follow_artist(song.artist))
32 | ```
33 |
34 | Show Output
35 |
36 | ```
37 | True
38 | True
39 | ```
40 |
41 |
42 | ### Sign Up
43 |
44 | Arguments:
45 |
46 | | Name | Type | Description
47 | -------|------|------------
48 | `firstname` | `str` | Your account firstname
49 | `lastname` | `str` | Your account lastname
50 | `username` | `str` | Your account username
51 | `email` | `str` | Your account email
52 | `password` | `str` | Your account password
53 | `auto_login` | `bool` | Login to account after sign up
54 |
55 | Example:
56 | ```python
57 | from radiojavanapi import Client
58 |
59 | client = Client()
60 | client.signup("YOUR FIRSTNAME", "YOUR LASTNAME", "YOUR USERNAME", "YOUR EMAIL", "YOUR PASSWORD", True)
61 | ```
62 |
63 | ### Save & Load session
64 | You can save your session like this:
65 | ```python
66 | clientObj.save_session('./myaccount')
67 | ```
68 | And you can load your session as well to avoid many logins:
69 | ```python
70 | clientObj.load_session('./myaccount')
71 | ```
72 |
73 | ## Account
74 | All account methods require authentication.
75 |
76 | ?> **Radiojavanapi Docs:** Check [here](methods?id=authentication) for login or sign up.
77 |
78 | ### Activity
79 | - Get list of:
80 | - Your followers & following
81 | - Song & video you've liked befor
82 | - Artist you're following now.
83 |
84 | Method | Return | Description | Login Required
85 | -------|------------|-------------|--------------
86 | liked_songs() | `list[`[Song](models?id=song)`]` | Returns list of songs you've liked | YES
87 | liked_videos() | `list[`[Video](models?id=video)`]` | Returns list of videos you've liked | YES
88 | following_artists() | `list[str]` | Returns list of artist names which you've followed | YES
89 | my_followers() | `list[`[ShortUser](models?id=shortuser)`]` | Returns list of your followers | YES
90 | my_following() | `list[`[ShortUser](models?id=shortuser)`]` | Returns list of your following| YES
91 | my_playlists() | [MyPlaylists](models?id=myplaylists) | Returns your video & music playlist's | YES
92 |
93 | ### Update
94 | Update your account.
95 |
96 | Method | Arguments | Return | Description | Login Required
97 | -------|----------|--------|-------------|--------------
98 | account_edit() | - `(Optional) firstname: str`
- `(Optional) lastname: str`
- `(Optional) username: str`
- `(Optional) email: str`
- `(Optional) bio: str`
| [Account](models?id=account) | Change profile data | YES
99 | account_notifications_update() | - `(Optional) new_music: bool`
- `(Optional) followed_artists: bool`
| `bool` | Update your notifications settings | YES
100 | change_password() | `password: str`
`(Your new passowrd)` | `bool` | Change your account password | YES
101 | upload_photo() | `photo_path: str` | `bool` | Upload your profile photo (only jpg/png) | YES
102 | remove_photo() | | `bool` | Remove your profile photo | YES
103 |
104 | !> To remove bio, pass an empty string.
105 |
106 | ### Others
107 | Method | Return | Description | Login Required
108 | -------|--------|-------------|--------------
109 | account_info() | [Account](models?id=account) | Returns private info of your account | YES
110 | account_notifications() | [NotificationsStatus](models?id=notificationsstatus) | Returns status of current notifications settings | YES
111 | deactive_account() | `bool` | Deactivate your account and logout | YES
112 |
113 |
114 | ## Actions
115 | All action methods require authentication.
116 |
117 | ?> **Radiojavanapi Docs:** Check [here](methods?id=authentication) for login or sign up.
118 |
119 | ### Like & Unlike
120 |
121 | Method | Arguments | Return | Description | Login Required
122 | -------|----------|--------|-------------|--------------
123 | like_song() | `song_id: Union[int, str]` | `bool` | Like a song | YES
124 | unlike_song() | `song_id: Union[int, str]` | `bool` | Unlike a song | YES
125 | like_video() | `video_id: Union[int, str]` | `bool` | Like a video | YES
126 | unlike_video() | `video_id: Union[int, str]` | `bool` | Unlike a video | YES
127 | like_story() | `story_id: Union[int, str]` | `bool` | Like a story | YES
128 | unlike_story() | `story_id: Union[int, str]` | `bool` | Unlike a story | YES
129 | like_podcast() | `podcast_id: Union[int, str]` | `bool` | Like a podcast | YES
130 | unlike_podcast() | `podcast_id: Union[int, str]` | `bool` | Unlike a podcast | YES
131 |
132 | !> like methods return false if media had been liked already and unlike methods return false if media hadn't been liked before.
133 |
134 | ### Follow & Unfollow
135 |
136 | Method | Arguments | Return | Description | Login Required
137 | -------|----------|--------|-------------|--------------
138 | follow_artist() | `name: str` | `bool` | Follow a artist | YES
139 | unfollow_artist() | `name: str` | `bool` | Unfollow a artist | YES
140 | follow_user() | `username: str` | `bool` | Follow a user | YES
141 | unfollow_user() | `username: str` | `bool` | Unfollow a user | YES
142 | follow_music_playlist() | `id: str` | `bool` | Follow a music (song or mp3) playlist | YES
143 | unfollow_music_playlist() | `id: str` | `bool` | Unfollow a music (song or mp3) playlist | YES
144 |
145 | !> Pass exact artist name (on RJ api) as name
146 |
147 | ### Work with playlists
148 | - Create, rename or delete playlist
149 | - Add song or video to playlist
150 | - Remove song or video from playlist
151 |
152 | Method | Arguments | Return | Description | Login Required
153 | -------|----------|--------|-------------|--------------
154 | create_music_playlist() | - `name: str`
- `song_id: Union[int, str]`
| `str` | Create a music playlist and returns playlist id | YES
155 | create_video_playlist() | - `name: str`
- `video_id: Union[int, str]`
| `str` | Create a video playlist and returns playlist id | YES
156 | rename_music_playlist() | | `bool` | Rename your music playlist | YES
157 | rename_video_playlist() | | `bool` | Rename your video playlist | YES
158 | delete_music_playlist() | `id: str` | `bool` | Delete your music playlist | YES
159 | delete_video_playlist() | `id: str` | `bool` | Delete your video playlist | YES
160 | add_to_music_playlist() | - `id: str`
- `song_id: Union[int, str]`
| `bool` | Add a song to your music playlist | YES
161 | add_to_video_playlist() | - `id: str`
- `video_id: Union[int, str]`
| `bool` | Add a video to your video playlist | YES
162 | remove_from_music_playlist() | - `id: str`
- `song_id: Union[int, str]`
| `bool` | Remove a song from your music playlist | YES
163 | remove_from_video_playlist() | - `id: str`
- `video_id: Union[int, str]`
| `bool` | Remove a video from your video playlist | YES
164 |
165 | !> In RadioJavan you can't create empty playlist , so you need a song/video id for creating playlist.
166 |
167 | !> **Add methods**: returns false if song/video had been added already\
168 | **Remove methods**: returns false if song/video hadn't been added before
169 |
170 | ## Get Info
171 | Get medias & users info as their [models](models).
172 |
173 | > All methods work with out authentication too.
174 |
175 | ### Song
176 | Method | Arguments | Return | Description | Login Required
177 | -------|----------|--------|-------------|--------------
178 | get_song_by_url() | `url: HttpUrl` | [Song](models?id=song) | Returns song info by site url (e.g. `play.radiojavan.com/song/...`) | NO
179 | get_song_by_id() | `id: Union[int, str]` | [Song](models?id=song) | Returns song info by id | NO
180 |
181 |
182 | ### Video
183 | Method | Arguments | Return | Description | Login Required
184 | -------|----------|--------|-------------|--------------
185 | get_video_by_url() | `url: HttpUrl` | [Video](models?id=video) | Returns video info by site url (e.g. `play.radiojavan.com/video/...`) | NO
186 | get_video_by_id() | `id: Union[int, str]` | [Video](models?id=video) | Returns video info by id | NO
187 |
188 | ### Story
189 | Method | Arguments | Return | Description | Login Required
190 | -------|----------|--------|-------------|--------------
191 | get_story_by_url() | `url: HttpUrl` | [Story](models?id=story) | Returns story info by site url (e.g. `play.radiojavan.com/story/...`) | NO
192 | get_story_by_hash_id() | `hash_id: str` | [Story](models?id=story) | Returns story by hash id | NO
193 |
194 | ### Podcast
195 | Method | Arguments | Return | Description | Login Required
196 | -------|----------|--------|-------------|--------------
197 | get_podcast_by_url() | `url: HttpUrl` | [Podcast](models?id=podcast) | Returns podcast info by site url (e.g. `play.radiojavan.com/podcast/...`) | NO
198 | get_podcast_by_id() | `id: Union[int, str]` | [Podcast](models?id=podcast) | Returns podcast info by id | NO
199 |
200 | ### Album
201 | Method | Arguments | Return | Description | Login Required
202 | -------|----------|--------|-------------|--------------
203 | get_album_by_url() | `url: HttpUrl` | [Album](models?id=album) | Returns album info by site url (e.g. `play.radiojavan.com/album/...`) | NO
204 | get_album_by_id() | `id: Union[int, str]` | [Album](models?id=album) | Returns album info by id | NO
205 |
206 | !> This id belong to one of album-tracks, usually first track.
207 |
208 | ### Artist
209 | Method | Arguments | Return | Description | Login Required
210 | -------|----------|--------|-------------|--------------
211 | get_artist_by_url() | `url: HttpUrl` | [Artist](models?id=artist) | Returns artist info by site url (e.g. `play.radiojavan.com/artist/...`) | NO
212 | get_artist_by_name() | `name: str` | [Artist](models?id=artist) | Returns artist info by id | NO
213 |
214 | ### Playlist
215 | Method | Arguments | Return | Description | Login Required
216 | -------|----------|--------|-------------|--------------
217 | get_music_playlist_by_url() | `url: HttpUrl` | [MusicPlaylist](models?id=musicplaylist) | Returns music playlist info by site url (e.g. `play.radiojavan.com/playlist/mp3/...`) | NO
218 | get_music_playlist_by_id() | `id: str` | [MusicPlaylist](models?id=musicplaylist) | Returns music playlist info by id | NO
219 | get_video_playlist_by_url() | `url: HttpUrl` | [VideoPlaylist](models?id=videoplaylist) | Returns video playlist info by site url (e.g. `play.radiojavan.com/playlist/video/...`) | NO
220 | get_video_playlist_by_id() | `id: str` | [VideoPlaylist](models?id=videoplaylist) | Returns video playlist info by id | NO
221 |
222 | ### User
223 | Method | Arguments | Return | Description | Login Required
224 | -------|----------|--------|-------------|--------------
225 | get_user_by_url() | `url: HttpUrl` | [User](models?id=user) | Returns user info by site url (e.g. `play.radiojavan.com/u/...`) | NO
226 | get_user_by_username() | `username: str` | [User](models?id=user) | Returns user info by username | NO
227 | get_user_followers() | `username: str` | `list[`[ShortUser](models?id=shortuser)`]` | Returns list of user followers | NO
228 | get_user_following() | `username: str` | `list[`[ShortUser](models?id=shortuser)`]` | Returns list of user following | NO
229 |
230 |
231 | ## Search
232 | Search on RadioJavan and get results as [SearchResults](models?id=searchresults).
233 |
234 | > This method work with out authentication too.
235 |
236 | Method | Arguments | Return | Description | Login Required
237 | -------|----------|--------|-------------|--------------
238 | search() | `query: str` | [SearchResults](models?id=searchresults) | Returns search results object | NO
239 |
240 | ## Browse
241 | Everything you see in `Browse` tab in desktop application. like `trending`, `popular`, `featured`, `latest` and `...`.
242 |
243 | > All methods work with out authentication too.
244 |
245 | Method | Return | Description | Login Required
246 | -------|--------|-------------|--------------
247 | get_latest_stories() | `list[`[Story](models?id=story)`]` | Returns list of latest stories | NO
248 | get_trending_songs() | `list[`[Song](models?id=song)`]` | Returns list of trending songs | NO
249 | get_popular_songs() | `list[`[Song](models?id=song)`]` | Returns list of popular songs | NO
250 | get_featured_songs() | `list[`[Song](models?id=song)`]` | Returns list of featured songs | NO
251 | get_latest_albums() | `list[`[Album](models?id=album)`]` | Returns list of latest albums | NO
252 | get_trending_videos() | `list[`[Video](models?id=video)`]` | Returns list of trending videos | NO
253 | get_popular_videos() | `list[`[Video](models?id=video)`]` | Returns list of popular videos | NO
254 | get_featured_videos() | `list[`[Video](models?id=video)`]` | Returns list of featured videos | NO
255 | get_latest_videos() | `list[`[Video](models?id=video)`]` | Returns list of latest videos | NO
256 | get_popular_podcasts() | `list[`[Podcast](models?id=podcast)`]` | Returns list of popular podcasts | NO
257 | get_featured_podcasts() | `list[`[Podcast](models?id=podcast)`]` | Returns list of featured podcasts | NO
258 | get_talk_podcasts() | `list[`[Podcast](models?id=podcast)`]` | Returns list of talk podcasts | NO
259 | get_shows_podcasts() | `list[`[Podcast](models?id=podcast)`]` | Returns list of shows podcasts | NO
260 |
261 |
262 | ## Others
263 | Get stream links and ... .
264 |
265 | > All methods work with out authentication too.
266 |
267 | Method | Return | Description | Login Required
268 | -------|-----------|-------------|--------------
269 | get_trending_searches() | `list[str]` | Returns list of trending searches | NO
270 | get_tv_stream() | `str` | Returns RJ tv stream link | NO
271 | get_radio_stream() | `dict` | Returns RJ radio stream links and short data of current and next songs | NO
--------------------------------------------------------------------------------
/docs/models.md:
--------------------------------------------------------------------------------
1 | # Models
2 |
3 | ## Account
4 | This models your account on RadioJavan.
5 |
6 |
7 | ### Attributes
8 | > Read-only values on a Account object
9 |
10 |
11 |
12 | Example:
13 |
14 | ```python
15 | accountObj.email
16 | ```
17 |
18 | Available attributes:
19 |
20 | | Name | Type | Description
21 | -------|------|------------
22 | `name` | `str` | Your account name
23 | `firstname` | `str` | Your account firstname
24 | `lastname` | `str` | Your account lastname
25 | `display_name` | `str` | The name which shows to other users
26 | `username` | `str` | Your account username
27 | `email` | `str` | Your account email
28 | `bio` | `Optional[str]` | Your account bio
29 | `share_link` | `HttpUrl` | Your profile url
30 | `has_subscription` | `bool` | Status of subscription
31 | `has_custom_photo` | `bool` | Its true when you have profile photo
32 | `is_verified` | `bool` | Status of verification
33 | `default_photo` | `HttpUrl` | Default photo url
34 | `default_thumbnail` | `HttpUrl` | Default thumbnail url
35 | `photo` | `HttpUrl` | Current photo url
36 | `thumbnail` | `HttpUrl` | Thumbnail url
37 | `followers_count` | `int` | Followers count
38 | `following_count` | `int` | Following count
39 | `playlists_count` | `int` | Number of playlists you have in your library
40 | `songs_count` | `int` | Number of songs you have in your library
41 | `artists_count` | `int` | Number of artists you've followed
42 | `artists_name` | `list[str]` | Name of artists you've followed
43 | `stories` | `list[`[Story](#story)`]` | Stories you've uploaded on RJ
44 |
45 | ## Song
46 | This models a song (mp3) on RadioJavan.
47 |
48 |
49 | ### Attributes
50 | > Read-only values on a Song object
51 |
52 |
53 |
54 | Example:
55 |
56 | ```python
57 | songObj.dislikes
58 | ```
59 |
60 | Available attributes:
61 |
62 | | Name | Type | Description
63 | -------|------|------------
64 | `id` | `int` | Song id
65 | `name` | `str` | Song name
66 | `artist` | `str` | Song artist name
67 | `artist_tags` | `list[str]` | Song artist tags
68 | `plays` | `int` | Plays count
69 | `downloads` | `int` | Downloads count
70 | `created_at` | `str` | Date and time the song was posted
71 | `permlink` | `str` | Song permlink (e.g. `Donya-Boro-Bargard`)
72 | `photo` | `HttpUrl` | Photo url
73 | `photo_player` | `Optional[HttpUrl]` | Player photo url
74 | `share_link` | `HttpUrl` | Song url
75 | `title` | `str` | Title
76 | `credits` | `Optional[str]` | Credits
77 | `credit_tags` | `list[str]` | Credit tags
78 | `likes` | `int` | Likes count
79 | `dislikes` | `int` | DisLikes count
80 | `link` | `HttpUrl` | Download link
81 | `hq_link` | `HttpUrl` | High quality download link
82 | `lq_link` | `HttpUrl` | Low quality download link
83 | `hls_link` | `Optional[HttpUrl]` | Stream link
84 | `hq_hls` | `Optional[HttpUrl]` | High quality stream link
85 | `lq_hls` | `Optional[HttpUrl]` | Low quality stream link
86 | `album` | `Optional[str]` | Album name
87 | `date` | `Optional[str]` | IDK! RJ api sent everything except date!
88 | `duration` | `float` | Song duration
89 | `thumbnail` | `HttpUrl` | Thumbnail url
90 | `lyric` | `Optional[str]` | Song lyric
91 | `related_songs` | `list[`[ShortData](#shortdata)`]` | Related songs
92 | `stories` | `list[`[Story](#story)`]` | Stories with that song
93 |
94 | ## Video
95 | This models a video on RadioJavan.
96 |
97 |
98 | ### Attributes
99 | > Read-only values on a Video object
100 |
101 |
102 |
103 | Example:
104 |
105 | ```python
106 | videoObj.dislikes
107 | ```
108 |
109 | Available attributes:
110 |
111 | | Name | Type | Description
112 | -------|------|------------
113 | `id` | `int` | Video id
114 | `name` | `str` | Video name
115 | `artist` | `str` | Video artist name
116 | `artist_tags` | `list[str]` | Video artist tags
117 | `views` | `int` | Views count
118 | `created_at` | `str` | Date and time the video was posted
119 | `permlink` | `str` | Video permlink
120 | `photo` | `HttpUrl` | Photo url
121 | `photo_player` | `Optional[HttpUrl]` | Player photo url
122 | `share_link` | `HttpUrl` | Video url
123 | `title` | `str` | Title
124 | `credit_tags` | `list[str]` | Credit tags
125 | `likes` | `int` | Likes count
126 | `dislikes` | `int` | DisLikes count
127 | `link` | `HttpUrl` | Download link
128 | `hq_link` | `HttpUrl` | High quality download link
129 | `lq_link` | `HttpUrl` | Low quality download link
130 | `hq_hls` | `Optional[HttpUrl]` | High quality stream link
131 | `lq_hls` | `Optional[HttpUrl]` | Low quality stream link
132 | `date` | `Optional[str]` | IDK! RJ api sent everything except date!
133 | `related_videos` | `list[`[ShortData](#shortdata)`]` | Related videos
134 |
135 | ## Album
136 | This models an album on RadioJavan.
137 |
138 |
139 | ### Attributes
140 | > Read-only values on an Album object
141 |
142 |
143 |
144 | Example:
145 |
146 | ```python
147 | albumObj.tracks
148 | ```
149 |
150 | Available attributes:
151 |
152 | | Name | Type | Description
153 | -------|------|------------
154 | `id` | `str` | Album id
155 | `name` | `str` | Album name
156 | `artist` | `str` | Album artist
157 | `created_at` | `str` | Date and time the Album was posted
158 | `date` | `str` | IDK! RJ api sent everything except date!
159 | `tracks` | `list[`[Song](#song)`]` | All tracks
160 | `share_link` | `HttpUrl` | Album url
161 |
162 | ## Podcast
163 | This models a podcast on RadioJavan.
164 |
165 |
166 | ### Attributes
167 | > Read-only values on a Podcast object
168 |
169 |
170 |
171 | Example:
172 |
173 | ```python
174 | podcastObj.duration
175 | ```
176 |
177 | Available attributes:
178 |
179 | | Name | Type | Description
180 | -------|------|------------
181 | `id` | `int` | Podcast id
182 | `plays` | `int` | Plays count
183 | `created_at` | `str` | Date and time the podcast was posted
184 | `permlink` | `str` | Podcast permlink
185 | `show_permlink` | `str` | In many times its equal to `permlink`
186 | `photo` | `HttpUrl` | Photo url
187 | `photo_player` | `Optional[HttpUrl]` | Player photo url
188 | `share_link` | `HttpUrl` | Podcast url
189 | `title` | `str` | Title
190 | `credit_tags` | `list[str]` | Credit tags
191 | `likes` | `int` | Likes count
192 | `dislikes` | `int` | DisLikes count
193 | `link` | `HttpUrl` | Download link
194 | `hq_link` | `HttpUrl` | High quality download link
195 | `lq_link` | `HttpUrl` | Low quality download link
196 | `hls_link` | `Optional[HttpUrl]` | Stream link
197 | `hq_hls` | `Optional[HttpUrl]` | High quality stream link
198 | `lq_hls` | `Optional[HttpUrl]` | Low quality stream link
199 | `is_talk` | `bool` | Its true if it is a talk-podcast
200 | `date` | `str` | IDK! RJ api sent everything except date!
201 | `short_date` | `str` | Same as `date`
202 | `duration` | `float` | Song duration
203 | `thumbnail` | `HttpUrl` | Thumbnail url
204 | `tracklist` | `Optional[str]` | Podcast track list (contains new line `\n`)
205 | `related_podcasts` | `list[`[ShortData](#shortdata)`]` | Related podcasts
206 |
207 | ## Artist
208 | This models an artist on RadioJavan.
209 |
210 |
211 | ### Attributes
212 | > Read-only values on an Artist object
213 |
214 |
215 |
216 | Example:
217 |
218 | ```python
219 | artistObj.name
220 | ```
221 |
222 | Available attributes:
223 |
224 | | Name | Type | Description
225 | -------|------|------------
226 | `name` | `str` | Artist name
227 | `plays` | `str` | Plays count (e.g. `2M`)
228 | `photo` | `HttpUrl` | Photo url
229 | `photo_player` | `HttpUrl` | Player photo url
230 | `photo_thumb` | `HttpUrl` | Thumbnail url
231 | `background` | `HttpUrl` | Background photo url
232 | `share_link` | `HttpUrl` | Artist url
233 | `following` | `bool` | Its true if you following that artist
234 | `followers_count` | `int` | Artist followers count
235 | `prereleases` | `Optional[list]` | Prereleases items
236 | `events` | `Optional[list]` | Events
237 | `photos` | `Optional[list]` | Photos
238 | `latest_song` | `list[`[ShortData](#shortdata)`]` | Latest song
239 | `songs` | `list[`[ShortData](#shortdata)`]` | Artist songs
240 | `albums` | `list[`[ShortData](#shortdata)`]` | Artist albums
241 | `videos` | `list[`[ShortData](#shortdata)`]` | Artist videos
242 | `podcasts` | `list[`[ShortData](#shortdata)`]` | Artist podcasts
243 | `music_playlists` | `list[`[ShortData](#shortdata)`]` | Artist song (mp3) playlist
244 |
245 |
246 | ## Story
247 | This models a story (selfie) on RadioJavan.
248 |
249 |
250 | ### Attributes
251 | > Read-only values on a Story object
252 |
253 |
254 |
255 | Example:
256 |
257 | ```python
258 | storyObj.id
259 | ```
260 |
261 | Available attributes:
262 |
263 | | Name | Type | Description
264 | -------|------|------------
265 | `id` | `int` | Story id
266 | `hash_id` | `str` | Story hash id
267 | `title` | `str` | Story title
268 | `location` | `str` | Story location
269 | `song` | `str` | Name of song in story
270 | `song_id` | `int` | Id of song in story
271 | `artist` | `str` | Song artist name
272 | `link` | `HttpUrl` | Download link
273 | `hq_link` | `HttpUrl` | High quality download link
274 | `lq_link` | `HttpUrl` | Low quality download link
275 | `filename` | `str` | File name
276 | `share_link` | `HttpUrl` | Story url
277 | `photo` | `HttpUrl` | Photo url
278 | `thumbnail` | `HttpUrl` | Thumbnail url
279 | `is_verified` | `bool` | Status of user or story verification
280 | `likes` | `str` | Story likes count
281 | `likes_pretty` | `str` | Story likes count in pretty format
282 | `user` | `list[`[User](#user)`]` | The owner of story
283 | `is_my_story` | `bool` | Its true if you are the owner
284 |
285 | ## User
286 | This models a user on RadioJavan.
287 |
288 |
289 | ### Attributes
290 | > Read-only values on a User object
291 |
292 |
293 |
294 | Example:
295 |
296 | ```python
297 | userObj.tracks
298 | ```
299 |
300 | Available attributes:
301 |
302 | | Name | Type | Description
303 | -------|------|------------
304 | `name` | `str` | User's name
305 | `firstname` | `str` | User's firstname
306 | `lastname` | `str` | User's lastname
307 | `display_name` | `str` | The name which shows to other users
308 | `username` | `str` | User's username
309 | `bio` | `Optional[str]` | User's bio
310 | `share_link` | `HttpUrl` | User profile url
311 | `has_subscription` | `bool` | Status of subscription
312 | `has_custom_photo` | `bool` | Its true when user has profile photo
313 | `is_verified` | `bool` | Status of verification
314 | `default_photo` | `HttpUrl` | Default photo url
315 | `default_thumbnail` | `HttpUrl` | Default thumbnail url
316 | `photo` | `HttpUrl` | Current photo url
317 | `thumbnail` | `HttpUrl` | Thumbnail url
318 | `followers_count` | `int` | Followers count
319 | `following_count` | `int` | Following count
320 | `following` | `Optional[bool]` | Its true if you following this user
321 | `playlists_count` | `int` | Number of playlists user has
322 | `songs_count` | `int` | Number of songs user has in library
323 | `artists_count` | `int` | Number of artists user has followed
324 | `artists_name` | `list[str]` | Name of artists user has followed
325 | `stories` | `list[`[Story](#story)`]` | Stories user has uploaded on RJ
326 | `music_playlists` | `list[`[ShortData](#shortdata)`]` | User music (mp3) playlists
327 |
328 | ## ShortUser
329 | This models a user with small data. To get full data you must use [Get Info](methods?id=get-info) methods with User's username.
330 |
331 |
332 | ### Attributes
333 | > Read-only values on a ShortUser object
334 |
335 |
336 |
337 | Example:
338 |
339 | ```python
340 | shortuserObj.tracks
341 | ```
342 |
343 | Available attributes:
344 |
345 | | Name | Type | Description
346 | -------|------|------------
347 | `display_name` | `str` | The name which users see
348 | `username` | `str` | Username
349 | `thumbnail` | `HttpUrl` | Thumbnail url
350 | `share_link` | `Optional[HttpUrl]` | Thumbnail url
351 |
352 | ## MusicPlaylist
353 | This models a music (song or mp3) playlist on RadioJavan.
354 |
355 |
356 | ### Attributes
357 | > Read-only values on a MusicPlaylist object
358 |
359 |
360 |
361 | Example:
362 |
363 | ```python
364 | playlistObj.songs
365 | ```
366 |
367 | Available attributes:
368 |
369 | | Name | Type | Description
370 | -------|------|------------
371 | `id` | `str` | MusicPlaylist id
372 | `title` | `str` | Title
373 | `count` | `int` | Songs count
374 | `created_at` | `str` | Date and time the playlist was created
375 | `created_by` | `str` | Owner of playlist
376 | `last_updated_at` | `str` | Latest date and time the playlist was updated
377 | `share_link` | `HttpUrl` | Playlist url
378 | `followers` | `int` | Followers count
379 | `following` | `Optional[bool]` | Its true if you following that playlist
380 | `sync` | `Optional[bool]` | Sync
381 | `is_public` | `bool` | Its true if its a public playlist
382 | `is_my_playlist` | `bool` | Its true if you are the owner
383 | `has_custom_photo` | `bool` | Its true when it has a custom photo
384 | `photo` | `HttpUrl` | Photo url
385 | `photo_player` | `Optional[HttpUrl]` | Player photo url
386 | `thumbnail` | `HttpUrl` | Thumbnail url
387 | `songs` | `list[`[Song](#song)`]` | All songs which in playlist
388 |
389 | ## VideoPlaylist
390 | This models a video playlist on RadioJavan.
391 |
392 |
393 | ### Attributes
394 | > Read-only values on a VideoPlaylist object
395 |
396 |
397 |
398 | Example:
399 |
400 | ```python
401 | playlistObj.videos
402 | ```
403 |
404 | Available attributes:
405 |
406 | | Name | Type | Description
407 | -------|------|------------
408 | `id` | `str` | VideoPlaylist id
409 | `title` | `str` | Title
410 | `count` | `int` | Videos count
411 | `created_at` | `str` | Date and time the playlist was created
412 | `last_updated_at` | `str` | Latest date and time the playlist was updated
413 | `share_link` | `HttpUrl` | Playlist url
414 | `photo` | `HttpUrl` | Photo url
415 | `photo_player` | `Optional[HttpUrl]` | Player photo url
416 | `thumbnail` | `HttpUrl` | Thumbnail url
417 | `is_my_playlist` | `bool` | Its true if you are the owner
418 | `videos` | `list[`[Video](#video)`]` | All videos which in playlist
419 |
420 | ## SearchResults
421 | This models a search results on RadioJavan.
422 |
423 |
424 | ### Attributes
425 | > Read-only values on a SearchResults object
426 |
427 |
428 |
429 | Example:
430 |
431 | ```python
432 | resultsObj.songs
433 | ```
434 |
435 | Available attributes:
436 |
437 | | Name | Type | Description
438 | -------|------|------------
439 | `query` | `str` | Query
440 | `songs` | `list[`[ShortData](#shortdata)`]` | All songs which found by search
441 | `albums` | `list[`[ShortData](#shortdata)`]` | All albums which found by search
442 | `videos` | `list[`[ShortData](#shortdata)`]` | All videos which found by search
443 | `podcasts` | `list[`[ShortData](#shortdata)`]` | All podcasts which found by search
444 | `music_playlists` | `list[`[ShortData](#shortdata)`]` | All song (mp3) playlists which found by search
445 | `shows` | `list[`[ShortData](#shortdata)`]` | All shows which found by search
446 | `users` | `list[`[User](#user)`]` | All users which found by search
447 | `artist_names` | `list[str]` | Name of all artists which found by search
448 |
449 |
450 | ## ShortData
451 | This models medias with small data. To get full data you must use [Get Info](methods?id=get-info) methods with id or name.
452 |
453 |
454 | ### Attributes
455 | > Read-only values on a ShortData object
456 |
457 |
458 |
459 | Example:
460 |
461 | ```python
462 | shordataObj.id
463 | ```
464 |
465 | Available attributes:
466 |
467 | | Name | Type | Description
468 | -------|------|------------
469 | `id` | `Union[int, str]` | Media id
470 | `artist` | ` Optional[str]` | Name of media artist
471 | `name` | ` Optional[str]` | Name of media
472 | `created_at` | `str` | Date and time that the media was posted
473 | `permlink` | ` Optional[str]` | Media permlink (e.g. `Donya-Boro-Bargard`)
474 | `photo` | `HttpUrl` | Photo url
475 | `photo_player` | `HttpUrl` | Player photo url
476 | `share_link` | `HttpUrl` | Media url
477 | `title` | `str` | Title
478 |
479 | ## MyPlaylists
480 | This models your playlists on RadioJavan.
481 |
482 |
483 | ### Attributes
484 | > Read-only values on a MyPlaylists object
485 |
486 |
487 |
488 | Example:
489 |
490 | ```python
491 | myplaylistsObj.music_playlists
492 | ```
493 |
494 | Available attributes:
495 |
496 | | Name | Type | Description
497 | -------|------|------------
498 | `music_playlists` | `list[`[ShortData](#shortdata)`]` | Your song (mp3) playlists
499 | `video_playlists` | `list[`[ShortData](#shortdata)`]` | Your video plyalists
500 |
501 |
502 | ## NotificationsStatus
503 | This models your current status of notifications settings on RadioJavan.
504 |
505 |
506 | ### Attributes
507 | > Read-only values on a NotificationsStatus object
508 |
509 |
510 |
511 | Example:
512 |
513 | ```python
514 | notifObj.artists_email
515 | ```
516 |
517 | Available attributes:
518 |
519 | | Name | Type
520 | -------|------
521 | `artists_email` | `bool`
522 | `artists_push` | `bool`
523 | `events_push` | `bool`
524 | `music_email` | `bool`
525 | `music_push` | `bool`
526 | `playlists_followers_push` | `bool`
527 | `selfies_push` | `bool`
--------------------------------------------------------------------------------
/docs/quick-start.md:
--------------------------------------------------------------------------------
1 | # Quick Start
2 |
3 | ## Installation
4 |
5 | **From PyPI**
6 | ```
7 | pip install radiojavanapi
8 | ```
9 |
10 | **From Github**
11 | ```
12 | pip install git+https://github.com/xHossein/radiojavanapi@master
13 | ```
14 |
15 |
16 | ## Basic Usage
17 |
18 | ```python
19 | from radiojavanapi import Client
20 |
21 | # Create a Client instance. and set a proxy (Optional)
22 | client = Client()
23 | client.set_proxy({
24 | 'http':'socks5://127.0.0.1:8087'
25 | 'https':'socks5://127.0.0.1:8087'
26 | })
27 |
28 | song = client.get_song_by_url(
29 | 'https://www.radiojavan.com/mp3s/mp3/Sijal-Baz-Mirim-Baham-(Ft-Sami-Low)')
30 |
31 | print(f"""
32 | Name: {song.name}
33 | Artist: {song.artist}
34 | Plays: {song.plays}
35 | Downloads: {song.downloads}
36 | HQ-Link: {song.hq_link}
37 | """)
38 |
39 | ```
40 |
41 | Show Output
42 |
43 | ```
44 | Name: Baz Mirim Baham (Ft Sami Low)
45 | Artist: Sijal
46 | Plays: 693934
47 | Downloads: 693934
48 | HQ-Link: https://host2.mediacon-rj.app/media/mp3/aac-256/99926-cf9dd3814907dbb.m4a
49 | ```
50 |
51 |
52 | !> Please note that `set_proxy` only gets proxy as dict.
53 |
--------------------------------------------------------------------------------
/docs/sidebar.md:
--------------------------------------------------------------------------------
1 | - [**Introduction**](introduction)
2 | - [**Quick Start**](quick-start)
3 | - [**Methods**](methods)
4 | - [**Models**](models)
5 | - [**Exceptions**](exceptions)
6 | - [**Changelog**](changelog)
--------------------------------------------------------------------------------
/radiojavanapi/__init__.py:
--------------------------------------------------------------------------------
1 | import json
2 | from radiojavanapi.mixins.song import SongMixin
3 | from radiojavanapi.mixins.album import AlbumMixin
4 | from radiojavanapi.mixins.story import StoryMixin
5 | from radiojavanapi.mixins.browse import BrowseMixin
6 | from radiojavanapi.mixins.playlist import MusicPlayListMixin, VideoPlayListMixin
7 | from radiojavanapi.mixins.podcast import PodcastMixin
8 | from radiojavanapi.mixins.video import VideoMixin
9 | from radiojavanapi.mixins.account import AccountMixin
10 |
11 | class Client(
12 | AccountMixin,
13 | VideoMixin,
14 | SongMixin,
15 | PodcastMixin,
16 | BrowseMixin,
17 | VideoPlayListMixin,
18 | MusicPlayListMixin,
19 | StoryMixin,
20 | AlbumMixin
21 | ):
22 | """
23 | Class used to access all features, this is the only class that
24 | needs to be imported (along with the exceptions)
25 | """
26 |
27 | def set_proxy(self, proxy:dict) -> None:
28 | assert isinstance(proxy, dict), f'Proxy must been Dict, but now "{proxy}" ({type(proxy)})'
29 | self.private.proxies = self.proxy = proxy
30 |
31 | def unset_proxy(self) -> None:
32 | self.private.proxies = self.proxy = None
33 |
34 | def save_session(self, path: str) -> bool:
35 | with open(path, 'w') as wf:
36 | json.dump({
37 | 'cookie': self.cookie,
38 | 'email': self.email
39 | }, wf, indent=4)
40 | return True
41 |
42 | def load_session(self, path: str) -> bool:
43 | with open(path, 'r') as rf:
44 | json_data = json.load(rf)
45 | self.initial()
46 | self.authorized = True
47 | self.cookie = json_data['cookie']
48 | self.email = json_data['email']
49 | self.private.headers.update({'Cookie': self.cookie})
50 | return True
--------------------------------------------------------------------------------
/radiojavanapi/constants.py:
--------------------------------------------------------------------------------
1 | API_DOMAIN = "https://rj-deskcloud.com/api2/{}"
2 |
3 | BASE_HEADERS = {
4 | 'Accept': 'application/json, text/plain, */*',
5 | 'Accept-Language': 'en-US',
6 | 'Accept-Encoding': 'gzip, deflate',
7 | 'Host': 'rj-deskcloud.com',
8 | 'Connection': 'keep-alive',
9 | 'x-rj-user-agent': 'Radio Javan/4.0.2/v4.0.3-1160-g4605067b (Windows 10 64-bit 13.0.1) com.radioJavan.rj.desktop',
10 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) RadioJavan/4.0.2 Chrome/91.0.4472.69 Electron/13.0.1 Safari/537.36',
11 | }
12 |
--------------------------------------------------------------------------------
/radiojavanapi/exceptions.py:
--------------------------------------------------------------------------------
1 | class ClientError(Exception):
2 | def __init__(self, *args, **kwargs):
3 | super().__init__(*args, **kwargs)
4 |
5 | class ClientLoginRequired(ClientError):
6 | pass
7 |
8 | class ClientJSONDecodeError(ClientError):
9 | pass
10 |
11 | class ClientConnectionError(ClientError):
12 | pass
13 |
14 | class PrivateError(ClientError):
15 | """For Private API"""
16 |
17 | class BadCredentials(PrivateError):
18 | pass
19 |
20 | class InvalidName(PrivateError):
21 | pass
22 |
23 | class LongString(PrivateError):
24 | pass
25 |
26 | class EmailExists(PrivateError):
27 | pass
28 |
29 | class UsernameExists(PrivateError):
30 | pass
31 |
32 | class DuplicateName(PrivateError):
33 | pass
34 |
35 | class InvalidMediaId(PrivateError):
36 | pass
37 |
38 | class InvalidEmail(PrivateError):
39 | pass
40 |
41 | class DuplicatePassword(PrivateError):
42 | pass
43 |
44 | class UnknownError(PrivateError):
45 | pass
46 |
47 |
--------------------------------------------------------------------------------
/radiojavanapi/extractors.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.helper import to_int
2 | from radiojavanapi.models import (
3 | Account, Album, Artist, MyPlaylists, NotificationsStatus,
4 | ShortUser, Song, MusicPlaylist, Story, User, Video,
5 | ShortData, Podcast, SearchResults, VideoPlaylist
6 | )
7 |
8 | def extract_account(data) -> Account:
9 | data["default_thumbnail"] = data.pop('default_thumb')
10 | data["has_subscription"] = data.pop('subscription')
11 | data["has_custom_photo"] = data.pop('custom_photo')
12 | data["is_verified"] = data.pop('verify')
13 | data["artists_name"] = [artist["name"] for artist in data.pop('artists')]
14 | data["stories"] = [extract_story(story)
15 | for story in data.pop('selfies',[])]
16 | return Account(**data)
17 |
18 | def extract_user(data) -> User:
19 | data["default_thumbnail"] = data.pop('default_thumb')
20 | data["has_custom_photo"] = data.pop('custom_photo')
21 | data["has_subscription"] = data.pop('subscription')
22 | data["is_verified"] = data.pop('verify')
23 | data["artists_name"] = [artist["name"] for artist in data.pop('artists')]
24 | data["stories"] = [extract_story(story)
25 | for story in data.pop('selfies',[])]
26 | data["music_playlists"] = [extract_short_data(playlist['playlist'], MusicPlaylist)
27 | for playlist in data.pop('playlists')]
28 | return User(**data)
29 |
30 | def extract_song(data) -> Song:
31 | album = data.pop('album', None)
32 | if type(album) == dict:
33 | album = album.pop('album', None)
34 | data["album"] = album if album else data.pop('album', None)
35 | data["plays"] = to_int(data.pop('plays'))
36 | data["name"] = data.pop('song')
37 | data["likes"] = to_int(data.pop('likes'))
38 | data["dislikes"] = to_int(data.pop('dislikes'))
39 | data["downloads"] = to_int(data.pop('downloads'))
40 | data["related_songs"] = [extract_short_data(song, Song) for song in data.pop('related',[])]
41 | data["stories"] = [extract_story(story) for story in data.pop('selfies',[])]
42 | return Song(**data)
43 |
44 | def extract_video(data) -> Video:
45 | data["lq_hls"] = data.get('low_web')
46 | data["hq_hls"] = data.get('high_web')
47 | data["name"] = data.pop('song')
48 | data["views"] = to_int(data.pop('views'))
49 | data["likes"] = to_int(data.pop('likes'))
50 | data["dislikes"] = to_int(data.pop('dislikes'))
51 | data["related_videos"] = [extract_short_data(video, Video) for video in data.pop('related',[])]
52 | return Video(**data)
53 |
54 | def extract_podcast(data) -> Podcast:
55 | data["is_talk"] = data.pop('talk')
56 | data["plays"] = to_int(data.pop('plays'))
57 | data["likes"] = to_int(data.pop('likes'))
58 | data["dislikes"] = to_int(data.pop('dislikes'))
59 | data["related_podcasts"] = [extract_short_data(podcast, Podcast) for podcast in data.pop('related',[])]
60 | return Podcast(**data)
61 |
62 | def extract_artist(data) -> Artist:
63 | data["photo_thumbnail"] = data.pop('photo_thumb')
64 | data["latest_song"] = extract_short_data(data.pop('latest'), Song) if data.get('latest') else None
65 | data['name'] = data.pop('query')
66 | followers = data.pop('followers')
67 | data["followers_count"] = followers['count']
68 | data["following"] = followers['following']
69 | data["plays"] = followers['plays']
70 | data["songs"] = [extract_short_data(mp3, Song) for mp3 in data.pop('mp3s')]
71 | data["albums"] = [extract_short_data(album, Album) for album in data.pop('albums')]
72 | data["videos"] = [extract_short_data(video, Video) for video in data.pop('videos')]
73 | data["podcasts"] = [extract_short_data(podcasts, Podcast) for podcasts in data.pop('podcasts')]
74 | data["music_playlists"] = [extract_short_data(playlist['playlist'], MusicPlaylist)
75 | for playlist in data.pop('playlists')]
76 | return Artist(**data)
77 |
78 | def extract_short_data(data, type) -> ShortData:
79 | if type == Song or type == Video:
80 | data["name"] = data["song"]
81 |
82 | elif type == Album:
83 | data["artist"] = data["album_artist"]
84 | data["name"] = data["album_album"]
85 |
86 | elif type == Podcast:
87 | data["artist"] = data["podcast_artist"]
88 | data["name"] = data["title"]
89 | data["title"] = '{} - \"{}\"'.format(data["artist"], data["name"])
90 |
91 | elif type == MusicPlaylist:
92 | data["name"] = data["title"]
93 | data["title"] = '{} - \"{}\"'.format(data["name"], data["created_by"])
94 |
95 | elif type == VideoPlaylist:
96 | data["name"] = data["title"]
97 |
98 | elif type == 'show':
99 | data["artist"] = data["date"]
100 | data["name"] = data["show_title"]
101 | data["title"] = '{} - \"{}\"'.format(data["artist"], data["name"])
102 | data["permlink"] = data["show_permlink"]
103 |
104 | return ShortData(**data)
105 |
106 | def extract_search_results(data) -> SearchResults:
107 | data["songs"] = [extract_short_data(mp3, Song) for mp3 in data.pop('mp3s')]
108 | data["albums"] = [extract_short_data(album, Album) for album in data.pop('albums')]
109 | data["videos"] = [extract_short_data(video, Video) for video in data.pop('videos')]
110 | data["podcasts"] = [extract_short_data(podcasts, Podcast) for podcasts in data.pop('podcasts')]
111 | data["shows"] = [extract_short_data(show, 'show') for show in data.pop('shows')]
112 | data["users"] = [extract_short_user(profile) for profile in data.pop('profiles')]
113 | data["artist_names"] = [artist["name"] for artist in data.pop('artists')]
114 | data["music_playlists"] = [extract_short_data(playlist['playlist'], MusicPlaylist)
115 | for playlist in data.pop('playlists')]
116 | return SearchResults(**data)
117 |
118 | def extract_video_playlist(data) -> VideoPlaylist:
119 | data['is_my_playlist'] = data.pop('myplaylist')
120 | data["videos"] = [extract_video(video) for video in data.pop('items',[])]
121 | return VideoPlaylist(**data)
122 |
123 | def extract_music_playlist(data) -> MusicPlaylist:
124 | data['is_my_playlist'] = data.pop('myplaylist')
125 | data['is_public'] = data.pop('public')
126 | data['has_custom_photo'] = data.pop('custom_photo')
127 | data["sync"] = True if data.pop('sync', None) else False
128 | data["songs"] = [extract_song(song) for song in data.pop('items',[])]
129 | return MusicPlaylist(**data)
130 |
131 | def extract_short_user(data) -> Story:
132 | return ShortUser(**data)
133 |
134 | def extract_story(data) -> Story:
135 | data['is_verified'] = data.pop('verified')
136 | data['lq_link'] = data.pop('hls')
137 | data["song_id"] = data.pop('mp3')
138 | data['is_my_story'] = data.pop('myselfie',None)
139 | data['user'] = extract_short_user(data.pop('user'))
140 | return Story(**data)
141 |
142 | def extract_album(data):
143 | data["tracks"] = [extract_song(song)
144 | for song in data.pop('album_tracks')]
145 | data['name'] = data.pop('album_album')
146 | data['artist'] = data.pop('album_artist')
147 | data['share_link'] = data.get('album_share_link','share_link')
148 | return Album(**data)
149 |
150 | def extract_my_playlists(data):
151 | return MyPlaylists(**{
152 | "music_playlists": [extract_short_data(mpl, MusicPlaylist)
153 | for mpl in data['mp3s']['myplaylists']],
154 | "video_playlists": [extract_short_data(vpl, VideoPlaylist)
155 | for vpl in data['videos']['myplaylists']]
156 | })
157 |
158 | def extract_notifications_status(data) -> NotificationsStatus:
159 | return NotificationsStatus(**data)
160 |
--------------------------------------------------------------------------------
/radiojavanapi/helper.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | def url_to_id(url: str) -> str:
4 | return re.findall(
5 | r'.*[video|mp3|song|podcast|artist|story|preview|u]/([\d\w\-_()+]+)',url)[0]
6 |
7 | def to_int(string: str) -> int:
8 | return int(string.replace(',','').replace('+',''))
9 |
10 | def extract_cookie(string: str) -> str:
11 | return re.findall(r'(_rj_web=.*?;)', string)[0]
--------------------------------------------------------------------------------
/radiojavanapi/mixins/__init__.py:
--------------------------------------------------------------------------------
1 | #
--------------------------------------------------------------------------------
/radiojavanapi/mixins/account.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.user import UserMixin
2 | from radiojavanapi.mixins.auth import AuthMixin
3 | from radiojavanapi.models import Account, MyPlaylists, NotificationsStatus, ShortUser
4 | from radiojavanapi.extractors import extract_account, extract_my_playlists, extract_notifications_status
5 |
6 | from typing import List
7 |
8 | class AccountMixin(AuthMixin, UserMixin):
9 | def account_info(self) -> Account:
10 | """
11 | Get private info for your account
12 |
13 | Returns
14 | -------
15 | Account: An object of Account type
16 |
17 | """
18 | response = self.private_request('user_profile', need_login=True).json()
19 | return extract_account(response)
20 |
21 |
22 | def account_edit(self,
23 | firstname: str = None,
24 | lastname: str = None,
25 | username: str = None,
26 | email: str = None,
27 | bio: str = None,
28 | _remove_photo: bool = False
29 | ) -> Account:
30 |
31 | """
32 | Change profile data (e.g. email, firstname, ...).
33 | Note: To remove bio, pass empty string.
34 |
35 | Arguments
36 | ----------
37 | firstname: Account's firstname
38 | lastname : Account's lastname
39 | username : Account's username
40 | email : Account's email
41 |
42 | Returns
43 | -------
44 | Account: An object of Account type
45 |
46 | """
47 | account = self.account_info()
48 | payload = {
49 | 'firstname' : firstname or account.firstname,
50 | 'lastname' : lastname or account.lastname,
51 | 'email' : email or account.email,
52 | 'username' : username or account.username,
53 | }
54 | if _remove_photo:
55 | payload.update({"remove_photo": True})
56 |
57 | if bio != None:
58 | payload.update({"bio": bio})
59 |
60 | self.private_request('user_profile_update',
61 | json=payload, need_login=True)
62 | return self.account_info()
63 |
64 | def account_notifications(self) -> NotificationsStatus:
65 | """
66 | Get current status of account notifications settings
67 |
68 | Returns
69 | -------
70 | NotificationsStatus: An object of NotificationsStatus type which holds current settings
71 |
72 | """
73 | response = self.private_request('user_notifications', need_login=True).json()
74 | return extract_notifications_status(response)
75 |
76 | def account_notifications_update(self,
77 | new_music: bool = None,
78 | followed_artists: bool = None,
79 | ) -> bool:
80 |
81 | """
82 | Update account notifications settings.
83 |
84 | Arguments
85 | ----------
86 | new_music: Send occasional email notifications new music.
87 | followed_artists : Send email notifications about artists that you follow.
88 |
89 | Returns
90 | -------
91 | bool: Returns true if success
92 |
93 | """
94 | acc_notif = self.account_notifications()
95 | payload = {
96 | 'music_email' : str(int(new_music if new_music != None else acc_notif.music_email)),
97 | 'artists_email' : str(int(followed_artists if followed_artists != None else acc_notif.artists_email))
98 | }
99 | return self.private_request('user_notifications_update',
100 | json=payload, need_login=True).json()['success']
101 |
102 | def change_password(self, password: str) -> bool:
103 | """
104 | Change your account password
105 |
106 | Arguments
107 | ----------
108 | password: Your new password
109 |
110 | Returns
111 | -------
112 | bool: Returns true if success
113 |
114 | """
115 | response = self.private_request('user_password_update',
116 | json={
117 | "oldpass" : self.password,
118 | "newpass1": password,
119 | "newpass2": password
120 | }, need_login=True)
121 |
122 | response_json = response.json()
123 | if response_json['success']:
124 | self.password = password
125 | self.cookie = {
126 | 'Cookie': '_rj_web={}'.format(
127 | response.headers['Set-Cookie'].split('_rj_web=')[1].split(';')[0]
128 | )
129 | }
130 | self.private.headers.update(self.cookie)
131 | return True
132 | return False
133 |
134 | def upload_photo(self, photo_path: str) -> bool:
135 | """
136 | Upload your profile photo (support jpg/png)
137 |
138 | Arguments
139 | ----------
140 | photo_path:
141 | Path to the image you want to upload
142 |
143 | Returns
144 | -------
145 | bool: Returns true if success
146 |
147 | """
148 | account = self.account_info()
149 | with open(photo_path, 'rb') as photo:
150 | fields = {
151 | 'firstname': account.firstname,
152 | 'lastname': account.lastname,
153 | 'email': account.email,
154 | 'username': account.username,
155 | 'remove_photo': 'undefined',
156 | 'photo': ('0', photo, 'image/jpeg')
157 | }
158 | return self.private_request('user_profile_update',
159 | fields=fields , need_login=True).json()['success']
160 |
161 | def remove_photo(self) -> bool:
162 | """
163 | Remove your profile photo
164 |
165 | Returns
166 | -------
167 | bool: Returns true if removed successfully
168 |
169 | """
170 | account = self.account_edit(_remove_photo = True)
171 | return account.has_custom_photo != True
172 |
173 | def deactive_account(self) -> bool:
174 | """
175 | Deactivate your profile and logout
176 |
177 | Returns
178 | -------
179 | bool: Returns true if success
180 |
181 | """
182 | if self.private_request('deactivate',
183 | need_login=True).json()['success'] and self.logout():
184 | return True
185 | return False
186 |
187 | def following_artists(self) -> List[str]:
188 | """
189 | Get list of artist names which you've followed
190 |
191 | Returns
192 | -------
193 | List: names list
194 |
195 | """
196 | return self.account_info().artists_name
197 |
198 | def my_followers(self) -> List[ShortUser]:
199 | """
200 | Get list of your followers
201 |
202 | Returns
203 | -------
204 | List[ShortUser]: list of your followers as ShortUser object
205 |
206 | """
207 | return self.get_user_followers(self.account_info().username)
208 |
209 | def my_following(self) -> List[ShortUser]:
210 | """
211 | Get list of user which you've followed
212 |
213 | Returns
214 | -------
215 | List[ShortUser]: list of your following as ShortUser object
216 |
217 | """
218 | return self.get_user_following(self.account_info().username)
219 |
220 | def my_playlists(self) -> MyPlaylists:
221 | """
222 | Get your MusicPlaylist & VideoPlaylist' shortdata as MyPlaylists Object
223 |
224 | Returns
225 | -------
226 | MyPlaylists: An object of MyPlaylists type
227 |
228 | """
229 | response = self.private_request('playlists_dash', need_login=True).json()
230 | return extract_my_playlists(response)
231 |
--------------------------------------------------------------------------------
/radiojavanapi/mixins/album.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.search import SearchMixin
2 | from radiojavanapi.extractors import extract_album
3 | from radiojavanapi.models import Album
4 | from radiojavanapi.helper import url_to_id
5 |
6 | from typing import Union
7 | from pydantic import HttpUrl
8 |
9 | class AlbumMixin(SearchMixin):
10 | def get_album_by_url(self, url: HttpUrl) -> Album:
11 | """
12 | Get album info by site url (e.g. `play.radiojavan.com/album/...` or `play.radiojavan.com/redirect?r=radiojavan://mp3_album/...`)
13 |
14 | Arguments
15 | ----------
16 | url: Site url of album
17 |
18 | Returns
19 | -------
20 | Album: An object of Album type
21 |
22 | """
23 | query = url_to_id(url).lower().replace('-',' ')
24 | albums = self.search(query).albums
25 | for album in albums:
26 | if album.artist.lower() in query and album.name.lower() in query:
27 | return self.get_album_by_id(album.id)
28 |
29 | def get_album_by_id(self, id: Union[int, str]) -> Album:
30 | """
31 | Get album info by id
32 |
33 | Arguments
34 | ----------
35 | id: This id belong to one of album-tracks
36 |
37 | Returns
38 | -------
39 | Album: An object of Album type
40 |
41 | """
42 | album = self.private_request('mp3',
43 | params=f'id={id}').json()
44 | return extract_album(album)
45 |
46 |
--------------------------------------------------------------------------------
/radiojavanapi/mixins/artist.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.private import PrivateRequest
2 | from radiojavanapi.extractors import extract_artist
3 | from radiojavanapi.models import Artist
4 | from radiojavanapi.helper import url_to_id
5 |
6 | from pydantic import HttpUrl
7 | from urllib.parse import quote_plus
8 |
9 | class ArtistMixin(PrivateRequest):
10 | def get_artist_by_url(self, url: HttpUrl) -> Artist:
11 | """
12 | Get artist info by site url (e.g. `play.radiojavan.com/artist/...` or `play.radiojavan.com/redirect?r=radiojavan://artist/...`)
13 |
14 | Arguments
15 | ----------
16 | url: Site url of artist
17 |
18 | Returns
19 | -------
20 | Artist: An object of Artist type
21 |
22 | """
23 | return self.get_artist_by_name(url_to_id(url))
24 |
25 | def get_artist_by_name(self, name: str) -> Artist:
26 | """
27 | Get artist info by name (must be the exact name on RadioJavan API)
28 |
29 | Arguments
30 | ----------
31 | name: Exact name of artist on RadioJavan API
32 |
33 | Returns
34 | -------
35 | Artist: Return An object of Artist type
36 |
37 | """
38 | response = self.private_request(
39 | 'artist', params=f'query={quote_plus(name)}').json()
40 | return extract_artist(response)
41 |
42 | def follow_artist(self, name: str) -> bool:
43 | """
44 | Follow an artist
45 |
46 | Arguments
47 | ----------
48 | name: Exact name of artist on RadioJavan API
49 |
50 | Returns
51 | -------
52 | bool: RJ api result
53 |
54 | """
55 | response = self.private_request('artist_follow',
56 | params=f'artist={quote_plus(name)}',
57 | need_login=True).json()
58 | return response['success'] == True
59 |
60 | def unfollow_artist(self, name: str) -> bool:
61 | """
62 | UnFollow an artist
63 |
64 | Arguments
65 | ----------
66 | name: Exact name of artist on RadioJavan API
67 |
68 | Returns
69 | -------
70 | bool: RJ api result
71 |
72 | """
73 | response = self.private_request('artist_unfollow',
74 | params=f'artist={quote_plus(name)}',
75 | need_login=True).json()
76 | return response['success'] == True
77 |
--------------------------------------------------------------------------------
/radiojavanapi/mixins/auth.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.helper import extract_cookie
2 | from radiojavanapi.mixins.private import PrivateRequest
3 | from radiojavanapi.constants import BASE_HEADERS
4 |
5 | class AuthMixin(PrivateRequest):
6 | def __init__(self) -> None:
7 | super().__init__()
8 | self.cookie = None
9 |
10 | def initial(self):
11 | """
12 | Initialize Login/SignUp helpers
13 |
14 | """
15 | if self.authorized:
16 | self.logout()
17 |
18 | self.private.cookies.clear()
19 | self.private.headers.update(BASE_HEADERS)
20 | self.cookie = None
21 | self.authorized = False
22 | self.email = None
23 | self.password = None
24 | self.update_cookie(self.private_request('app_config'))
25 |
26 | def update_cookie(self, response):
27 | """
28 | Extract cookie and update headers
29 |
30 | """
31 | self.cookie = extract_cookie(response.headers.get('Set-Cookie'))
32 | self.private.headers.update({'Cookie': self.cookie})
33 |
34 | def login(self, email: str, password: str) -> bool:
35 | """
36 | Log in to RadioJavan
37 |
38 | Arguments
39 | ----------
40 | email: Your account email
41 | password: Your account password
42 |
43 | Returns
44 | -------
45 | bool: login result
46 |
47 | """
48 | self.initial()
49 | self.email = email
50 | self.password = password
51 |
52 | payload = {
53 | "login_email": email,
54 | "login_password": password
55 | }
56 |
57 | response = self.private_request('login', json=payload)
58 | response_json = response.json()
59 |
60 | if response_json.get('success'):
61 | self.update_cookie(response)
62 | self.authorized = True
63 |
64 | return self.authorized
65 |
66 | def logout(self) -> bool:
67 | return self.private_request('logout', need_login=True).json()["success"]
68 |
69 | def signup(self,
70 | firstname: str, lastname: str,
71 | username: str, email: str, password: str,
72 | auto_login: bool = False) -> bool:
73 |
74 | """
75 | Sign up to RadioJavan
76 |
77 | Arguments
78 | ----------
79 | firstname: Your account firstname
80 | lastname: Your account lastname
81 | username: Your account username
82 | email: Your account email
83 | password: Your account password
84 | auto_login [optional]: login to account after sign up
85 |
86 | Returns
87 | -------
88 | bool: sign up result
89 |
90 | """
91 | self.initial()
92 |
93 | payload = {
94 | "email": email,
95 | "password": password,
96 | "username": username,
97 | "email_confirm": email,
98 | "firstname": firstname,
99 | "lastname": lastname
100 | }
101 |
102 | is_success = self.private_request('signup_mobile', json=payload).json()['success']
103 | if is_success and auto_login:
104 | self.login(email, password)
105 |
106 | return is_success
--------------------------------------------------------------------------------
/radiojavanapi/mixins/browse.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.album import AlbumMixin
2 | from radiojavanapi.mixins.artist import ArtistMixin
3 | from radiojavanapi.models import (
4 | Album, Artist, Podcast, Song, Story, Video
5 | )
6 | from radiojavanapi.extractors import (
7 | extract_song, extract_podcast,
8 | extract_story, extract_video,
9 | )
10 |
11 | from typing import Dict, List
12 |
13 | class BrowseMixin(ArtistMixin, AlbumMixin):
14 | def __browse_req__(self, endpoint, type):
15 | extractor = None
16 | if type == 'albums':
17 | extractor = self.get_album_by_id
18 | elif endpoint == "mp3s":
19 | extractor = extract_song
20 | elif endpoint == "videos":
21 | extractor = extract_video
22 | elif endpoint == "podcasts":
23 | extractor = extract_podcast
24 |
25 | page, items, response = 1, [], {""}
26 | while bool(response):
27 | response = self.private_request(endpoint,
28 | params=f'url={endpoint}&type={type}&page={page}').json()
29 | items.extend([extractor(item['id'] if type=='albums' else item) for item in response])
30 | page+=1
31 | return items
32 |
33 | def get_latest_stories(self) -> List[Story]:
34 | """Get list of latest stories"""
35 | response = self.private_request('selfies_browse',
36 | params='url=selfies_browse').json()
37 | return [extract_story(story) for story in response]
38 |
39 | def get_trending_songs(self) -> List[Song]:
40 | """Get list of trending songs"""
41 | return self.__browse_req__('mp3s','trending')
42 |
43 | def get_popular_songs(self) -> List[Song]:
44 | """Get list of popular songs"""
45 | return self.__browse_req__('mp3s','popular')
46 |
47 | def get_featured_songs(self) -> List[Song]:
48 | """Get list of featured songs"""
49 | return self.__browse_req__('mp3s','featured')
50 |
51 | def get_latest_albums(self) -> List[Album]:
52 | """Get list of latest albums"""
53 | return self.__browse_req__('mp3s','albums')
54 |
55 | def get_trending_videos(self) -> List[Video]:
56 | """Get list of trending videos"""
57 | return self.__browse_req__('videos','trending')
58 |
59 | def get_popular_videos(self) -> List[Video]:
60 | """Get list of popular videos"""
61 | return self.__browse_req__('videos','popular')
62 |
63 | def get_featured_videos(self) -> List[Video]:
64 | """Get list of featured videos"""
65 | return self.__browse_req__('videos','featured')
66 |
67 | def get_latest_videos(self) -> List[Video]:
68 | """Get list of latest videos"""
69 | return self.__browse_req__('videos','latest')
70 |
71 | def get_popular_podcasts(self) -> List[Podcast]:
72 | """Get list of popular podcasts"""
73 | return self.__browse_req__('podcasts','popular')
74 |
75 | def get_featured_podcasts(self) -> List[Podcast]:
76 | """Get list of featured podcasts"""
77 | return self.__browse_req__('podcasts','featured')
78 |
79 | def get_talk_podcasts(self) -> List[Podcast]:
80 | """Get list of talk podcasts"""
81 | return self.__browse_req__('podcasts','talk')
82 |
83 | def get_shows_podcasts(self) -> List[Podcast]:
84 | """Get list of shows podcasts"""
85 | return self.__browse_req__('podcasts','shows')
86 |
87 | def get_radio_stream(self) -> Dict:
88 | """Get RJ radio stream links and short data of current and next songs"""
89 | songs = self.private_request('radio_nowplaying').json()
90 | return {
91 | 'links': self.private_request('streams').json(),
92 | 'current_song': songs[0],
93 | 'next_songs': songs[1:]
94 | }
95 |
96 | def get_tv_stream(self) -> str:
97 | """Get RJ tv stream link"""
98 | return self.private_request('app_config').json()['config']['tv']['hls']
--------------------------------------------------------------------------------
/radiojavanapi/mixins/playlist.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.private import PrivateRequest
2 | from radiojavanapi.extractors import extract_video_playlist, extract_music_playlist
3 | from radiojavanapi.helper import url_to_id
4 | from radiojavanapi.models import MusicPlaylist, VideoPlaylist
5 |
6 | from typing import Optional, Union
7 | from pydantic import HttpUrl
8 |
9 | class MusicPlayListMixin(PrivateRequest):
10 | def get_music_playlist_by_url(self, url: HttpUrl) -> MusicPlaylist:
11 | """
12 | Get music playlist info by site url (e.g. `play.radiojavan.com/playlist/mp3/...` or `play.radiojavan.com/redirect?r=radiojavan://playlist/mp3/...`)
13 |
14 | Arguments
15 | ----------
16 | url: Site url of music playlist
17 |
18 | Returns
19 | -------
20 | MusicPlaylist: An object of Music Playlist type
21 |
22 | """
23 | return self.get_music_playlist_by_id(url_to_id(url))
24 |
25 | def get_music_playlist_by_id(self, id: str) -> MusicPlaylist:
26 | """
27 | Get music playlist info by id
28 |
29 | Arguments
30 | ----------
31 | id: Unique id of music playlist
32 |
33 | Returns
34 | -------
35 | MusicPlaylist: An object of Music Playlist type
36 |
37 | """
38 | response = self.private_request('mp3_playlist_with_items',
39 | params=f'id={id}').json()
40 | return extract_music_playlist(response)
41 |
42 | def follow_music_playlist(self, id: str) -> bool:
43 | """
44 | Follow a music playlist
45 |
46 | Arguments
47 | ----------
48 | id: An id of music playlist
49 |
50 | Returns
51 | -------
52 | bool: RJ api result
53 |
54 | """
55 | response = self.private_request('mp3_playlist_follow',
56 | params=f'id={id}&type=mp3',
57 | need_login=True).json()
58 | return response['success'] == True
59 |
60 | def unfollow_music_playlist(self, id: str) -> bool:
61 | """
62 | UnFollow a music playlist
63 |
64 | Arguments
65 | ----------
66 | id: An id of music playlist
67 |
68 | Returns
69 | -------
70 | bool: RJ api result
71 |
72 | """
73 | response = self.private_request('mp3_playlist_unfollow',
74 | params=f'id={id}&type=mp3',
75 | need_login=True).json()
76 | return response['success'] == True
77 |
78 | def create_music_playlist(self, name: str, song_id: Union[int, str]) -> Optional[str]:
79 | """
80 | Create a music playlist
81 | Note: in RJ you can't create empty playlist , so you need a song for creating playlist
82 |
83 | Arguments
84 | ----------
85 | name: Name of playlist
86 | song_id: A digit id of Song
87 |
88 | Returns
89 | -------
90 | str: Playlist's id
91 |
92 | """
93 | response = self.private_request('mp3_playlist_add',
94 | params=f'type=mp3&mp3={song_id}&name={name}',
95 | need_login=True).json()
96 | return response['playlist'] if response['success'] else None
97 |
98 | def delete_music_playlist(self, id: str) -> bool:
99 | """
100 | Delete your music playlist
101 |
102 | Arguments
103 | ----------
104 | id: An id of music playlist
105 |
106 | Returns
107 | -------
108 | bool: Returns true if success
109 |
110 | """
111 | return self.private_request('mp3_playlist_remove',
112 | params=f'type=mp3&id={id}',
113 | need_login=True).json()['success']
114 |
115 | def rename_music_playlist(self, id: str, name: str) -> bool:
116 | """
117 | Rename your music playlist
118 |
119 | Arguments
120 | ----------
121 | id: An id of music playlist
122 | name: The name you want to set for a playlist
123 |
124 | Returns
125 | -------
126 | bool: Returns true if success
127 |
128 | """
129 | return self.private_request('mp3_playlist_rename',
130 | params=f'type=mp3&id={id}&name={name}',
131 | need_login=True).json()['success']
132 |
133 | def add_to_music_playlist(self, id: str, song_id: Union[int, str]) -> bool:
134 | """
135 | Add a song to your music playlist
136 |
137 | Arguments
138 | ----------
139 | id: An id of music playlist
140 | song_id: A digit id of Song
141 |
142 | Returns
143 | -------
144 | bool: Returns false if song had been added already
145 |
146 | """
147 | songs = self.get_music_playlist_by_id(id).songs
148 | for sng in songs:
149 | if song_id == sng.id:
150 | return False
151 |
152 | return self.private_request('mp3_playlist_add',
153 | params=f'id={id}&mp3={song_id}&start=0',
154 | need_login=True).json()['success']
155 |
156 | def remove_from_music_playlist(self, id: str, song_id: Union[int, str]) -> bool:
157 | """
158 | Remove a song from your music playlist
159 |
160 | Arguments
161 | ----------
162 | id: An id of music playlist
163 | song_id: A digit id of Song
164 |
165 | Returns
166 | -------
167 | bool: Returns false if song hadn't been added before
168 |
169 | """
170 | songs = self.get_music_playlist_by_id(id).songs
171 | for sng in songs:
172 | if song_id == sng.id:
173 | return self.private_request('mp3_playlist_item_remove',
174 | params=f'type=mp3&id={id}&item={sng.item}',
175 | need_login=True).json()['success']
176 | return False
177 |
178 | class VideoPlayListMixin(PrivateRequest):
179 | def get_video_playlist_by_url(self, url: HttpUrl) -> VideoPlaylist:
180 | """
181 | Get video playlist info by site url (e.g. `play.radiojavan.com/playlist/video/...` or `play.radiojavan.com/redirect?r=radiojavan://playlist/video/...`)
182 |
183 | Arguments
184 | ----------
185 | url: Site url of video playlist
186 |
187 | Returns
188 | -------
189 | VideoPlaylist: An object of Video Playlist type
190 |
191 | """
192 | return self.get_video_playlist_by_id(url_to_id(url))
193 |
194 | def get_video_playlist_by_id(self, id: str) -> VideoPlaylist:
195 | """
196 | Get video playlist info by id
197 |
198 | Arguments
199 | ----------
200 | id: Unique id of video playlist
201 |
202 | Returns
203 | -------
204 | VideoPlaylist: An object of Video Playlist type
205 |
206 | """
207 | response = self.private_request('video_playlist_with_items',
208 | params=f'id={id}').json()
209 | return extract_video_playlist(response)
210 |
211 | def create_video_playlist(self, name: str, video_id: Union[int, str]) -> Optional[str]:
212 | """
213 | Create a video playlist
214 | Note: in RJ you can't create empty playlist , so you need a video for creating playlist
215 |
216 | Arguments
217 | ----------
218 | name: Name of playlist
219 | video_id: A digit id of Video
220 |
221 | Returns
222 | -------
223 | str: Playlist's id
224 |
225 | """
226 | response = self.private_request('video_playlist_add',
227 | params=f'type=video&video={video_id}&name={name}',
228 | need_login=True).json()
229 | return response['playlist'] if response['success'] else None
230 |
231 | def delete_video_playlist(self, id: str) -> bool:
232 | """
233 | Delete your video playlist
234 |
235 | Arguments
236 | ----------
237 | id: An id of video playlist
238 |
239 | Returns
240 | -------
241 | bool: Returns true if success
242 |
243 | """
244 | return self.private_request('video_playlist_remove',
245 | params=f'type=video&id={id}',
246 | need_login=True).json()['success']
247 |
248 | def rename_video_playlist(self, id: str, name: str) -> bool:
249 | """
250 | Rename your video playlist
251 |
252 | Arguments
253 | ----------
254 | id: An id of video playlist
255 | name: The name you want to set for a playlist
256 |
257 | Returns
258 | -------
259 | bool: Returns true if success
260 |
261 | """
262 | return self.private_request('video_playlist_rename',
263 | params=f'type=video&id={id}&name={name}',
264 | need_login=True).json()['success']
265 |
266 | def add_to_video_playlist(self, id: str, video_id: Union[int, str]) -> bool:
267 | """
268 | Add a video to your video playlist
269 |
270 | Arguments
271 | ----------
272 | id: An id of video playlist
273 | video_id: A digit id of Video
274 |
275 | Returns
276 | -------
277 | bool: Returns false if video had been added already
278 |
279 | """
280 | videos = self.get_video_playlist_by_id(id).videos
281 | for vid in videos:
282 | if vid.id == video_id:
283 | return False
284 |
285 | return self.private_request('video_playlist_add',
286 | params=f'type=video&video={video_id}&id={id}',
287 | need_login=True).json()['success']
288 |
289 | def remove_from_video_playlist(self, id: str, video_id: Union[int, str]) -> bool:
290 | """
291 | Remove a video from your video playlist
292 |
293 | Arguments
294 | ----------
295 | id: An id of video playlist
296 | video_id: A digit id of Video
297 |
298 | Returns
299 | -------
300 | bool: Returns false if video hadn't been added before
301 |
302 | """
303 | videos = self.get_video_playlist_by_id(id).videos
304 | for vid in videos:
305 | if vid.id == video_id:
306 | return self.private_request('video_playlist_item_remove',
307 | params=f'type=video&id={id}&item={vid.item}',
308 | need_login=True).json()['success']
309 | return False
310 |
311 |
312 |
--------------------------------------------------------------------------------
/radiojavanapi/mixins/podcast.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.private import PrivateRequest
2 | from radiojavanapi.extractors import extract_podcast
3 | from radiojavanapi.models import Podcast
4 | from radiojavanapi.helper import url_to_id
5 |
6 | from typing import Union
7 | from pydantic import HttpUrl
8 |
9 | class PodcastMixin(PrivateRequest):
10 | LIKE_ENDPOINT = 'podcast_vote'
11 | TYPE = 'podcast'
12 |
13 | def get_podcast_by_url(self, url: HttpUrl) -> Podcast:
14 | """
15 | Get podcast info by site url (e.g. `play.radiojavan.com/podcast/...` or `play.radiojavan.com/redirect?r=radiojavan://podcast/...`)
16 |
17 | Arguments
18 | ----------
19 | url: Site url of podcast
20 |
21 | Returns
22 | -------
23 | Podcast: An object of Podcast type
24 |
25 | """
26 | return self.get_podcast_by_id(url_to_id(url))
27 |
28 | def get_podcast_by_id(self, id: Union[int, str]) -> Podcast:
29 | """
30 | Get podcast info by id
31 |
32 | Arguments
33 | ----------
34 | id: Unique id of podcast
35 |
36 | Returns
37 | -------
38 | Podcast: An object of Podcast type
39 |
40 | """
41 | response = self.private_request('podcast',
42 | params=f'id={id}').json()
43 | return extract_podcast(response)
44 |
45 | def like_podcast(self, podcast_id: Union[int, str]) -> bool:
46 | """
47 | Like a podcast
48 |
49 | Arguments
50 | ----------
51 | podcast_id: A digit id of podcast
52 |
53 | Returns
54 | -------
55 | bool: Returns false if podcast had been liked already
56 |
57 | """
58 | return PodcastMixin.__like__(self, podcast_id)
59 |
60 | def unlike_podcast(self, podcast_id: Union[int, str]) -> bool:
61 | """
62 | UnLike a podcast
63 |
64 | Arguments
65 | ----------
66 | podcast_id: A digit id of podcast
67 |
68 | Returns
69 | -------
70 | bool: Returns false if podcast hadn't been liked before
71 |
72 | """
73 | return PodcastMixin.__unlike__(self, podcast_id)
74 |
75 |
--------------------------------------------------------------------------------
/radiojavanapi/mixins/private.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.constants import API_DOMAIN, BASE_HEADERS
2 | from radiojavanapi.exceptions import (
3 | BadCredentials, ClientJSONDecodeError, ClientConnectionError, DuplicatePassword,
4 | EmailExists, ClientLoginRequired, InvalidEmail, LongString, InvalidName, DuplicateName,
5 | UnknownError, UsernameExists, ClientLoginRequired, InvalidMediaId
6 | )
7 |
8 | import requests
9 | from json.decoder import JSONDecodeError
10 | from requests_toolbelt.multipart.encoder import MultipartEncoder
11 |
12 | class PrivateRequest():
13 | def __init__(self) -> None:
14 | self.private = requests.Session()
15 | self.private.headers = BASE_HEADERS
16 | self.authorized = False
17 | self.email = None
18 | self.password = None
19 |
20 | def private_request(self,endpoint,data=None,json=None,fields=None,
21 | params=None,headers=None,need_login=False):
22 | if need_login and not self.authorized:
23 | raise ClientLoginRequired("to use this method, you need to login first.")
24 |
25 | if headers:
26 | self.private.headers.update(headers)
27 |
28 | response = None
29 | try:
30 | if data:
31 | response = self.private.post(API_DOMAIN.format(endpoint), data=data, params=params)
32 | elif json:
33 | response = self.private.post(API_DOMAIN.format(endpoint), json=json, params=params)
34 | elif fields:
35 | multipart_data = MultipartEncoder(fields=fields)
36 | response = self.private.post(API_DOMAIN.format(endpoint), data=multipart_data,
37 | headers={
38 | **{'Content-Type': multipart_data.content_type},
39 | **self.private.headers
40 | }
41 | )
42 | else:
43 | response = self.private.get(API_DOMAIN.format(endpoint), params=params)
44 |
45 | except requests.exceptions.RequestException:
46 | raise ClientConnectionError()
47 |
48 | except Exception as e: # TO DO
49 | raise UnknownError(e)
50 |
51 | response_json = None
52 | try:
53 | response_json = response.json()
54 | except JSONDecodeError as e:
55 | raise ClientJSONDecodeError(f"JSONDecodeError {e} while opening {response.url}")
56 |
57 | if isinstance(response_json, dict) and response_json.get('success') == False and 'user_subscription' not in response.url:
58 | msg = response_json.get('msg')
59 | if msg:
60 | if 'is too long' in msg:
61 | raise LongString(msg)
62 | elif 'Invalid email' in msg:
63 | raise BadCredentials(msg)
64 | elif 'only contain letters' in msg:
65 | raise InvalidName(msg)
66 | elif 'already have that email' in msg:
67 | raise EmailExists(msg)
68 | elif 'username is not available' in msg:
69 | raise UsernameExists(msg)
70 | elif 'Name already exists.' == msg:
71 | raise DuplicateName('Playlist with this name already exists.')
72 | elif 'Playlist with this name already exists.' == msg:
73 | raise DuplicateName(msg)
74 | elif 'new password cannot be the same' in msg:
75 | raise DuplicatePassword(msg)
76 | elif 'need a valid email' in msg:
77 | raise InvalidEmail(msg)
78 | else: # TO DO
79 | raise UnknownError(msg)
80 |
81 | elif len(response_json) == 1:
82 | raise InvalidMediaId(f"An invalid media is was provided.")
83 | else:
84 | raise UnknownError(response_json)
85 |
86 | return response
87 |
88 | #### these are for like song/video/podcast/story:
89 | @classmethod
90 | def __like__(cls, self, id):
91 | if 'vote' in cls.__toggle_like__(self, id):
92 | return True
93 | else:
94 | cls.__toggle_like__(self, id)
95 | return False
96 |
97 | @classmethod
98 | def __unlike__(cls, self, id):
99 | if 'vote' in cls.__toggle_like__(self, id):
100 | cls.__toggle_like__(self, id)
101 | return False
102 | else:
103 | return True
104 |
105 | @classmethod
106 | def __toggle_like__(cls, self, id):
107 | return self.private_request(cls.LIKE_ENDPOINT,
108 | params=f'id={id}&type={cls.TYPE}&vote=5&remove=1',
109 | need_login=True).json()
--------------------------------------------------------------------------------
/radiojavanapi/mixins/search.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.private import PrivateRequest
2 | from radiojavanapi.models import SearchResults
3 | from radiojavanapi.extractors import extract_search_results
4 |
5 | from typing import List
6 | from urllib.parse import quote_plus
7 |
8 | class SearchMixin(PrivateRequest):
9 | def search(self, query: str) -> SearchResults:
10 | """
11 | Search and get results as SearchResults object.
12 |
13 | Arguments
14 | ----------
15 | query: Search query
16 |
17 | Returns
18 | -------
19 | SearchResults: An object of SearchResults type
20 |
21 | """
22 | response = self.private_request('search',
23 | params='query={}'.format(quote_plus(query))).json()
24 | return extract_search_results(response)
25 |
26 | def get_trending_searches(self) -> List[str]:
27 | """
28 | Get list of trending searches
29 |
30 | Returns
31 | -------
32 | List: A string list
33 |
34 | """
35 | return self.private_request('search_trending').json()
--------------------------------------------------------------------------------
/radiojavanapi/mixins/song.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.private import PrivateRequest
2 | from radiojavanapi.extractors import extract_song
3 | from radiojavanapi.models import Song
4 | from radiojavanapi.helper import url_to_id
5 |
6 | from typing import List, Union
7 | from pydantic import HttpUrl
8 |
9 | class SongMixin(PrivateRequest):
10 | LIKE_ENDPOINT = 'mp3_vote'
11 | TYPE = 'mp3'
12 |
13 | def get_song_by_url(self, url: HttpUrl) -> Song:
14 | """
15 | Get song info by site url (e.g. `play.radiojavan.com/song/...` or `play.radiojavan.com/redirect?r=radiojavan://mp3/...`)
16 |
17 | Arguments
18 | ----------
19 | url: Site url of song (mp3)
20 |
21 | Returns
22 | -------
23 | Song: An object of Song type
24 |
25 | """
26 | return self.get_song_by_id(url_to_id(url))
27 |
28 | def get_song_by_id(self, id: Union[int, str]) -> Song:
29 | """
30 | Get song info by id
31 |
32 | Arguments
33 | ----------
34 | id: A digit id of Song (mp3)
35 |
36 | Returns
37 | -------
38 | Song: An object of Song type
39 |
40 | """
41 | response = self.private_request('mp3',
42 | params=f'id={id}').json()
43 | return extract_song(response)
44 |
45 | def like_song(self, song_id: Union[int, str]) -> bool:
46 | """
47 | Like a song
48 |
49 | Arguments
50 | ----------
51 | song: A digit id of Song
52 |
53 | Returns
54 | -------
55 | bool: Returns false if song had been liked already
56 |
57 | """
58 | return SongMixin.__like__(self, song_id)
59 |
60 | def unlike_song(self, song_id: Union[int, str]) -> bool:
61 | """
62 | UnLike a song
63 |
64 | Arguments
65 | ----------
66 | song: A digit id of Song
67 |
68 | Returns
69 | -------
70 | bool: Returns false if song hadn't been liked before
71 |
72 | """
73 | return SongMixin.__unlike__(self, song_id)
74 |
75 | def liked_songs(self) -> List[Song]:
76 | """
77 | Get list of songs you had liked
78 |
79 | Returns
80 | -------
81 | List: A list of Song object
82 |
83 | """
84 | response = self.private_request('mp3s_liked', need_login=True).json()
85 | return [extract_song(song) for song in response]
--------------------------------------------------------------------------------
/radiojavanapi/mixins/story.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.private import PrivateRequest
2 | from radiojavanapi.extractors import extract_story
3 | from radiojavanapi.models import Story
4 | from radiojavanapi.helper import url_to_id
5 |
6 | from typing import Union
7 | from pydantic import HttpUrl
8 |
9 | class StoryMixin(PrivateRequest):
10 | LIKE_ENDPOINT = 'selfie_vote'
11 | TYPE = 'selfie'
12 |
13 | def get_story_by_url(self, url: HttpUrl) -> Story:
14 | """
15 | Get story info by site url (e.g. `play.radiojavan.com/story/1QVwK40x` or `rj.app/story/1QVwK40x`)
16 |
17 | Arguments
18 | ----------
19 | url: Site url of story (selfie)
20 |
21 | Returns
22 | -------
23 | Story: An object of Story type
24 |
25 | """
26 | return self.get_story_by_hash_id(url_to_id(url))
27 |
28 | def get_story_by_hash_id(self, hash_id: str) -> Story:
29 | """
30 | Get story info by hash id
31 |
32 | Arguments
33 | ----------
34 | hash_id: Unique hash id of story (selfie)
35 |
36 | Returns
37 | -------
38 | Story: An object of Story type
39 |
40 | """
41 | response = self.private_request('selfie',
42 | params=f'id={hash_id}').json()
43 | return extract_story(response)
44 |
45 | def like_story(self, story_id: Union[int, str]) -> bool:
46 | """
47 | Like a story (selfie)
48 |
49 | Arguments
50 | ----------
51 | story_id: A digit id of stroy
52 |
53 | Returns
54 | -------
55 | bool: Returns false if story had been liked already
56 |
57 | """
58 | return StoryMixin.__like__(self, story_id)
59 |
60 | def unlike_story(self, story_id: Union[int, str]) -> bool:
61 | """
62 | UnLike a story (selfie)
63 |
64 | Arguments
65 | ----------
66 | story_id: A digit id of stroy
67 |
68 | Returns
69 | -------
70 | bool: Returns false if story hadn't been liked before
71 |
72 | """
73 | return StoryMixin.__unlike__(self, story_id)
74 |
75 |
76 |
--------------------------------------------------------------------------------
/radiojavanapi/mixins/user.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.private import PrivateRequest
2 | from radiojavanapi.extractors import extract_short_user, extract_user
3 | from radiojavanapi.models import ShortUser, User
4 | from radiojavanapi.helper import url_to_id
5 |
6 | from pydantic import HttpUrl
7 | from typing import List
8 |
9 | class UserMixin(PrivateRequest):
10 | def get_user_by_url(self, url: HttpUrl) -> User:
11 | """
12 | Get user info by site url (e.g. `play.radiojavan.com/u/...` or `play.radiojavan.com/redirect?r=radiojavan://user/...`)
13 |
14 | Arguments
15 | ----------
16 | url: Site url of user
17 |
18 | Returns
19 | -------
20 | User: An object of User type
21 |
22 | """
23 | return self.get_user_by_username(url_to_id(url))
24 |
25 | def get_user_by_username(self, username: str) -> User:
26 | """
27 | Get user info by usename
28 |
29 | Arguments
30 | ----------
31 | username: username of user
32 |
33 | Returns
34 | -------
35 | User: An object of User type
36 |
37 | """
38 | response = self.private_request(
39 | 'user_profile', params=f'stats=1&username={username}').json()
40 | return extract_user(response)
41 |
42 | def get_user_followers(self, username: str) -> List[ShortUser]:
43 | """
44 | Get user followers list
45 |
46 | Arguments
47 | ----------
48 | username: username of user
49 |
50 | Returns
51 | -------
52 | List[ShortUser]: list of user followers as ShortUser object
53 |
54 | """
55 | response = self.private_request(
56 | 'user_followers_list', params=f'username={username}').json()
57 | return [extract_short_user(user) for user in response]
58 |
59 | def get_user_following(self, username: str) -> List[ShortUser]:
60 | """
61 | Get user following list
62 |
63 | Arguments
64 | ----------
65 | username: username of user
66 |
67 | Returns
68 | -------
69 | List[ShortUser]: list of user following as ShortUser object
70 |
71 | """
72 | response = self.private_request(
73 | 'user_following_list', params=f'username={username}').json()
74 | return [extract_short_user(user) for user in response]
75 |
76 | def follow_user(self, username: str) -> bool:
77 | """
78 | Follow a user
79 |
80 | Arguments
81 | ----------
82 | username: username of user
83 |
84 | Returns
85 | -------
86 | bool: RJ api result
87 |
88 | """
89 | response = self.private_request('user_follow',
90 | json={"username":username},
91 | need_login=True).json()
92 | return response['success'] == True
93 |
94 | def unfollow_user(self, username: str) -> bool:
95 | """
96 | UnFollow a user
97 |
98 | Arguments
99 | ----------
100 | username: username of user
101 |
102 | Returns
103 | -------
104 | bool: RJ api result
105 |
106 | """
107 | response = self.private_request('user_unfollow',
108 | json={"username":username},
109 | need_login=True).json()
110 | return response['success'] == True
111 |
--------------------------------------------------------------------------------
/radiojavanapi/mixins/video.py:
--------------------------------------------------------------------------------
1 | from radiojavanapi.mixins.private import PrivateRequest
2 | from radiojavanapi.extractors import extract_video
3 | from radiojavanapi.models import Video
4 | from radiojavanapi.helper import url_to_id
5 |
6 | from typing import List, Union
7 | from pydantic import HttpUrl
8 |
9 | class VideoMixin(PrivateRequest):
10 | LIKE_ENDPOINT = 'video_vote'
11 | TYPE = 'video'
12 |
13 | def get_video_by_url(self, url: HttpUrl) -> Video:
14 | """
15 | Get video info by site url (e.g. `play.radiojavan.com/video/...` or `play.radiojavan.com/redirect?r=radiojavan://video/...`)
16 |
17 | Arguments
18 | ----------
19 | url: Site url of video
20 |
21 | Returns
22 | -------
23 | Video: An object of Video type
24 |
25 | """
26 | return self.get_video_by_id(url_to_id(url))
27 |
28 | def get_video_by_id(self, id: Union[int, str]) -> Video:
29 | """
30 | Get video info by id
31 |
32 | Arguments
33 | ----------
34 | id: Unique id of video
35 |
36 | Returns
37 | -------
38 | Video: An object of Video type
39 |
40 | """
41 | response = self.private_request('video',
42 | params=f'id={id}').json()
43 | return extract_video(response)
44 |
45 | def like_video(self, video_id: Union[int, str]) -> bool:
46 | """
47 | Like a video
48 |
49 | Arguments
50 | ----------
51 | video_id: A digit id of Video
52 |
53 | Returns
54 | -------
55 | bool: Returns false if video had been liked already
56 |
57 | """
58 | return VideoMixin.__like__(self, video_id)
59 |
60 | def unlike_video(self, video_id: Union[int, str]) -> bool:
61 | """
62 | UnLike a video
63 |
64 | Arguments
65 | ----------
66 | video_id: A digit id of Video
67 |
68 | Returns
69 | -------
70 | bool: Returns false if video hadn't been liked before
71 |
72 | """
73 | return VideoMixin.__unlike__(self, video_id)
74 |
75 | def liked_videos(self) -> List[Video]:
76 | """
77 | Get list of videos you had liked
78 |
79 | Returns
80 | -------
81 | List: A list of Video object
82 |
83 | """
84 | response = self.private_request(
85 | 'videos_liked', need_login=True).json()
86 | return [extract_video(video) for video in response]
--------------------------------------------------------------------------------
/radiojavanapi/models.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional, Union
2 | from pydantic import BaseModel, HttpUrl
3 |
4 | class ShortUser(BaseModel):
5 | thumbnail: HttpUrl
6 | username: Optional[str]
7 | display_name: str
8 | share_link: Optional[HttpUrl]
9 |
10 | class ShortData(BaseModel):
11 | id: Union[int, str]
12 | artist: Optional[str]
13 | name: Optional[str]
14 | created_at: str
15 | permlink: Optional[str]
16 | photo: HttpUrl
17 | photo_player: Optional[HttpUrl]
18 | share_link: HttpUrl
19 | title: str
20 |
21 | class MyPlaylists(BaseModel):
22 | music_playlists: List[ShortData] = []
23 | video_playlists: List[ShortData] = []
24 |
25 | class Story(BaseModel):
26 | id: int
27 | hash_id: str
28 | title: str
29 | song: str
30 | song_id: int
31 | artist: str
32 | link: HttpUrl
33 | lq_link: HttpUrl
34 | hq_link: HttpUrl
35 | filename: str
36 | share_link: HttpUrl
37 | photo: HttpUrl
38 | thumbnail: HttpUrl
39 | is_verified: bool
40 | likes: str
41 | likes_pretty: str
42 | user: ShortUser
43 | location: str
44 | is_my_story: Optional[bool]
45 |
46 | class User(BaseModel):
47 | name: str
48 | firstname: str
49 | lastname: str
50 | display_name: Optional[str]
51 | username: Optional[str]
52 | share_link: HttpUrl
53 | display_name: Optional[str]
54 | bio: Optional[str]
55 | default_photo: HttpUrl
56 | default_thumbnail: HttpUrl
57 | photo: HttpUrl
58 | has_custom_photo: bool
59 | thumbnail: HttpUrl
60 | followers_count: str
61 | following_count: str
62 | has_subscription : bool
63 | is_verified: bool
64 | following: bool
65 | playlists_count: int
66 | songs_count: int
67 | artists_count: int
68 | artists_name: List[str] = []
69 | stories: List[Story] = []
70 | music_playlists: List[ShortData] = []
71 |
72 | class Account(BaseModel):
73 | name: str
74 | firstname: str
75 | lastname: str
76 | display_name: str
77 | username: str
78 | share_link: HttpUrl
79 | email: str
80 | bio: Optional[str]
81 | default_photo: HttpUrl
82 | default_thumbnail: HttpUrl
83 | has_subscription : bool
84 | has_custom_photo : bool
85 | is_verified: bool
86 | photo: HttpUrl
87 | thumbnail: HttpUrl
88 | followers_count: str
89 | following_count: str
90 | playlists_count: int
91 | songs_count: int
92 | artists_count : int
93 | stories: List[Story] = []
94 | artists_name: List[str] = []
95 |
96 | class RJBaseModel(BaseModel):
97 | created_at: str
98 | credit_tags: List[str] = []
99 | dislikes: int
100 | hq_hls: Optional[HttpUrl]
101 | hq_link: HttpUrl
102 | id: int
103 | likes: int
104 | link: HttpUrl
105 | lq_hls: Optional[HttpUrl]
106 | lq_link: HttpUrl
107 | permlink: str
108 | photo: HttpUrl
109 | photo_player: Optional[HttpUrl]
110 | share_link: HttpUrl
111 | title: str
112 |
113 | class Song(RJBaseModel):
114 | artist: str
115 | name: str
116 | item: Optional[str] # for playlist
117 | album: Optional[str]
118 | date: Optional[str]
119 | duration: float
120 | hls_link: Optional[HttpUrl]
121 | thumbnail: HttpUrl
122 | plays: int
123 | downloads: int
124 | credits: Optional[str]
125 | artist_tags: List[str] = []
126 | lyric: Optional[str]
127 | related_songs: List[ShortData] = []
128 | stories: List[Story] = []
129 |
130 | class Video(RJBaseModel):
131 | artist: str
132 | name: str
133 | item: Optional[str] # for playlist
134 | date: Optional[str]
135 | views: int
136 | artist_tags: List[str] = []
137 | related_videos: List[ShortData] = []
138 |
139 | class Podcast(RJBaseModel):
140 | date: str
141 | short_date: str
142 | is_talk: bool
143 | duration: float
144 | hls_link: Optional[HttpUrl]
145 | thumbnail: HttpUrl
146 | plays: int
147 | show_permlink: Optional[str]
148 | tracklist: Optional[str]
149 | related_podcasts: List[ShortData] = []
150 |
151 | class Artist(BaseModel):
152 | name: str
153 | background: HttpUrl
154 | photo: HttpUrl
155 | photo_player: HttpUrl
156 | photo_thumbnail: HttpUrl
157 | share_link: HttpUrl
158 | prereleases: List = []
159 | events: List = []
160 | photos: List = []
161 | latest_song: Optional[ShortData]
162 | followers_count: int
163 | following: bool
164 | plays: str
165 | songs: List[ShortData] = []
166 | albums: List[ShortData] = []
167 | videos: List[ShortData] = []
168 | podcasts: List[ShortData] = []
169 | music_playlists: List[ShortData] = []
170 |
171 |
172 | class SearchResults(BaseModel):
173 | query: str
174 | songs: List[ShortData] = []
175 | albums: List[ShortData] = []
176 | videos: List[ShortData] = []
177 | podcasts: List[ShortData] = []
178 | music_playlists: List[ShortData] = []
179 | shows: List[ShortData] = []
180 | users: List[ShortUser] = []
181 | artist_names: List[str] = []
182 |
183 |
184 | class MusicPlaylist(BaseModel):
185 | id: str
186 | title: str
187 | count: int
188 | created_at: str
189 | created_by: str
190 | last_updated_at: str
191 | share_link: HttpUrl
192 | followers: int
193 | following: Optional[bool] # login required
194 | sync: Optional[bool] # login required
195 | is_public: bool
196 | is_my_playlist: bool
197 | photo: HttpUrl
198 | has_custom_photo: bool
199 | photo_player: Optional[HttpUrl]
200 | thumbnail: HttpUrl
201 | songs: List[Song] = []
202 |
203 | class VideoPlaylist(BaseModel):
204 | id: str
205 | title: str
206 | count: int
207 | created_at: str
208 | last_updated_at: str
209 | share_link: HttpUrl
210 | is_my_playlist: bool
211 | photo: HttpUrl
212 | photo_player: Optional[HttpUrl]
213 | thumbnail: HttpUrl
214 | videos: List[Video] = []
215 |
216 | class Album(BaseModel):
217 | id: str
218 | created_at: str
219 | date: str
220 | tracks: List[Song] = []
221 | name: str
222 | artist: str
223 | share_link: HttpUrl
224 |
225 | class NotificationsStatus(BaseModel):
226 | artists_email: bool
227 | artists_push: bool
228 | events_push: bool
229 | music_email: bool
230 | music_push: bool
231 | playlists_followers_push: bool
232 | selfies_push: bool
233 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 | from setuptools import find_packages, setup
3 |
4 | HERE = pathlib.Path(__file__).parent
5 | README = (HERE / "README.md").read_text(encoding='utf-8')
6 |
7 | requirements = [
8 | 'requests==2.25.1',
9 | 'requests-toolbelt==0.9.1',
10 | 'PySocks==1.7.1',
11 | 'pydantic==1.8.1'
12 | ]
13 |
14 | setup(
15 | name='radiojavanapi',
16 | version='0.5.0',
17 | author='xHossein',
18 | license='MIT',
19 | url='https://github.com/xHossein/radiojavanapi',
20 | install_requires=requirements,
21 | keywords=['radiojavan private api','radiojavan-private-api','radiojavan api','radiojavan-api',
22 | 'rj api','rj-api','radiojavan','radio javan','radio-javan'
23 | ],
24 | description='Fast and effective RadioJavan API Wrapper',
25 | long_description=README,
26 | long_description_content_type='text/markdown',
27 | packages=find_packages(exclude=['*tests*']),
28 | python_requires=">=3.7",
29 | include_package_data=True,
30 | classifiers=[
31 | 'Development Status :: 4 - Beta',
32 | 'Intended Audience :: Developers',
33 | 'License :: OSI Approved :: MIT License',
34 | 'Topic :: Software Development :: Libraries :: Python Modules',
35 | 'Programming Language :: Python :: 3.7',
36 | 'Programming Language :: Python :: 3.8',
37 | 'Programming Language :: Python :: 3.9',
38 | 'Programming Language :: Python :: 3.10',
39 | ]
40 | )
--------------------------------------------------------------------------------