Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
EchterAlsFake committed Feb 6, 2024
2 parents e886557 + 0460ab1 commit 5368eeb
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 20 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: EPorner API test

on: [push, pull_request]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.11
uses: actions/setup-python@v2
with:
python-version: 3.11
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest requests bs4 lxml
- name: Test with pytest
run: |
pytest
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<div align="center">
<a href="https://pepy.tech/project/Eporner-API"><img src="https://static.pepy.tech/badge/Eporner-API" alt="Downloads"></a>
<a href="https://github.com/EchterAlsFake/EPorner_API/workflows/"><img src="https://github.com/EchterAlsFake/EPorner_API/workflows/CodeQL/badge.svg" alt="CodeQL Analysis"/></a>
<a href="https://github.com/EchterAlsFake/EPorner_API/workflows/"><img src="https://github.com/EchterAlsFake/EPorner_API/actions/workflows/tests.yml/badge.svg" alt="API Tests"/></a>
</div>

# Description
Expand Down
9 changes: 8 additions & 1 deletion README/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@
e.g:

Quality.BEST would translate to "best"
<br>etc...
<br>etc...

# 1.3

- Changed JSON methods
- Fixed stuff
- Written Unit Tests
- /hdporn/ URls now also work
2 changes: 1 addition & 1 deletion README/Documentation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# EPorner Documentation

> - Version 1.2
> - Version 1.3
> - Author: Johannes Habel
> - Copryight (C) 2024
> - License: GPL 3
Expand Down
88 changes: 72 additions & 16 deletions eporner_api/eporner_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ def __init__(self, url, enable_html_scraping=False):
self.enable_html = enable_html_scraping
self.html_content = None
self.json_data = self.raw_json_data()

if self.enable_html:
self.request_html_content()
self.html_json_data = self.extract_json_from_html()
Expand All @@ -73,7 +72,12 @@ def video_id(self) -> str:
return video_id.group(1)

else:
raise InvalidURL("The URL is not valid. Couldn't extract ID!")
try:
video_id = REGEX_ID_ALTERNATE.search(self.url)
return video_id.group(1)

except Exception:
raise InvalidURL("The URL is not valid. Couldn't extract ID!")

else:
return self.url # Assuming this is a video ID (hopefully)
Expand Down Expand Up @@ -155,13 +159,34 @@ def extract_json_from_html(self):
raise HTML_IS_DISABLED("HTML content is disabled! See Documentation for more details")

soup = BeautifulSoup(self.html_content, 'lxml')
# Find the <script> tag with type="application/ld+json"
script_tag = soup.find('script', {'type': 'application/ld+json'})
script_tags = soup.find_all('script', {'type': 'application/ld+json'})

if script_tag:
json_text = script_tag.string.strip() # Get the content of the tag as a string
data = json.loads(json_text)
return data
combined_data = {}

for script in script_tags:
json_text = script.string.strip()
data = json.loads(json_text)
combined_data.update(data)
cleaned_dictionary = self.flatten_json(combined_data)
return cleaned_dictionary

def flatten_json(self, nested_json, parent_key='', sep='_'):
"""
Flatten a nested json dictionary. Duplicate keys will be overridden.
:param nested_json: The nested JSON dictionary to be flattened.
:param parent_key: The base key to use for the flattened keys.
:param sep: The separator between nested keys.
:return: A flattened dictionary.
"""
items = []
for k, v in nested_json.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(self.flatten_json(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)

@cached_property
def bitrate(self):
Expand Down Expand Up @@ -191,10 +216,29 @@ def rating(self):
:return: str
"""
if self.enable_html:
return self.html_json_data["ratingValue"]
try:
return self.html_json_data["aggregateRating_ratingValue"]

else:
return None
except KeyError:
raise NotAvailable("No rating available. This isn't an error!")

@cached_property
def likes(self):
"""
Returns the video likes
:return: str
"""
if self.enable_html:
return REGEX_VIDEO_LIKES.search(self.html_content).group(1)

@cached_property
def dislikes(self):
"""
Returns the video dislikes
:return:
"""
if self.enable_html:
return REGEX_VIDEO_DISLIKES.search(self.html_content).group(1)

@cached_property
def rating_count(self):
Expand All @@ -203,7 +247,7 @@ def rating_count(self):
:return: str
"""
if self.enable_html:
return self.html_json_data["ratingCount"]
return self.html_json_data["aggregateRating_ratingCount"]

else:
return None
Expand All @@ -215,7 +259,16 @@ def author(self):
:return: str
"""
if self.enable_html:
return REGEX_VIDEO_UPLOADER.search(self.html_content).group(1)
match = REGEX_VIDEO_UPLOADER.search(self.html_content)
if match:
if match.group(1) is None or match.group(1) == "":
match = REGEX_VIDEO_PORNSTAR.search(self.html_content)
return match.group(1)

else:
return match.group(1)



def direct_download_link(self, quality, mode) -> str:
"""
Expand Down Expand Up @@ -258,10 +311,13 @@ def direct_download_link(self, quality, mode) -> str:
for preference in quality_preferences[start_index:]:
for resolution, link in available_links:
if resolution == preference:
return link
return f"https://eporner.com/{link}"

# If no specific match is found, return None or the lowest available quality
return available_links[-1][1] if available_links else None
if len(available_links) <= 0:
raise NotAvailable("No available links for given quality / mode found. Not all videos support AV1")

return "https://eporner.com" + available_links[-1][1] if available_links else None

@classmethod
def fix_quality(cls, quality):
Expand All @@ -286,7 +342,7 @@ def download_video(self, quality, output_path, callback=None, mode=Encoding.mp4_
quality = self.fix_quality(quality)

session = requests.Session()
response_redirect_url = session.get(f"https://www.eporner.com{self.direct_download_link(quality, mode)}",
response_redirect_url = session.get(self.direct_download_link(quality, mode),
allow_redirects=False)

if 'Location' in response_redirect_url.headers:
Expand Down
6 changes: 5 additions & 1 deletion eporner_api/modules/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,9 @@

# REGEXES
REGEX_ID = re.compile("https://www.eporner.com/video-(.*?)/")
REGEX_ID_ALTERNATE = re.compile("hd-porn/(.*?)/")
REGEX_HTML_JSON = re.compile(r'<script type="application/ld\+json">\s*(\{.*?})\s*</script>')
REGEX_VIDEO_UPLOADER = re.compile(r'title="Uploader">(.*?)</a>')
REGEX_VIDEO_UPLOADER = re.compile(r'title="Uploader">(.*?)</a>')
REGEX_VIDEO_PORNSTAR = re.compile('<a href="/pornstar/(.*?)/">')
REGEX_VIDEO_LIKES = re.compile(r'<div class="likeup" onclick="EP\.video\.postVote\(\d+, [01], \'video\'\);"><i>\d+</i><small>(.*?)</small></div>')
REGEX_VIDEO_DISLIKES = re.compile(r'<div class="likedown" onclick="EP\.video\.postVote\(\d+, [01], \'video\'\);"><i>\d+</i><small>(\d+)</small></div>')
5 changes: 5 additions & 0 deletions eporner_api/modules/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ def __init__(self, msg):
class HTML_IS_DISABLED(Exception):
def __init__(self, msg):
self.msg = msg


class NotAvailable(Exception):
def __init__(self, msg):
self.msg = msg
Empty file added eporner_api/tests/__init__.py
Empty file.
48 changes: 48 additions & 0 deletions eporner_api/tests/test_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from ..eporner_api import Client, Gay, Order, LowQuality

client = Client()
query = "Mia Khalifa"
pages = 2
per_page = 10


def test_search_1():
videos = client.search_videos(query, page=pages, per_page=per_page, sorting_gay=Gay.exclude_gay_content, sorting_order=Order.top_rated, sorting_low_quality=LowQuality.exclude_low_quality_content)
for video in videos:
assert len(video.title) > 0


def test_search_2():
videos = client.search_videos(query, page=pages, per_page=per_page, sorting_gay=Gay.only_gay_content, sorting_order=Order.latest, sorting_low_quality=LowQuality.only_low_quality_content)
for video in videos:
assert len(video.title) > 0


def test_search_3():
videos = client.search_videos(query, page=pages, per_page=per_page, sorting_gay=Gay.include_gay_content, sorting_order=Order.longest, sorting_low_quality=LowQuality.include_low_quality_content)
for video in videos:
assert len(video.title) > 0


def test_search_4():
videos = client.search_videos(query, page=pages, per_page=pages, sorting_gay=Gay.exclude_gay_content, sorting_order=Order.shortest, sorting_low_quality=LowQuality.include_low_quality_content)
for video in videos:
assert len(video.title) > 0


def test_search_5():
videos = client.search_videos(query, page=pages, per_page=per_page, sorting_order=Gay.include_gay_content, sorting_gay=Order.top_weekly, sorting_low_quality=LowQuality.include_low_quality_content)
for video in videos:
assert len(video.title) > 0


def test_search_6():
videos = client.search_videos(query, page=pages, per_page=per_page, sorting_order=Order.most_popular, sorting_low_quality=LowQuality.include_low_quality_content, sorting_gay=Gay.only_gay_content)
for video in videos:
assert len(video.title) > 0


def test_search_7():
videos = client.search_videos(query, page=pages, per_page=per_page, sorting_gay=Gay.include_gay_content, sorting_order=Order.top_monthly, sorting_low_quality=LowQuality.include_low_quality_content)
for video in videos:
assert len(video.title) > 0
78 changes: 78 additions & 0 deletions eporner_api/tests/test_video.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from ..eporner_api import Client, Quality, Encoding, NotAvailable

url = "https://www.eporner.com/hd-porn/f8MuayGnGiS/Exciting-Moments-With-Lacey-Duvalle/"
video = Client.get_video(url, enable_html_scraping=True)


def test_title():
assert isinstance(video.title, str) and len(video.title) > 0


def test_video_id():
assert isinstance(video.video_id, str) and len(video.video_id) > 0


def test_keywords():
assert isinstance(video.keywords, list) and len(video.keywords) > 0


def test_views():
assert isinstance(video.views, int) and video.views > 0


def test_rate():
assert isinstance(video.rate, str) and len(video.rate) > 0


def test_publish_date():
assert isinstance(video.publish_date, str) and len(video.publish_date) > 0


def test_length_seconds():
assert isinstance(video.length_seconds, int) > 0


def test_length_minutes():
assert isinstance(video.length_minutes, str) and len(video.length_minutes) > 0


def test_embed_url():
assert isinstance(video.embed_url, str) and len(video.embed_url) > 0


def test_thumbnails():
assert isinstance(video.thumbnails, list) and len(video.thumbnails) > 0


def test_bitrate():
assert isinstance(video.bitrate, str) and len(video.bitrate) > 0


def test_source_video_url():
assert isinstance(video.source_video_url, str) and len(video.source_video_url) > 0


def test_rating():
assert isinstance(video.rating, str) and len(video.rating) > 0



def test_rating_count():
assert isinstance(video.rating_count, str) and len(video.rating_count) > 0


def test_author():
assert isinstance(video.author, str) and len(video.author) > 0


def test_direct_download_url():
assert isinstance(video.direct_download_link(quality=Quality.BEST, mode=Encoding.mp4_h264), str)
assert isinstance(video.direct_download_link(quality=Quality.HALF, mode=Encoding.mp4_h264), str)
assert isinstance(video.direct_download_link(quality=Quality.WORST, mode=Encoding.mp4_h264), str)
try:
assert isinstance(video.direct_download_link(quality=Quality.BEST, mode=Encoding.av1), str)
assert isinstance(video.direct_download_link(quality=Quality.HALF, mode=Encoding.av1), str)
assert isinstance(video.direct_download_link(quality=Quality.WORST, mode=Encoding.av1), str)

except NotAvailable:
pass
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="Eporner_API",
version="1.2",
version="1.3",
packages=find_packages(),
install_requires=[
"requests", "bs4", "lxml"
Expand Down

0 comments on commit 5368eeb

Please sign in to comment.