├── .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 | [![PyPI version](https://badge.fury.io/py/radiojavanapi.svg)](https://badge.fury.io/py/radiojavanapi) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://github.com/xHossein/radiojavanapi/blob/master/LICENSE) 3 | [![Downloads](https://pepy.tech/badge/radiojavanapi)](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 | [![PyPI version](https://badge.fury.io/py/radiojavanapi.svg)](https://badge.fury.io/py/radiojavanapi) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://github.com/xHossein/radiojavanapi/blob/master/LICENSE) 5 | [![Downloads](https://pepy.tech/badge/radiojavanapi)](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() | | [Account](models?id=account) | Change profile data | YES 99 | account_notifications_update() | | `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() | | `str` | Create a music playlist and returns playlist id | YES 155 | create_video_playlist() | | `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() | | `bool` | Add a song to your music playlist | YES 161 | add_to_video_playlist() | | `bool` | Add a video to your video playlist | YES 162 | remove_from_music_playlist() | | `bool` | Remove a song from your music playlist | YES 163 | remove_from_video_playlist() | | `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 | ) --------------------------------------------------------------------------------