Add issue templates and CI workflows; remove obsolete files

- Created bug report and feature request templates for better issue tracking.
- Added a build workflow for automated package management and release.
- Removed outdated CodeQL analysis and Python publish workflows.
- Updated project metadata in pyproject.toml and README.md.
- Refactored torrent handling and site interaction modules.
This commit is contained in:
2025-05-23 16:42:27 +02:00
parent 38df01c21f
commit 8ddea25e5b
18 changed files with 349 additions and 249 deletions

View File

@@ -0,0 +1,34 @@
name: Bug Report
description: Report a bug in this project
labels:
- kind/bug
- triage
body:
- type: textarea
id: bug
attributes:
label: Describe the bug
description: |
A clear and concise description of what the bug is.
What did you expect to happen? What happened instead?
Include screenshots if applicable.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: |
A clear and concise description of how to reproduce the bug.
Include steps, code snippets, or screenshots if applicable.
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional information
description: |
Add any other context or screenshots about the bug here.

View File

@@ -0,0 +1,23 @@
name: Feature request
description: Suggest an idea for this project
labels:
- kind/feature
- triage
body:
- type: textarea
id: feature
attributes:
label: Describe the feature
description: |
A clear and concise description of what the feature is.
What is the problem it solves? What are you trying to accomplish?
Include screenshots if applicable.
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional information
description: |
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,88 @@
on:
workflow_dispatch:
inputs:
release_notes:
description: Release notes (use \n for newlines)
type: string
required: false
github_release:
description: 'Create Gitea Release'
default: true
type: boolean
bump:
description: 'Bump type'
required: true
default: 'patch'
type: choice
options:
- 'major'
- 'minor'
- 'patch'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv python install
- name: Set Git identity
run: |
git config user.name "Gitea CI"
git config user.email "ci@git.theprivateserver.de"
- name: Bump version
id: bump
run: |
uv tool install bump-my-version
uv tool run bump-my-version bump ${{ github.event.inputs.bump }}
# echo the version to github env, the version is shown by using uv tool run bump-my-version show current_version
echo "VERSION<<EOF" >> $GITHUB_ENV
echo "$(uv tool run bump-my-version show current_version)" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ github.ref }}
- name: Add release notes to environment
id: add_release_notes
run: |
echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
echo "${{ github.event.inputs.release_notes }}" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Build package
run: uv build
- name: Publish package
env:
USERNAME: ${{ github.repository_owner }}
run: uv publish --publish-url https://git.theprivateserver.de/api/packages/$USERNAME/pypi/ -t ${{ secrets.TOKEN }}
- name: Generate changelog
id: changelog
uses: metcalfc/changelog-generator@v4.6.2
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Get the changelog
run: |
cat << "EOF"
${{ steps.changelog.outputs.changelog }}
EOF
- name: Create release
id: create_release
if: ${{ github.event.inputs.github_release == 'true' }}
uses: softprops/action-gh-release@master
with:
tag_name: ${{ env.VERSION }}
release_name: Release ${{ env.VERSION }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
make_latest: true
files: |
dist/*
env:
GITHUB_TOKEN: ${{ secrets.TOKEN }}

View File

@@ -1,70 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '38 17 * * 0'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -1,39 +0,0 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Upload Python Package
on:
release:
types: [published]
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@v1.9.0
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}

View File

@@ -1,45 +0,0 @@
# How to contribute with this project
## Getting started
1. Star the repo, it will help me a lot.
2. Make a fork for you.
3. Clone the repo into your local machine.
4. Create a new branch for your changes.
5. Start hacking :-)
## Not familiarized with the Python workflow?
1. Be sure that you have Python 3 and virtualenv installed (if not, install them)
2. Create a new virtualenv
```
python -m virtualenv env -p python3
```
3. And activate it!
4. Now it's time to install the dependencies.
```
pip install -r requirements.txt
```
5. And now you're ready to hack.
## Hacking
1. Please, make sure your code is valid according the PEP8 specification
2. If you want to add a new feature, not planned yet, open an issue first.
3. Fix the issues tagged as bug, then we can fix the issues related with features and improvements.
4. Add comments to your code, don't be shy.
## Hacktoberfest plus
If this is your first time, follow this steps:
1. Go to `Getting started` and follow the instructions there.
2. Push your changes into your fork.
3. Open a PR across forks from your `dev` branch to parent's repo `dev` branch (NOT MASTER)
4. Wait for feedback
Happy coding!

View File

@@ -1,3 +1,8 @@
# Hard Fork
## TBD
# Original Repo
<p align="center">
<img src="https://github.com/JuanjoSalvador/NyaaPy/blob/master/nyaapy-logo.png?raw=true" />
</p>

View File

@@ -1,31 +0,0 @@
from enum import Enum
def json_to_class(data):
# We check if the data passed is a list or not
if isinstance(data, list):
object_list = []
for item in data:
object_list.append(Torrent(item))
# Return a list of Torrent objects
return object_list
else:
return Torrent(data)
# This deals with converting the dict to an object
class Torrent(object):
def __init__(self, my_dict):
for key in my_dict:
setattr(self, key, my_dict[key])
class TorrentSite(Enum):
"""
Contains torrent sites
"""
NYAASI = "https://nyaa.si"
SUKEBEINYAASI = "https://sukebei.nyaa.si"
NYAALAND = "https://nyaa.land"

View File

@@ -1,23 +1,18 @@
[tool.poetry]
name = "nyaapy"
version = "0.7"
description = "Unofficial Python wrapper for NyaaPantsu API and Nyaa.si"
authors = ["Juanjo Salvador <juanjosalvador@netc.eu>"]
[project]
name = "komsuite-nyaapy"
version = "0.1.0"
description = "A rewritten hard fork of the original NyaaPy library."
license = "MIT"
readme = "README.md"
authors = [{ name = "WorldTeacher", email = "coding_contact@pm.me" }]
requires-python = ">=3.13"
dependencies = [
"bencodepy>=0.9.5",
"lxml>=5.3.1",
"regex>=2024.11.6",
]
[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.32.3"
lxml = "^5.2.2"
[tool.poetry.group.dev.dependencies]
pytest = "^8.2.2"
black = "^24.4.2"
mypy = "^1.10.1"
types-requests = "^2.32.0.20240712"
lxml-stubs = "^0.5.1"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"

View File

@@ -1,3 +0,0 @@
requests>=2.20.0
beautifulsoup4==4.6.0
lxml

View File

@@ -1,2 +0,0 @@
[metadata]
description-file = README.md

View File

@@ -1,25 +0,0 @@
from setuptools import setup, find_packages # type: ignore
from os import path
currdir = path.abspath(path.dirname(__file__))
with open(path.join(currdir, "README.md"), encoding="utf-8") as f:
long_desc = f.read()
setup(
name="nyaapy",
version="0.6.3",
install_requires=[
"requests",
"lxml",
],
url="https://github.com/juanjosalvador/nyaapy",
long_description=long_desc,
long_description_content_type="text/markdown",
download_url=("https://github.com/juanjosalvador/" "nyaapy/archive/0.6.3.tar.gz"),
license="MIT",
author="Juanjo Salvador",
author_email="juanjosalvador@netc.eu",
description="Allows you to make requests on Nyaa.si and nyaa.pantsu.cat",
packages=find_packages(exclude=["tests"]),
zip_safe=False,
)

View File

@@ -0,0 +1,4 @@
from .modules.anime_site import AnimeTorrentSite
from .modules.torrent import TorrentSite
from .sites.nyaa import Nyaa, SukebeiNyaa
from .modules.torrent import Torrent

View File

@@ -1,11 +1,11 @@
import requests
from nyaapy import torrent
from nyaapy.parser import parse_nyaa, parse_single, parse_nyaa_rss
from komsuite_nyaapy.modules import torrent
from komsuite_nyaapy.modules.parser import parse_nyaa, parse_single, parse_nyaa_rss
class AnimeTorrentSite:
SITE = torrent.TorrentSite.NYAASI
URL = "https://nyaa.si"
URL = SITE
@classmethod
def last_uploads(self, number_of_results: int):
@@ -21,18 +21,20 @@ class AnimeTorrentSite:
return torrent.json_to_class(json_data)
@classmethod
def search(self, keyword: str, **kwargs):
def search(
self,
keyword: str,
category: int = 0,
subcategory: int = 0,
filters: int = 0,
page: int = 0,
sorting: str = "id",
order: str = "desc",
**kwargs,
):
base_url = self.URL
user = kwargs.get("user", None)
category = kwargs.get("category", 0)
subcategory = kwargs.get("subcategory", 0)
filters = kwargs.get("filters", 0)
page = kwargs.get("page", 0)
sorting = kwargs.get(
"sort", "id"
) # Sorting by id = sorting by date, this is the default.
order = kwargs.get("order", "desc")
user_uri = f"user/{user}" if user else ""
@@ -62,7 +64,6 @@ class AnimeTorrentSite:
if not user:
search_uri += "&page=rss"
http_response = requests.get(search_uri)
http_response.raise_for_status()

View File

@@ -1,6 +1,6 @@
from lxml import etree
from nyaapy.magnet import magnet_builder
from nyaapy.torrent import TorrentSite
from .magnet import magnet_builder
from .torrent import TorrentSite
def nyaa_categories(b):
@@ -115,7 +115,6 @@ def parse_nyaa(request_text, limit, site):
for td in tr.xpath("./td"):
for link in td.xpath("./a"):
href = link.attrib.get("href").split("/")[-1]
# Only caring about non-comment pages.

View File

@@ -0,0 +1,155 @@
from enum import Enum
from dataclasses import dataclass
from dataclasses import field
import os
import bencodepy
import regex
from typing import Optional
import loguru
import sys
log = loguru.logger
log.remove()
log.add("cli.log", rotation="1 week", retention="1 month")
log.add(sys.stdout, level="INFO")
def json_to_class(data):
# We check if the data passed is a list or not
if isinstance(data, list):
object_list = []
for item in data:
object_list.append(Torrent(**item))
# Return a list of Torrent objects
return object_list
else:
return [Torrent(**data)]
# This deals with converting the dict to an object
@dataclass
class Torrent:
id: int = 0
category: Optional[str] = None
category: Optional[str] = None
url: Optional[str] = None
name: Optional[str] = None
download_url: Optional[str] = None
magnet: Optional[str] = None
size: Optional[str] = None
seeders: int = 0
leechers: int = 0
completed_downloads: int = 0
date: Optional[str] = None
type: Optional[str] = None
volumes: list[int] = field(default_factory=list)
contents: list[str] = field(default_factory=list)
filetypes: list[str] = field(default_factory=list)
def __post_init__(self):
self.get_contents
self.seeders = int(self.seeders)
self.leechers = int(self.leechers) if self.leechers is not None else 0
@property
def filesizes(self):
data_count = self.size.split(" ")[0]
data_value = self.size.split(" ")[1]
mib = 1
kib = 2
gib = 3
return float(data_count) * (
1024
** (mib if data_value == "MiB" else kib if data_value == "KiB" else gib)
)
@property
def get_contents(self):
os.system(f"wget {self.download_url}> /dev/null 2>&1")
with open(f"{self.download_url.split('/')[-1]}", "rb") as f:
data = bencodepy.decode(f.read())
info = data[b"info"]
filetypes: list[str] = []
# For multi-file torrents
if b"files" in info:
files: list[str] = []
for file in info[b"files"]: # type: ignore
path_parts = [part.decode("utf-8") for part in file[b"path"]] # type: ignore
files.append("/".join(path_parts)) # type: ignore
filetypes.append(path_parts[-1].split(".")[-1]) # type: ignore
self.filetypes = list(set(filetypes))
self.contents = files
# check each file for the presence of r"^(.*?)\s(vol\.\s\d{2})|(v\d{2,3})"
volumes: list[int] = []
for file in files:
match = regex.findall(
r"(vol\.?\s*\d{1,3}|v\d{1,3}(?:[-v]\d{1,3})?)",
file,
regex.IGNORECASE,
)
if match:
match = (
"".join(match[0])
.strip()
.replace("vol", "")
.replace("v", "")
.replace(".", "")
)
if "-" in match:
match = match.split("-")
# if difference between numbers is greater than 1, add missing numbers to volumes
if int(match[1]) - int(match[0]) > 1:
for i in range(int(match[0]) - 1, int(match[1]) + 1):
volumes.append(i)
else:
volumes.append(int(match[0]))
volumes.append(int(match[1]))
else:
if match.isdigit():
volumes.append(int(match))
volumes = sorted(volumes)
self.volumes = volumes
# For single-file torrents
# elif b"name" in info:
# return [info[b"name"].decode("utf-8")]
else:
self.volumes = [0]
# log.debug("Filetypes: {}, Volumes: {}".format(self.filetypes, self.volumes)) #! enable for debug
os.remove(f"{self.download_url.split('/')[-1]}")
class TorrentSite(Enum):
"""
Contains torrent sites
"""
NYAASI = "https://nyaa.si"
SUKEBEINYAASI = "https://sukebei.nyaa.si"
NYAALAND = "https://nyaa.land"
@classmethod
def list_sites(cls):
return [site.value for site in cls]
@classmethod
def get_site(cls, site):
if isinstance(site, TorrentSite):
return site.value
for s in cls:
if s.value == site:
return s
return None
@classmethod
def get_site_url(cls, site):
return cls.site.value
@classmethod
def nyaa(cls):
return (cls.NYAASI, cls.NYAASI.value)

View File

@@ -0,0 +1,11 @@
from komsuite_nyaapy import AnimeTorrentSite, TorrentSite
class SukebeiNyaa(AnimeTorrentSite):
SITE = TorrentSite.SUKEBEINYAASI
URL = TorrentSite.get_site(SITE)
class Nyaa(AnimeTorrentSite):
SITE = TorrentSite.NYAASI
URL = TorrentSite.get_site(SITE)