Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
rpakishore committed Dec 9, 2023
1 parent 1ddbc8e commit 1671310
Show file tree
Hide file tree
Showing 18 changed files with 388 additions and 500 deletions.
24 changes: 0 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
- [2.3.2. Development](#232-development)
- [3. Usage](#3-usage)
- [3.1. Development](#31-development)
- [4. Modules](#4-modules)
- [4.1. requests\_package.py](#41-requests_packagepy)
- [5. Roadmap](#5-roadmap)
- [6. FAQ](#6-faq)
- [7. License](#7-license)
Expand Down Expand Up @@ -89,10 +87,6 @@ To run this project, you will need to add the following environment variables to
Create the virutual environment and install dependencies

```bash
python -m venv .venv

.venv\Scripts\activate.bat

pip install flit
```

Expand Down Expand Up @@ -132,24 +126,6 @@ Use this space to tell a little more about your project and how it can be used.
4. Review the dependencies under `pyproject.toml` and remove as needed.
5. Remove unneeded dependencies from `src\<app_name>\`

## 4. Modules

### 4.1. requests_package.py

Includes frequently used requests packages, functions, classes and defaults
The following functions are defined in the `req` class

|Function Name| Purpose|
|-------------|--------|
|`randomize_header`|Randomize request headers by updating both referer and useragent|
|`change_useragent`|Change request useragent to random one|
|`change_referer`|Randomly set google.com as referer|
|`get_from_list`|Complete requests to a list of urls and return the list of responses|
|`get`|URL request with header randomization, timeout, proxy and retries builtin|
|`proxy_get_from_list`|Complete requests to a list of urls and return the list of responses using proxy ips|
|`proxy_get`|completes `get` request using proxies|
|`create_session`|Generate sessions object with adequate headers and adapters|

<!-- Roadmap -->
## 5. Roadmap

Expand Down
Binary file added database.pkl
Binary file not shown.
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ dependencies = [
"typer[all]", #cli_app.py
"icecream",
"slack_sdk", #Slack.py
"requests", #request_package
"ak_requests", #request_package
"beautifulsoup4", #request_package
"ffmpy",
"eyed3"
"eyed3",
"keyring","brotlipy"
]

[project.optional-dependencies]
test = [
"pytest",
"ipykernel",
"pandasgui"
"ipykernel"
]

[project.urls]
Expand Down
84 changes: 84 additions & 0 deletions src/jiosaavn/API/SaavnMe_Parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from ak_requests import RequestsSession
import brotli

from jiosaavn.file_parser import Song
import json

from jiosaavn.utils import log

class SaavnMe:
BASEURL: str = 'https://saavn.me/'
SESSION = RequestsSession()
log.info('SaavnMe Instance Initialized')

def __init__(self) -> None:
self.SESSION.MIN_REQUEST_GAP = 1.5 #To prevent Ratelimit in free API

def __str__(self) -> str:
return 'Instance of SaavnMe class for saavn.me parser'

def __repr__(self) -> str:
return 'SaavnMe()'

def playlist(self, id: int|str) -> list[Song]:
"""Provides a list of Song dataclass from the playlist id"""
log.info(f'Extracting Playlist Info with ID: {id}')
url: str = f'{self.BASEURL}playlists?id={id}'
_res = self.SESSION.get(url)
try:
data: dict = json.loads(_res.content)['data']
except Exception:
data: dict = json.loads(brotli.decompress(_res.content))['data']

log.debug(f'Playlist: {data.get("name")}\nSongCount:{data.get("songCount")}\nFollowers:{data.get("followerCount")}\nURL:{data.get("url")}')

return _parse_playlist_results(
song_list=data['songs']
)

def song(self, url: str) -> Song:
"""Returns Song dataclass from provided jiosaavn url"""
req_url: str = f'{self.BASEURL}songs?link={url}'
log.info(f'Extracting Song from URL: {url}')
data: dict = self.SESSION.get(req_url).json()['data']
return _parse_song_dict(song_dict=data)

def _parse_song_dict(song_dict: dict) -> Song:
# get media url
download_urls: list[dict] = song_dict['downloadUrl']
_currentkbps: int = 0
media_url: str = ''

for download_url in download_urls:
__curr = int(download_url['quality'].replace('kbps',''))
if __curr > _currentkbps:
media_url = download_url['link']
_currentkbps = __curr

#primary_artists
primary_artists = song_dict['primaryArtists'].split(', ')

# get image url
urls: list[dict] = song_dict['image']
_currentkbps: int = 0
image_url: str = ''

for url in urls:
__curr = int(url['quality'].split('x')[0])
if __curr > _currentkbps:
image_url = url['link']
_currentkbps = __curr

return Song(
song_id=song_dict['id'],
name=song_dict['name'],
album=song_dict['album']['name'],
media_url=media_url,
primary_artists=primary_artists,
artists=primary_artists,
year=int(song_dict['year']),
image_url=image_url
)

def _parse_playlist_results(song_list: list[dict]) -> list[Song]:
return [_parse_song_dict(song_dict) for song_dict in song_list]
1 change: 1 addition & 0 deletions src/jiosaavn/API/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .SaavnMe_Parser import SaavnMe
119 changes: 62 additions & 57 deletions src/jiosaavn/Slack.py
Original file line number Diff line number Diff line change
@@ -1,96 +1,101 @@
# Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk)
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from jiosaavn.credentials import getpwd

class Slack_instance:
from jiosaavn.utils import getpwd
from jiosaavn.utils import log

class Blocks:
def __init__(self) -> None:
self.DIVIDER = {"type": "divider"}

def TEXT(self, text: str, img_url: str='', img_alt: str ='') -> dict:
block = {
"type": "section",
"text": { "type": "mrkdwn", "text": text}
}

if img_url != '':
block["accessory"] = {
"type": "image",
"image_url": img_url,
"alt_text": img_alt
}
return block

def IMAGE(self, img_url: str, alt_txt: str='', text: str='') -> dict:
block: dict = {
"type": "image",
"image_url": img_url,
"alt_text": alt_txt
}

if text != '':
block['title'] = {
"type": "plain_text",
"text": text,
"emoji": True
}
return block

def HEADER(self, text: str) -> dict:
return {
"type": "header",
"text": {
"type": "plain_text",
"text": text,
"emoji": True
}
}

class Slack:
def __init__(self):
#GetSlack
self.client = WebClient(token=getpwd('Slack-pythonbot', 'token'))
self.BLOCK = Blocks()
log.info('Slack Instance Initialized')
return

# Destructor
def __del__(self):
return

def do_actions(self):
return 0

def __str__(self) -> str:
return "SlackAPI Instance"

def __repr__(self) -> str:
return "Slack()"

def channel_id(self, channel_name:str) -> str:
"""Returns channel id for the specified channel name
Args:
channel_name (str): Name of the slack channel
Returns:
str: Channel ID
"""
return getpwd('Slack-pythonbot', channel_name)

def init_block(self):
self.block = []
return

def msg(self, message:str, channel:str="python"):
def msg(self, message:str, channel:str="python") -> int:
"""Sends Slack message
Args:
message (str): Message to be sent
channel (str, optional): Slack channel to send the message to. Defaults to "#python".
"""
err = 0
try:
_ = self.client.chat_postMessage(
channel=self.channel_id(channel),
text=message)
log.debug('Slack message sent')
except SlackApiError as e:
# You will get a SlackApiError if "ok" is False
print(f'NG - Slack message not sent: {str(e)}')
log.error(f'NG - Slack message not sent: {str(e)}')
err = 1
return err


def add_text(self, text, image_url=None,image_alt_text=""):
"""Adds markdown element to block message
Args:
text (str): Text to display
image_url (str, optional): Image to display. Defaults to None.
image_alt_text (str, optional): Alt string for image. Defaults to "".
"""
if not image_url:
self.block.append(
{
"type": "section",
"text": { "type": "mrkdwn", "text": text}
})
else:
self.block.append(
{
"type": "section",
"text": {"type": "mrkdwn", "text": text},
"accessory": {
"type": "image",
"image_url": image_url,
"alt_text": image_alt_text
}
})

return

def add_divider(self):
"""Adds divider to the block message
"""
self.block.append({"type": "divider"})
return

def post_block(self, channel):
def post_block(self, channel: str, blocks: list[dict]):
"""Posts the currently constructed block to slack chat
Args:
channel (str): Channel name
"""
err = 0
try:
response = self.client.chat_postMessage(channel=self.channel_id(channel),blocks=self.block)
_ = self.client.chat_postMessage(channel=self.channel_id(channel),
blocks=blocks)
log.debug('Slack block sent successfully')
except SlackApiError as e:
# You will get a SlackApiError if "ok" is False
print(f'NG - Slack message not sent: {str(e)}')
log.error(f'NG - Slack message not sent: {str(e)}')
err = 1
return err
17 changes: 16 additions & 1 deletion src/jiosaavn/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
"Placeholder module info"
__version__ = "0.0.1"

from jiosaavn.debugger import *
from jiosaavn.utils import log, ic
from jiosaavn.main import JiosaavnDownload


log.info('Jiosaavn Module Initialized')

ic.disable()
ic.enable() # Comment this line out to enable debugger


if ic.enabled:
log.setLevel(10) #debug
else:
log.setLevel(20) #info

log.debug(f'Icecream Debugger: {ic.enabled}')
19 changes: 11 additions & 8 deletions src/jiosaavn/api_parser.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from dataclasses import dataclass
from jiosaavn.file_parser import Song
from jiosaavn.debugger import ic
from jiosaavn.request_package import Req
from pathlib import Path
from ak_requests import RequestsSession
from . import log, ic
ic.configureOutput(prefix=f'{Path(__file__).name} -> ')


class SaavnAPI:
session = Req()
session = RequestsSession()
def __init__(self, baseurl: str, port: int = 80):
self.baseurl = baseurl
self.port = port
Expand All @@ -20,14 +23,14 @@ def song(self, url: str) -> Song:
data = self.session.get(f"{self.url}/song/?query={url}").json()
return _song_from_json(data)

def playlist(self, url: str) -> tuple[Song]:
def playlist(self, url: str) -> list[Song]:
data = self.session.get(f"{self.url}/result/?query={url}").json()
return (_song_from_json(song) for song in data.get('songs'))
return [_song_from_json(song) for song in data.get('songs')]


def _song_from_json(data: dict) -> Song:
if type(data.get('artistMap')) == dict:
artists = list(data.get('artistMap').keys())
if isinstance(_artist_map:=data.get('artistMap'), dict):
artists = list(_artist_map.keys())
else:
artists = []

Expand All @@ -39,5 +42,5 @@ def _song_from_json(data: dict) -> Song:
primary_artists= data['primary_artists'].split(', '),
artists= artists,
year = int(data.get('year', 0)),
image_url= data.get('image')
image_url= data.get('image') # type: ignore
)
Loading

0 comments on commit 1671310

Please sign in to comment.