Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions .github/workflows/module_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ on:
schedule:
- cron: '0 7,17 * * *'
workflow_dispatch:
workflow_run:
workflows: ["Upload Python Package"]
branches: [master]
types:
- completed


jobs:
build:
Expand All @@ -11,16 +17,15 @@ jobs:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
make reqs
- name: Run tests
run: |
make module-test
cd ./tests/test_module
pip install -r requirements.txt
pytest -v test.py
env:
NCORE_USERNAME: ${{ secrets.NCORE_USER }}
NCORE_PASSWORD: ${{ secrets.NCORE_PASS }}
Expand Down
19 changes: 10 additions & 9 deletions .github/workflows/publish_package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ on:
jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}

steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: '3.9'
python-version-file: '.python-version'
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Build and upload package
run: |
python -m pip install --upgrade twine build
python -m build --sdist --wheel
python -m twine upload dist/*
uv version --bump minor
uv build
uv publish
make git-tag
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
17 changes: 9 additions & 8 deletions .github/workflows/unit_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ jobs:
matrix:
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Set up Python 3
uses: actions/setup-python@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
make reqs
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Run lint
run: |
make lint
uv run --python ${{ matrix.python-version }} pylint src/ncoreparser
- name: Check code format
run: |
uv run --python ${{ matrix.python-version }} black --check .
- name: Perform tests
run: |
make test
uv run --python ${{ matrix.python-version }} pytest
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ ncoreparser.egg-info/
.env.sh
.env
*.torrent
.tox*
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.9
31 changes: 13 additions & 18 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
ACTIVATE = . .venv/bin/activate

define LOAD_ENV
@set -a; [ -f .env ] && source .env; set +a;
endef

.venv:
rm -rf .venv
python3 -m venv .venv
$(ACTIVATE) && pip install --upgrade pip

venv: .venv

reqs: .venv
$(ACTIVATE) && pip install .[dev]
install:
uv sync --dev

lint:
$(ACTIVATE) && pylint ncoreparser
$(ACTIVATE) && mypy ncoreparser
uv run pylint src/ncoreparser
uv run mypy src/ncoreparser

format:
$(ACTIVATE) && black .
uv run black .

test:
$(ACTIVATE) && tox
uv run pytest

module-test:
$(LOAD_ENV) $(ACTIVATE) && cd ./tests/test_module && NCORE_USERNAME="${NCORE_USERNAME}" NCORE_PASSWORD="${NCORE_PASSWORD}" RSS_URL="${RSS_URL}" tox
$(LOAD_ENV) \
NCORE_USERNAME="${NCORE_USERNAME}" \
NCORE_PASSWORD="${NCORE_PASSWORD}" \
RSS_URL="${RSS_URL}" \
cd ./tests/test_module && && pytest

manual-test:
$(LOAD_ENV) $(ACTIVATE) && python -m tests.manual --user ${NCORE_USERNAME} \
$(LOAD_ENV) uv run python -m tests.manual --user ${NCORE_USERNAME} \
--passw "${NCORE_PASSWORD}" --rss-feed "${RSS_URL}"

git-tag:
git tag v$(shell grep 'version' pyproject.toml | cut -d '"' -f2)
git tag v$(uv version -s)
git push --tags
23 changes: 9 additions & 14 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"


[project]
name = "ncoreparser"
version = "4.1.0"
version = "4.1.1"
description = "Package to download from ncore.pro"
authors = [
{ name="Aron Radics", email="aron.radics.jozsef@gmail.com" }
Expand All @@ -17,21 +12,21 @@ dependencies = [
]
requires-python = ">=3.9"


[project.urls]
Repository = "https://github.com/radaron/ncoreparser.git"


[project.optional-dependencies]
[dependency-groups]
dev = [
"pytest",
"pylint",
"black",
"mypy",
"tox",
"tox-gh"
]

[project.urls]
Repository = "https://github.com/radaron/ncoreparser.git"

[build-system]
requires = ["uv_build>=0.9.2,<0.10.0"]
build-backend = "uv_build"

[tool.black]
line-length = 120

Expand Down
File renamed without changes.
46 changes: 24 additions & 22 deletions ncoreparser/client.py → src/ncoreparser/client.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import os
import httpx
import sys
from typing import Dict, Optional
from typing_extensions import Any, Generator, Union # pylint: disable=no-name-in-module

import httpx

from ncoreparser.data import URLs, SearchParamType, SearchParamWhere, ParamSort, ParamSeq
from ncoreparser.error import NcoreConnectionError, NcoreCredentialError, NcoreDownloadError
from ncoreparser.parser import TorrentsPageParser, TorrenDetailParser, RssParser, ActivityParser, RecommendedParser
from ncoreparser.util import Size, check_login, extract_cookies_from_client, set_cookies_to_client
from ncoreparser.torrent import Torrent
from ncoreparser.types import SearchResult

if sys.version_info >= (3, 10):
from typing import Any, Generator, Union
else:
from typing_extensions import Any, Generator, Union # pylint: disable=no-name-in-module


class Client:
# pylint: disable=too-many-instance-attributes
def __init__(self, timeout: int = 1, cookies: Optional[Dict[str, str]] = None) -> None:
self._client = httpx.Client(
headers={"User-Agent": "python ncoreparser"}, timeout=timeout, follow_redirects=True
Expand All @@ -21,7 +29,7 @@ def __init__(self, timeout: int = 1, cookies: Optional[Dict[str, str]] = None) -
self._rss_parser = RssParser()
self._activity_parser = ActivityParser()
self._recommended_parser = RecommendedParser()
self._allowed_cookies = ['nick', 'pass', 'stilus', 'nyelv', 'PHPSESSID']
self._allowed_cookies = ["nick", "pass", "stilus", "nyelv", "PHPSESSID"]
if cookies:
set_cookies_to_client(self._client, cookies, self._allowed_cookies, URLs.COOKIE_DOMAIN.value)
if self._check_logged_in():
Expand All @@ -30,44 +38,38 @@ def __init__(self, timeout: int = 1, cookies: Optional[Dict[str, str]] = None) -
def _check_logged_in(self) -> bool:
try:
r = self._client.get(URLs.INDEX.value)
if 'login.php' in str(r.url) or '<title>nCore</title>' in r.text:
if "login.php" in str(r.url) or "<title>nCore</title>" in r.text:
return False
return True
except Exception:
except Exception: # pylint: disable=broad-except
return False

def login(self, username: str, password: str, twofactorcode: str = "") -> Dict[str, str]:
if self._logged_in and self._check_logged_in():
return extract_cookies_from_client(self._client, self._allowed_cookies, URLs.COOKIE_DOMAIN.value)
return extract_cookies_from_client(self._client, self._allowed_cookies)

self._client.cookies.clear()
self._logged_in = False

try:
login_data = {
"nev": username,
"pass": password,
"set_lang": "hu",
"submitted": "1",
"ne_leptessen_ki": "1"
}

login_data = {"nev": username, "pass": password, "set_lang": "hu", "submitted": "1", "ne_leptessen_ki": "1"}

if twofactorcode:
login_data["2factor"] = twofactorcode

r = self._client.post(URLs.LOGIN.value, data=login_data)
except Exception as e:
raise NcoreConnectionError(f"Error while performing post method to url '{URLs.LOGIN.value}'.") from e
if r.url != URLs.INDEX.value or '<title>nCore</title>' in r.text:

if r.url != URLs.INDEX.value or "<title>nCore</title>" in r.text:
self.logout()
error_msg = f"Error while login, check credentials for user: '{username}'"
if twofactorcode:
error_msg += ". Invalid 2FA code or wait 5 minutes between login attempts."
raise NcoreCredentialError(error_msg)

self._logged_in = True
return extract_cookies_from_client(self._client, self._allowed_cookies, URLs.COOKIE_DOMAIN.value)
return extract_cookies_from_client(self._client, self._allowed_cookies)

@check_login
# pylint: disable=too-many-arguments, too-many-positional-arguments
Expand Down Expand Up @@ -171,4 +173,4 @@ def download(self, torrent: Torrent, path: str, override: bool = False) -> str:
def logout(self) -> None:
self._client.cookies.clear()
self._client.close()
self._logged_in = False
self._logged_in = False
44 changes: 23 additions & 21 deletions ncoreparser/client_async.py → src/ncoreparser/client_async.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
# pylint: disable=duplicate-code

import os
import httpx
import sys
from typing import Dict, Optional
from typing_extensions import Any, AsyncGenerator, Union # pylint: disable=no-name-in-module

import httpx

from ncoreparser.data import URLs, SearchParamType, SearchParamWhere, ParamSort, ParamSeq
from ncoreparser.error import NcoreConnectionError, NcoreCredentialError, NcoreDownloadError
from ncoreparser.parser import TorrentsPageParser, TorrenDetailParser, RssParser, ActivityParser, RecommendedParser
from ncoreparser.util import Size, check_login, extract_cookies_from_client, set_cookies_to_client
from ncoreparser.torrent import Torrent
from ncoreparser.types import SearchResult

if sys.version_info >= (3, 10):
from typing import Any, AsyncGenerator, Union
else:
from typing_extensions import Any, AsyncGenerator, Union # pylint: disable=no-name-in-module


class AsyncClient:
# pylint: disable=too-many-instance-attributes
def __init__(self, timeout: int = 1, cookies: Optional[Dict[str, str]] = None) -> None:
self._client = httpx.AsyncClient(
headers={"User-Agent": "python ncoreparser"}, timeout=timeout, follow_redirects=True
Expand All @@ -23,52 +31,46 @@ def __init__(self, timeout: int = 1, cookies: Optional[Dict[str, str]] = None) -
self._rss_parser = RssParser()
self._activity_parser = ActivityParser()
self._recommended_parser = RecommendedParser()
self._allowed_cookies = ['nick', 'pass', 'stilus', 'nyelv', 'PHPSESSID']
self._allowed_cookies = ["nick", "pass", "stilus", "nyelv", "PHPSESSID"]
if cookies:
set_cookies_to_client(self._client, cookies, self._allowed_cookies, URLs.COOKIE_DOMAIN.value)
# Note: Cookie validation for async client happens on first operation since we can't await in __init__

async def _check_logged_in(self) -> bool:
try:
r = await self._client.get(URLs.INDEX.value)
if 'login.php' in str(r.url) or '<title>nCore</title>' in r.text:
if "login.php" in str(r.url) or "<title>nCore</title>" in r.text:
return False
return True
except Exception:
except Exception: # pylint: disable=broad-except
return False

async def login(self, username: str, password: str, twofactorcode: str = "") -> Dict[str, str]:
if self._logged_in and await self._check_logged_in():
return extract_cookies_from_client(self._client, self._allowed_cookies, URLs.COOKIE_DOMAIN.value)
return extract_cookies_from_client(self._client, self._allowed_cookies)

self._client.cookies.clear()
self._logged_in = False

try:
login_data = {
"nev": username,
"pass": password,
"set_lang": "hu",
"submitted": "1",
"ne_leptessen_ki": "1"
}

login_data = {"nev": username, "pass": password, "set_lang": "hu", "submitted": "1", "ne_leptessen_ki": "1"}

if twofactorcode:
login_data["2factor"] = twofactorcode

r = await self._client.post(URLs.LOGIN.value, data=login_data)
except Exception as e:
raise NcoreConnectionError(f"Error while performing post method to url '{URLs.LOGIN.value}'.") from e
if r.url != URLs.INDEX.value or '<title>nCore</title>' in r.text:

if r.url != URLs.INDEX.value or "<title>nCore</title>" in r.text:
await self.logout()
error_msg = f"Error while login, check credentials for user: '{username}'"
if twofactorcode:
error_msg += ". Invalid 2FA code or wait 5 minutes between login attempts."
raise NcoreCredentialError(error_msg)

self._logged_in = True
return extract_cookies_from_client(self._client, self._allowed_cookies, URLs.COOKIE_DOMAIN.value)
return extract_cookies_from_client(self._client, self._allowed_cookies)

@check_login
# pylint: disable=too-many-arguments, too-many-positional-arguments
Expand Down
2 changes: 1 addition & 1 deletion ncoreparser/data.py → src/ncoreparser/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,4 @@ class URLs(Enum):
)
DETAIL_PATTERN = TORRENTS_BASE + "?action=details&id={id}"
DOWNLOAD_LINK = "https://ncore.pro/torrents.php?action=download&id={id}&key={key}"
COOKIE_DOMAIN = "ncore.pro"
COOKIE_DOMAIN = "ncore.pro"
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading