diff --git a/README.md b/README.md index 871a9a3..79116ff 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ XNXX API is an API for xnxx.com. It allows you to fetch information from videos ```python -from xnxx_api import Client, Quality, threaded, default, FFMPEG +from xnxx_api import Client # Initialize a Client object client = Client() @@ -37,7 +37,7 @@ print(video_object.title) print(video_object.likes) # Download the video -video_object.download(downloader=threaded, quality=Quality.BEST, path="your_output_path + filename") +video_object.download(downloader="threaded", quality="best", path="your_output_path + filename") # SEE DOCUMENTATION FOR MORE ``` @@ -48,6 +48,17 @@ video_object.download(downloader=threaded, quality=Quality.BEST, path="your_outp # Changelog See [Changelog](https://github.com/EchterAlsFake/xnxx_api/blob/master/README/Changelog.md) for more details. +# Support (Donations) +I am developing all my projects entirely for free. I do that because I have fun and I don't want +to charge 30€ like other people do. + +However, if you find my work useful, please consider donating something. A tiny amount such as 1€ +means a lot to me. + +Paypal: https://paypal.me/EchterAlsFake +
XMR (Monero): `46xL2reuanxZgFxXBBaoagiEJK9c7bL7aiwKNR15neyX2wUsX2QVzkeRMVG2Cro44qLUNYvsP1BQa12KPbNat2ML41nyEeq` + + # Contribution Do you see any issues or having some feature requests? Simply open an Issue or talk in the discussions. @@ -56,5 +67,4 @@ Pull requests are also welcome. # License Licensed under the LGPLv3 License - -Copyright (C) 2023–2024 Johannes Habel +
Copyright (C) 2023–2025 Johannes Habel diff --git a/README/Changelog.md b/README/Changelog.md index 083cd8c..f403c48 100644 --- a/README/Changelog.md +++ b/README/Changelog.md @@ -19,4 +19,11 @@ - fixed an issue with video loading # 1.4.1 -- added support for mode searching #2 \ No newline at end of file +- added support for mode searching #2 + +# 1.4.0 +- proxy support (See Documentation) +- updated to eaf_base_api v2 +- written tests for search and user objects +- removed headers, cuz they were broken +- type hinting \ No newline at end of file diff --git a/setup.py b/setup.py index e666e76..29923e7 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="xnxx_api", - version="1.4.3", + version="1.5.0", packages=find_packages(), install_requires=[ "requests", "bs4", "lxml", "ffmpeg-progress-yield", "eaf_base_api" diff --git a/xnxx_api/__init__.py b/xnxx_api/__init__.py index e136bdc..3c664cd 100644 --- a/xnxx_api/__init__.py +++ b/xnxx_api/__init__.py @@ -1,10 +1,10 @@ # xnxx_api/__init__.py __all__ = [ - "Client", "Core", "Quality", "Video", "Callback", "threaded", "default", "FFMPEG", + "Client", "BaseCore", "Video", "Callback", "errors", "consts", "search_filters", "category" ] # Public API from xnxx_api.py -from xnxx_api.xnxx_api import Client, Core, Quality, Video, Callback, threaded, default, FFMPEG +from xnxx_api.xnxx_api import Client, BaseCore, Video, Callback from xnxx_api.modules import errors, consts, category, search_filters \ No newline at end of file diff --git a/xnxx_api/modules/consts.py b/xnxx_api/modules/consts.py index 2ea8bfc..ce1ea27 100644 --- a/xnxx_api/modules/consts.py +++ b/xnxx_api/modules/consts.py @@ -3,23 +3,6 @@ # ROOT URLs ROOT_URL = "https://www.xnxx.com/" -HEADERS = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36", - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", - "Accept-Language": "en-US,en;q=0.9", - "Accept-Encoding": "gzip, deflate, br", - "DNT": "1", # Do Not Track request header - "Connection": "keep-alive", - "Upgrade-Insecure-Requests": "1", - "Sec-Fetch-Site": "none", - "Sec-Fetch-Mode": "navigate", - "Sec-Fetch-User": "?1", - "Sec-Fetch-Dest": "document", - "Host": "www.xnxx.com", - "Referer": "https://www.xnxx.com" -} - - # REGEX REGEX_VIDEO_CHECK = re.compile(r"xnxx.com/(.*?)") REGEX_VIDEO_TITLE = re.compile(r"html5player\.setVideoTitle\('([^']*)'\);") diff --git a/xnxx_api/tests/test_search.py b/xnxx_api/tests/test_search.py new file mode 100644 index 0000000..44fa3bf --- /dev/null +++ b/xnxx_api/tests/test_search.py @@ -0,0 +1,11 @@ +from ..xnxx_api import Client + +client = Client() +search = client.search("fortnite") + +def test_search(): + for idx, video in enumerate(search.videos): + assert isinstance(video.title, str) + + if idx == 3: + break \ No newline at end of file diff --git a/xnxx_api/tests/test_user.py b/xnxx_api/tests/test_user.py new file mode 100644 index 0000000..83b0920 --- /dev/null +++ b/xnxx_api/tests/test_user.py @@ -0,0 +1,19 @@ +from ..xnxx_api import Client + +client = Client() +user = client.get_user("https://www.xnxx.com/pornstar/cory-chase") +objects_video = ["title", "publish_date", "length", "author"] + + + +def test_video_views(): + assert isinstance(user.total_video_views, str) + assert user.total_videos > 0 + +def test_videos(): + for idx, video in enumerate(user.videos): + if idx == 3: + break + for object in objects_video: + assert isinstance(getattr(video, object), str) + diff --git a/xnxx_api/tests/test_video.py b/xnxx_api/tests/test_video.py index 912c6a6..9da7264 100644 --- a/xnxx_api/tests/test_video.py +++ b/xnxx_api/tests/test_video.py @@ -1,5 +1,4 @@ from ..xnxx_api import Video -from base_api.modules.quality import Quality url = "https://www.xnxx.com/video-1b9bufc9/die_zierliche_stieftochter_passt_kaum_in_den_mund_ihres_stiefvaters" # This will be the URL for all tests @@ -77,5 +76,8 @@ def test_content_url(): def test_get_segments(): - segments = list(video.get_segments(quality=Quality.BEST)) + segments = list(video.get_segments(quality="best")) assert len(segments) > 10 + +def test_download_low(): + assert video.download(quality="worst", downloader="threaded") is True \ No newline at end of file diff --git a/xnxx_api/xnxx_api.py b/xnxx_api/xnxx_api.py index a29f8ac..71eb02c 100644 --- a/xnxx_api/xnxx_api.py +++ b/xnxx_api/xnxx_api.py @@ -8,23 +8,31 @@ from .modules.errors import * from .modules.search_filters import * -import json +import os import html +import json +import logging import argparse -import os +import traceback +from typing import Union, Generator from bs4 import BeautifulSoup from functools import cached_property -from base_api import Core, Quality, threaded, default, FFMPEG, Callback, setup_api +from base_api import BaseCore, Callback + +core = BaseCore() +logging.basicConfig(format='%(name)s %(levelname)s %(asctime)s %(message)s', datefmt='%I:%M:%S %p') +logger = logging.getLogger("XNXX API") +logger.setLevel(logging.DEBUG) -base_qualities = ["250p", "360p", "480p", "720p", "1080p", "1440p", "2160p"] +def disable_logging() -> None: + logger.setLevel(logging.CRITICAL) class Video: def __init__(self, url): self.url = url self.available_m3u8_urls = None - self.available_qualities = None self.script_content = None self.html_content = None self.metadata_matches = None @@ -39,8 +47,8 @@ def __init__(self, url): self.get_metadata_matches() self.extract_json_from_html() - def get_base_html(self): - self.html_content = Core().get_content(url=self.url, headers=None, cookies=None).decode("utf-8") + def get_base_html(self) -> None: + self.html_content = core.fetch(url=self.url) @classmethod def is_desired_script(cls, tag): @@ -49,7 +57,7 @@ def is_desired_script(cls, tag): script_contents = ['html5player', 'setVideoTitle', 'setVideoUrlLow'] return all(content in tag.text for content in script_contents) - def get_metadata_matches(self): + def get_metadata_matches(self) -> None: soup = BeautifulSoup(self.html_content, 'html.parser') metadata_span = soup.find('span', class_='metadata') metadata_text = metadata_span.get_text(separator=' ', strip=True) @@ -57,7 +65,7 @@ def get_metadata_matches(self): # Use a regex to extract the desired strings self.metadata_matches = re.findall(r'(\d+min|\d+p|\d[\d.,]*\s*[views]*)', metadata_text) - def get_script_content(self): + def get_script_content(self) -> None: soup = BeautifulSoup(self.html_content, 'lxml') target_script = soup.find(self.is_desired_script) if target_script: @@ -66,7 +74,7 @@ def get_script_content(self): else: raise InvalidResponse("Couldn't extract JSON from HTML") - def extract_json_from_html(self): + def extract_json_from_html(self) -> None: soup = BeautifulSoup(self.html_content, 'lxml') # Find the