Eporner_API 1.9.6__tar.gz → 2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {eporner_api-1.9.6 → eporner_api-2.0}/PKG-INFO +1 -3
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/eporner_api.py +74 -41
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/tests/test_category.py +16 -13
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/tests/test_pornstar.py +13 -12
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/tests/test_search.py +29 -24
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/tests/test_video.py +6 -51
- {eporner_api-1.9.6 → eporner_api-2.0}/pyproject.toml +3 -3
- {eporner_api-1.9.6 → eporner_api-2.0}/LICENSE +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/README.md +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/__init__.py +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/modules/__init__.py +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/modules/consts.py +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/modules/errors.py +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/modules/locals.py +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/modules/progressbar.py +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/modules/sorting.py +0 -0
- {eporner_api-1.9.6 → eporner_api-2.0}/eporner_api/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Eporner_API
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0
|
|
4
4
|
Summary: A Python API for the Porn Site Eporner.com
|
|
5
5
|
Author: Johannes Habel
|
|
6
6
|
Author-email: Johannes Habel <EchterAlsFake@proton.me>
|
|
@@ -10,8 +10,6 @@ Classifier: Programming Language :: Python
|
|
|
10
10
|
Requires-Dist: bs4
|
|
11
11
|
Requires-Dist: eaf-base-api
|
|
12
12
|
Requires-Dist: lxml ; extra == 'full'
|
|
13
|
-
Requires-Dist: httpx[http2] ; extra == 'full'
|
|
14
|
-
Requires-Dist: httpx[socks] ; extra == 'full'
|
|
15
13
|
Requires-Python: >=3.9
|
|
16
14
|
Project-URL: Homepage, https://github.com/EchterAlsFake/EPorner_API
|
|
17
15
|
Provides-Extra: full
|
|
@@ -3,6 +3,7 @@ import html
|
|
|
3
3
|
import json
|
|
4
4
|
import os.path
|
|
5
5
|
import logging
|
|
6
|
+
import asyncio
|
|
6
7
|
import argparse
|
|
7
8
|
import traceback
|
|
8
9
|
import threading
|
|
@@ -33,7 +34,7 @@ from bs4 import BeautifulSoup
|
|
|
33
34
|
from urllib.parse import urljoin
|
|
34
35
|
from functools import cached_property
|
|
35
36
|
from base_api.modules.config import RuntimeConfig
|
|
36
|
-
from typing import Generator, Union, Optional, List
|
|
37
|
+
from typing import AsyncGenerator, Generator, Union, Optional, List
|
|
37
38
|
from base_api.base import BaseCore, setup_logger, Helper
|
|
38
39
|
|
|
39
40
|
"""
|
|
@@ -102,21 +103,27 @@ def _choose_quality_from_list(available: List[str | int], target: Union[str, int
|
|
|
102
103
|
|
|
103
104
|
|
|
104
105
|
class Video:
|
|
105
|
-
def __init__(self, url: str, enable_html_scraping: bool = True, core: Optional[BaseCore] = None):
|
|
106
|
+
def __init__(self, url: str, enable_html_scraping: bool = True, core: Optional[BaseCore] = None, html_content=None):
|
|
106
107
|
self.core = core
|
|
107
108
|
self.url = url
|
|
108
109
|
self.enable_html = enable_html_scraping
|
|
109
|
-
self.html_content =
|
|
110
|
+
self.html_content = html_content
|
|
110
111
|
self.logger = setup_logger(name="EPorner API - [Video]", log_file=None, level=logging.CRITICAL)
|
|
111
|
-
self.json_data =
|
|
112
|
+
self.json_data = {}
|
|
113
|
+
self.html_json_data = {}
|
|
114
|
+
|
|
115
|
+
async def init(self):
|
|
116
|
+
self.json_data = await self.get_raw_json_data()
|
|
112
117
|
if self.enable_html:
|
|
113
|
-
self.
|
|
118
|
+
if not self.html_content:
|
|
119
|
+
await self.request_html_content()
|
|
114
120
|
is_removed = REGEX_VIDEO_DISABLED.findall(self.html_content)
|
|
115
121
|
for _ in is_removed:
|
|
116
122
|
if _ == "deletedfile":
|
|
117
123
|
raise VideoDisabled("Video has been removed because of a Copyright claim")
|
|
118
124
|
|
|
119
125
|
self.html_json_data = self.extract_json_from_html()
|
|
126
|
+
return self
|
|
120
127
|
|
|
121
128
|
def enable_logging(self, log_file: str, level, log_ip: str = None, log_port: int = None):
|
|
122
129
|
self.logger = setup_logger(name="EPorner API - [Video]", log_file=log_file, level=level, http_ip=log_ip, http_port=log_port)
|
|
@@ -144,13 +151,13 @@ class Video:
|
|
|
144
151
|
else:
|
|
145
152
|
return self.url # Assuming this is a video ID (hopefully)
|
|
146
153
|
|
|
147
|
-
def
|
|
154
|
+
async def get_raw_json_data(self):
|
|
148
155
|
"""
|
|
149
156
|
Uses the V2 API to retrieve information from a video
|
|
150
157
|
:return:
|
|
151
158
|
"""
|
|
152
159
|
|
|
153
|
-
data = self.core.fetch(f"{ROOT_URL}{API_VIDEO_ID}?id={self.video_id}&thumbsize=medium&format=json")
|
|
160
|
+
data = await self.core.fetch(f"{ROOT_URL}{API_VIDEO_ID}?id={self.video_id}&thumbsize=medium&format=json")
|
|
154
161
|
parsed_data = json.loads(data)
|
|
155
162
|
return parsed_data
|
|
156
163
|
|
|
@@ -212,11 +219,11 @@ class Video:
|
|
|
212
219
|
The following methods are using HTML scraping. This is against the ToS from EPorner.com!
|
|
213
220
|
"""
|
|
214
221
|
|
|
215
|
-
def request_html_content(self):
|
|
222
|
+
async def request_html_content(self):
|
|
216
223
|
if not self.enable_html:
|
|
217
224
|
raise HTML_IS_DISABLED("HTML content is disabled! See Documentation for more details")
|
|
218
225
|
|
|
219
|
-
self.html_content = html.unescape(self.core.fetch(self.url))
|
|
226
|
+
self.html_content = html.unescape(await self.core.fetch(self.url))
|
|
220
227
|
|
|
221
228
|
|
|
222
229
|
def extract_json_from_html(self):
|
|
@@ -333,13 +340,7 @@ JSONDecodeError: I need your help to fix this error. Please report the URL you'v
|
|
|
333
340
|
self.logger.error("Couldn't find author. Please report this!")
|
|
334
341
|
return None
|
|
335
342
|
|
|
336
|
-
def
|
|
337
|
-
"""
|
|
338
|
-
Returns the direct download URL for a given quality (best/half/worst or a specific resolution).
|
|
339
|
-
:param quality: 'best', 'half', 'worst', or a specific resolution like '720', '720p', 1080, etc.
|
|
340
|
-
:param mode: The mode to filter links by (e.g., 'video')
|
|
341
|
-
:return: str
|
|
342
|
-
"""
|
|
343
|
+
def _direct_download_quality_map(self, mode) -> dict[int, str]:
|
|
343
344
|
if not self.enable_html:
|
|
344
345
|
raise HTML_IS_DISABLED("HTML content is disabled! See Documentation for more details")
|
|
345
346
|
|
|
@@ -369,6 +370,25 @@ JSONDecodeError: I need your help to fix this error. Please report the URL you'v
|
|
|
369
370
|
height = int(m.group(1)) # e.g., 1080
|
|
370
371
|
quality_to_url[height] = href # last one wins; order doesn't matter
|
|
371
372
|
|
|
373
|
+
return quality_to_url
|
|
374
|
+
|
|
375
|
+
def video_qualities(self, mode=Encoding.mp4_h264) -> list:
|
|
376
|
+
"""
|
|
377
|
+
Returns the available direct download qualities for a given mode.
|
|
378
|
+
:param mode: The mode to filter links by (e.g., 'video')
|
|
379
|
+
:return: list
|
|
380
|
+
"""
|
|
381
|
+
quality_to_url = self._direct_download_quality_map(mode)
|
|
382
|
+
return sorted(quality_to_url.keys())
|
|
383
|
+
|
|
384
|
+
def direct_download_link(self, quality, mode) -> str:
|
|
385
|
+
"""
|
|
386
|
+
Returns the direct download URL for a given quality (best/half/worst or a specific resolution).
|
|
387
|
+
:param quality: 'best', 'half', 'worst', or a specific resolution like '720', '720p', 1080, etc.
|
|
388
|
+
:param mode: The mode to filter links by (e.g., 'video')
|
|
389
|
+
:return: str
|
|
390
|
+
"""
|
|
391
|
+
quality_to_url = self._direct_download_quality_map(mode)
|
|
372
392
|
if not quality_to_url:
|
|
373
393
|
raise NotAvailable(f"No URLs available for mode '{mode}'")
|
|
374
394
|
|
|
@@ -384,7 +404,7 @@ JSONDecodeError: I need your help to fix this error. Please report the URL you'v
|
|
|
384
404
|
self.logger.info(f"Using direct download link: {full_url} ({chosen_height}p)")
|
|
385
405
|
return full_url
|
|
386
406
|
|
|
387
|
-
def download(self, quality, path, callback=None, mode=Encoding.mp4_h264, no_title=False, use_workaround=False,
|
|
407
|
+
async def download(self, quality, path, callback=None, mode=Encoding.mp4_h264, no_title=False, use_workaround=False,
|
|
388
408
|
stop_event: threading.Event = None):
|
|
389
409
|
if not self.enable_html:
|
|
390
410
|
raise HTML_IS_DISABLED("HTML content is disabled! See Documentation for more details")
|
|
@@ -395,13 +415,13 @@ JSONDecodeError: I need your help to fix this error. Please report the URL you'v
|
|
|
395
415
|
|
|
396
416
|
url = self.direct_download_link(quality, mode)
|
|
397
417
|
if use_workaround:
|
|
398
|
-
response_redirect_url = self.core.fetch(self.direct_download_link(quality, mode),
|
|
418
|
+
response_redirect_url = await self.core.fetch(self.direct_download_link(quality, mode),
|
|
399
419
|
allow_redirects=True, get_response=True) # Sometimes the site trolls me
|
|
400
420
|
|
|
401
421
|
url = response_redirect_url.url
|
|
402
422
|
|
|
403
423
|
try:
|
|
404
|
-
self.core.legacy_download(url=url, callback=callback, path=path, stop_event=stop_event)
|
|
424
|
+
await self.core.legacy_download(url=url, callback=callback, path=path, stop_event=stop_event)
|
|
405
425
|
return True
|
|
406
426
|
|
|
407
427
|
except Exception:
|
|
@@ -412,18 +432,23 @@ JSONDecodeError: I need your help to fix this error. Please report the URL you'v
|
|
|
412
432
|
|
|
413
433
|
|
|
414
434
|
class Pornstar(Helper):
|
|
415
|
-
def __init__(self, url: str, enable_html_scraping: bool = False, core: Optional[BaseCore] = None):
|
|
435
|
+
def __init__(self, url: str, enable_html_scraping: bool = False, core: Optional[BaseCore] = None, html_content=None):
|
|
416
436
|
super().__init__(core=core, video=Video)
|
|
417
437
|
self.core = core
|
|
418
438
|
self.url = url
|
|
419
439
|
self.enable_html_scraping = enable_html_scraping
|
|
420
440
|
self.logger = setup_logger(name="EPorner API - [Pornstar]", log_file=None, level=logging.CRITICAL)
|
|
421
|
-
self.html_content =
|
|
441
|
+
self.html_content = html_content
|
|
442
|
+
|
|
443
|
+
async def init(self):
|
|
444
|
+
if not self.html_content:
|
|
445
|
+
self.html_content = await self.core.fetch(self.url)
|
|
446
|
+
return self
|
|
422
447
|
|
|
423
448
|
def enable_logging(self, log_file: str, level, log_ip: str = None, log_port: int = None):
|
|
424
449
|
self.logger = setup_logger(name="EPorner API - [Pornstar]", log_file=log_file, level=level, http_ip=log_ip, http_port=log_port)
|
|
425
450
|
|
|
426
|
-
def videos(self, pages: int = 0, videos_concurrency: int = None, pages_concurrency: int = None) ->
|
|
451
|
+
async def videos(self, pages: int = 0, videos_concurrency: int = None, pages_concurrency: int = None) -> AsyncGenerator[Video, None]:
|
|
427
452
|
if pages == 0:
|
|
428
453
|
video_amount = str(self.video_amount).replace(",", "")
|
|
429
454
|
pages = round(int(video_amount)) / 37 # One page contains 37 videos
|
|
@@ -433,8 +458,9 @@ class Pornstar(Helper):
|
|
|
433
458
|
|
|
434
459
|
pages = round(pages) # Dont ask
|
|
435
460
|
page_urls = [urljoin(f"{self.url}/", str(page)) for page in range(1, pages + 1)]
|
|
436
|
-
|
|
437
|
-
videos_concurrency=videos_concurrency)
|
|
461
|
+
async for video in self.iterator(page_urls=page_urls, extractor=extractor, pages_concurrency=pages_concurrency,
|
|
462
|
+
videos_concurrency=videos_concurrency):
|
|
463
|
+
yield await video.init()
|
|
438
464
|
|
|
439
465
|
@cached_property
|
|
440
466
|
def name(self) -> str:
|
|
@@ -551,41 +577,45 @@ class Client(Helper):
|
|
|
551
577
|
def enable_logging(self, log_file: str, level, log_ip: str = None, log_port: int = None):
|
|
552
578
|
self.logger = setup_logger(name="EPorner API - [Client]", log_file=log_file, level=level, http_ip=log_ip, http_port=log_port)
|
|
553
579
|
|
|
554
|
-
def get_video(self, url: str, enable_html_scraping: bool = True) -> Video:
|
|
580
|
+
async def get_video(self, url: str, enable_html_scraping: bool = True) -> Video:
|
|
555
581
|
"""Returns the Video object for a given URL"""
|
|
556
582
|
self.logger.info(f"Returning video object for: {url} HTML Scraping -> {enable_html_scraping}")
|
|
557
|
-
|
|
583
|
+
video = Video(url, enable_html_scraping=enable_html_scraping, core=self.core)
|
|
584
|
+
return await video.init()
|
|
558
585
|
|
|
559
|
-
def search_videos(self, query: str, sorting_gay: Union[str, Gay], sorting_order: Union[str, Order],
|
|
586
|
+
async def search_videos(self, query: str, sorting_gay: Union[str, Gay], sorting_order: Union[str, Order],
|
|
560
587
|
sorting_low_quality: Union[str, LowQuality],
|
|
561
|
-
page: int, per_page: int, enable_html_scraping: bool = True) ->
|
|
588
|
+
page: int, per_page: int, enable_html_scraping: bool = True) -> AsyncGenerator[Video, None]:
|
|
562
589
|
|
|
563
|
-
response = self.core.fetch(f"{ROOT_URL}{API_SEARCH}?query={query}&per_page={per_page}&%page={page}"
|
|
590
|
+
response = await self.core.fetch(f"{ROOT_URL}{API_SEARCH}?query={query}&per_page={per_page}&%page={page}"
|
|
564
591
|
f"&thumbsize=medium&order={sorting_order}&gay={sorting_gay}&lq="
|
|
565
592
|
f"{sorting_low_quality}&format=json")
|
|
566
593
|
|
|
567
594
|
json_data = json.loads(response)
|
|
568
595
|
for video_ in json_data.get("videos", []): # Don't know why this works lmao
|
|
569
596
|
id_ = video_["url"]
|
|
570
|
-
|
|
597
|
+
video = Video(id_, enable_html_scraping, core=self.core)
|
|
598
|
+
yield await video.init()
|
|
571
599
|
|
|
572
|
-
def get_videos_by_category(self, category: Union[str, Category], enable_html_scraping: bool = False,
|
|
573
|
-
videos_concurrency: int = None, pages_concurrency: int = None) ->
|
|
600
|
+
async def get_videos_by_category(self, category: Union[str, Category], enable_html_scraping: bool = False,
|
|
601
|
+
videos_concurrency: int = None, pages_concurrency: int = None) -> AsyncGenerator[Video, None]:
|
|
574
602
|
|
|
575
603
|
page_urls = [f"{ROOT_URL}cat/{category}/{page}" for page in range(1, 100)]
|
|
576
604
|
|
|
577
605
|
videos_concurrency = videos_concurrency or self.core.config.videos_concurrency
|
|
578
606
|
pages_concurrency = pages_concurrency or self.core.config.pages_concurrency
|
|
579
|
-
|
|
580
|
-
pages_concurrency=pages_concurrency, extractor=extractor)
|
|
607
|
+
async for video in self.iterator(page_urls=page_urls, videos_concurrency=videos_concurrency,
|
|
608
|
+
pages_concurrency=pages_concurrency, extractor=extractor):
|
|
609
|
+
yield await video.init()
|
|
581
610
|
|
|
582
611
|
|
|
583
|
-
def get_pornstar(self, url: str, enable_html_scraping: bool = True) -> Pornstar:
|
|
612
|
+
async def get_pornstar(self, url: str, enable_html_scraping: bool = True) -> Pornstar:
|
|
584
613
|
self.logger.info(f"Returning Pornstar object for: {url} HTML Scraping -> {enable_html_scraping}")
|
|
585
|
-
|
|
614
|
+
pornstar = Pornstar(url, enable_html_scraping, core=self.core)
|
|
615
|
+
return await pornstar.init()
|
|
586
616
|
|
|
587
617
|
|
|
588
|
-
def
|
|
618
|
+
async def run_main():
|
|
589
619
|
parser = argparse.ArgumentParser(description="API Command Line Interface")
|
|
590
620
|
parser.add_argument("--download", metavar="URL (str)", type=str, help="URL to download from")
|
|
591
621
|
parser.add_argument("--quality", metavar="best,half,worst", type=str, help="The video quality (best,half,worst)",
|
|
@@ -602,8 +632,8 @@ def main():
|
|
|
602
632
|
|
|
603
633
|
if args.download:
|
|
604
634
|
client = Client()
|
|
605
|
-
video = client.get_video(args.download, enable_html_scraping=True)
|
|
606
|
-
video.download(quality=args.quality, path=args.output, no_title=no_title)
|
|
635
|
+
video = await client.get_video(args.download, enable_html_scraping=True)
|
|
636
|
+
await video.download(quality=args.quality, path=args.output, no_title=no_title)
|
|
607
637
|
|
|
608
638
|
if args.file:
|
|
609
639
|
videos = []
|
|
@@ -613,11 +643,14 @@ def main():
|
|
|
613
643
|
content = file.read().splitlines()
|
|
614
644
|
|
|
615
645
|
for url in content:
|
|
616
|
-
videos.append(client.get_video(url, enable_html_scraping=True))
|
|
646
|
+
videos.append(await client.get_video(url, enable_html_scraping=True))
|
|
617
647
|
|
|
618
648
|
for video in videos:
|
|
619
|
-
video.download(quality=args.quality, path=args.output, no_title=no_title)
|
|
649
|
+
await video.download(quality=args.quality, path=args.output, no_title=no_title)
|
|
620
650
|
|
|
621
651
|
|
|
652
|
+
def main():
|
|
653
|
+
asyncio.run(run_main())
|
|
654
|
+
|
|
622
655
|
if __name__ == "__main__":
|
|
623
656
|
main()
|
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import pytest
|
|
3
2
|
from ..eporner_api import Client, Category
|
|
4
3
|
from base_api import BaseCore
|
|
5
|
-
core = BaseCore()
|
|
6
|
-
core.config.pages_concurrency = 1
|
|
7
|
-
core.config.videos_concurrency = 1
|
|
8
|
-
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
@pytest.mark.asyncio
|
|
6
|
+
async def test_category():
|
|
7
|
+
core = BaseCore()
|
|
8
|
+
core.config.pages_concurrency = 1
|
|
9
|
+
core.config.videos_concurrency = 1
|
|
10
|
+
|
|
11
11
|
videos_1 = Client(core).get_videos_by_category(category=Category.JAPANESE)
|
|
12
12
|
videos_2 = Client(core).get_videos_by_category(category=Category.HD)
|
|
13
13
|
videos_3 = Client(core).get_videos_by_category(category=Category.BLONDE)
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
idx = 0
|
|
16
|
+
async for video in videos_1:
|
|
16
17
|
if idx == 3:
|
|
17
18
|
break
|
|
18
|
-
|
|
19
19
|
assert isinstance(video.title, str) and len(video.title) > 0
|
|
20
|
+
idx += 1
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
idx = 0
|
|
23
|
+
async for video in videos_2:
|
|
22
24
|
if idx == 3:
|
|
23
25
|
break
|
|
24
|
-
|
|
25
26
|
assert isinstance(video.title, str) and len(video.title) > 0
|
|
27
|
+
idx += 1
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
idx = 0
|
|
30
|
+
async for video in videos_3:
|
|
28
31
|
if idx == 3:
|
|
29
32
|
break
|
|
30
|
-
|
|
31
33
|
assert isinstance(video.title, str) and len(video.title) > 0
|
|
34
|
+
idx += 1
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
+
import pytest
|
|
1
2
|
from ..eporner_api import Client
|
|
2
|
-
import time
|
|
3
|
-
url = "https://www.eporner.com/pornstar/riley-reid/"
|
|
4
3
|
from base_api import BaseCore
|
|
5
|
-
core = BaseCore()
|
|
6
|
-
core.config.pages_concurrency = 1
|
|
7
|
-
core.config.videos_concurrency = 1
|
|
8
4
|
|
|
9
|
-
|
|
5
|
+
@pytest.mark.asyncio
|
|
6
|
+
async def test_pornstar():
|
|
7
|
+
url = "https://www.eporner.com/pornstar/riley-reid/"
|
|
8
|
+
core = BaseCore()
|
|
9
|
+
core.config.pages_concurrency = 1
|
|
10
|
+
core.config.videos_concurrency = 1
|
|
11
|
+
pornstar = await Client(core).get_pornstar(url, enable_html_scraping=True)
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
def test_videos():
|
|
13
13
|
videos = pornstar.videos(pages=1)
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
|
|
15
|
+
idx = 0
|
|
16
|
+
async for video in videos:
|
|
16
17
|
assert isinstance(video.title, str) and len(video.title) > 3
|
|
17
18
|
if idx == 5:
|
|
18
19
|
break
|
|
20
|
+
idx += 1
|
|
19
21
|
|
|
20
|
-
def test_information():
|
|
21
22
|
assert isinstance(pornstar.pornstar_rank, str) and len(pornstar.pornstar_rank) >= 1
|
|
22
23
|
assert isinstance(pornstar.aliases, list) and len(pornstar.aliases) > 1
|
|
23
24
|
assert isinstance(pornstar.biography, str) and len(pornstar.biography) > 10
|
|
24
|
-
assert isinstance(pornstar.age, str) and len(pornstar.age) >= 2
|
|
25
|
+
assert isinstance(pornstar.age, str) and len(pornstar.age) >= 2
|
|
25
26
|
assert isinstance(pornstar.cup, str) and len(pornstar.cup) >= 1
|
|
26
27
|
assert isinstance(pornstar.country, str) and len(pornstar.country) >= 2
|
|
27
28
|
assert isinstance(pornstar.weight, str) and len(pornstar.weight) >= 2
|
|
@@ -1,48 +1,53 @@
|
|
|
1
|
+
import pytest
|
|
1
2
|
from ..eporner_api import Client, Gay, Order, LowQuality
|
|
2
|
-
import time
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
query = "Mia Khalifa"
|
|
6
|
-
pages =
|
|
7
|
-
per_page =
|
|
6
|
+
pages = 1
|
|
7
|
+
per_page = 1
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def client():
|
|
11
|
+
return Client()
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
@pytest.mark.asyncio
|
|
14
|
+
async def test_search_1(client):
|
|
10
15
|
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)
|
|
11
|
-
for video in videos:
|
|
16
|
+
async for video in videos:
|
|
12
17
|
assert len(video.title) > 0
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
def test_search_2():
|
|
19
|
+
@pytest.mark.asyncio
|
|
20
|
+
async def test_search_2(client):
|
|
16
21
|
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)
|
|
17
|
-
for video in videos:
|
|
22
|
+
async for video in videos:
|
|
18
23
|
assert len(video.title) > 0
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
def test_search_3():
|
|
25
|
+
@pytest.mark.asyncio
|
|
26
|
+
async def test_search_3(client):
|
|
22
27
|
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)
|
|
23
|
-
for video in videos:
|
|
28
|
+
async for video in videos:
|
|
24
29
|
assert len(video.title) > 0
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
def test_search_4():
|
|
31
|
+
@pytest.mark.asyncio
|
|
32
|
+
async def test_search_4(client):
|
|
28
33
|
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)
|
|
29
|
-
for video in videos:
|
|
34
|
+
async for video in videos:
|
|
30
35
|
assert len(video.title) > 0
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
def test_search_5():
|
|
37
|
+
@pytest.mark.asyncio
|
|
38
|
+
async def test_search_5(client):
|
|
34
39
|
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)
|
|
35
|
-
for video in videos:
|
|
40
|
+
async for video in videos:
|
|
36
41
|
assert len(video.title) > 0
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
def test_search_6():
|
|
43
|
+
@pytest.mark.asyncio
|
|
44
|
+
async def test_search_6(client):
|
|
40
45
|
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)
|
|
41
|
-
for video in videos:
|
|
46
|
+
async for video in videos:
|
|
42
47
|
assert len(video.title) > 0
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
def test_search_7():
|
|
49
|
+
@pytest.mark.asyncio
|
|
50
|
+
async def test_search_7(client):
|
|
46
51
|
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)
|
|
47
|
-
for video in videos:
|
|
52
|
+
async for video in videos:
|
|
48
53
|
assert len(video.title) > 0
|
|
@@ -1,70 +1,26 @@
|
|
|
1
|
+
import pytest
|
|
1
2
|
from ..eporner_api import Client, Encoding, NotAvailable
|
|
2
|
-
import time
|
|
3
|
-
url = "https://www.eporner.com/video-bTwP6vsFj5U/human-anal-sex-toy/"
|
|
4
|
-
video = Client().get_video(url, enable_html_scraping=True)
|
|
5
|
-
time.sleep(5) # Lmao
|
|
6
3
|
|
|
7
|
-
|
|
4
|
+
@pytest.mark.asyncio
|
|
5
|
+
async def test_video():
|
|
6
|
+
url = "https://www.eporner.com/video-bTwP6vsFj5U/human-anal-sex-toy/"
|
|
7
|
+
video = await Client().get_video(url, enable_html_scraping=True)
|
|
8
8
|
assert isinstance(video.title, str) and len(video.title) > 0
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def test_video_id():
|
|
12
9
|
assert isinstance(video.video_id, str) and len(video.video_id) > 0
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_tags():
|
|
16
10
|
assert isinstance(video.tags, list) and len(video.tags) > 0
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def test_views():
|
|
20
11
|
assert isinstance(video.views, int) and video.views > 0
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def test_rate():
|
|
24
12
|
assert isinstance(video.rate, str) and len(video.rate) > 0
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_publish_date():
|
|
28
13
|
assert isinstance(video.publish_date, str) and len(video.publish_date) > 0
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_length_seconds():
|
|
32
|
-
assert isinstance(video.length, int) > 0
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def test_length_minutes():
|
|
14
|
+
assert isinstance(video.length, int) and video.length > 0
|
|
36
15
|
assert isinstance(video.length_minutes, str) and len(video.length_minutes) > 0
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def test_embed_url():
|
|
40
16
|
assert isinstance(video.embed_url, str) and len(video.embed_url) > 0
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def test_thumbnails():
|
|
44
17
|
assert isinstance(video.thumbnail, str) and len(video.thumbnail) > 0
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def test_bitrate():
|
|
48
18
|
assert isinstance(video.bitrate, str) and len(video.bitrate) > 0
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def test_source_video_url():
|
|
52
19
|
assert isinstance(video.source_video_url, str) and len(video.source_video_url) > 0
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def test_rating():
|
|
56
20
|
assert isinstance(video.rating, str) and len(video.rating) > 0
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def test_rating_count():
|
|
60
21
|
assert isinstance(video.rating_count, str) and len(video.rating_count) > 0
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def test_author():
|
|
64
22
|
assert isinstance(video.author, str) and len(video.author) > 0
|
|
65
23
|
|
|
66
|
-
|
|
67
|
-
def test_direct_download_url():
|
|
68
24
|
assert isinstance(video.direct_download_link(quality=2160, mode=Encoding.mp4_h264), str)
|
|
69
25
|
assert isinstance(video.direct_download_link(quality="half", mode=Encoding.mp4_h264), str)
|
|
70
26
|
assert isinstance(video.direct_download_link(quality="worst", mode=Encoding.mp4_h264), str)
|
|
@@ -72,6 +28,5 @@ def test_direct_download_url():
|
|
|
72
28
|
assert isinstance(video.direct_download_link(quality="best", mode=Encoding.av1), str)
|
|
73
29
|
assert isinstance(video.direct_download_link(quality="half", mode=Encoding.av1), str)
|
|
74
30
|
assert isinstance(video.direct_download_link(quality="worst", mode=Encoding.av1), str)
|
|
75
|
-
|
|
76
31
|
except NotAvailable:
|
|
77
32
|
pass
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "Eporner_API"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0"
|
|
8
8
|
description = "A Python API for the Porn Site Eporner.com"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.9" # 3.9 due to httpx requirements
|
|
@@ -15,14 +15,14 @@ authors = [
|
|
|
15
15
|
]
|
|
16
16
|
dependencies = [
|
|
17
17
|
"bs4",
|
|
18
|
-
"
|
|
18
|
+
"eaf-base-api",
|
|
19
19
|
]
|
|
20
20
|
classifiers = [
|
|
21
21
|
"Programming Language :: Python",
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
[project.optional-dependencies]
|
|
25
|
-
full = ["lxml"
|
|
25
|
+
full = ["lxml"]
|
|
26
26
|
|
|
27
27
|
[project.urls]
|
|
28
28
|
Homepage = "https://github.com/EchterAlsFake/EPorner_API"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|