Move to poetry, mypy and black
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
parent
aa4bf1bbb2
commit
46977c7e38
@ -1,3 +0,0 @@
|
|||||||
.idea
|
|
||||||
.venv
|
|
||||||
.db
|
|
14
.drone.yml
14
.drone.yml
@ -3,11 +3,15 @@ name: default
|
|||||||
type: docker
|
type: docker
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: flake8
|
- name: lint
|
||||||
image: python:slim
|
image: python:3.7-slim
|
||||||
commands:
|
commands:
|
||||||
- pip install flake8
|
- pip install poetry
|
||||||
- flake8 pynyaata --ignore=E501
|
- poetry install
|
||||||
|
- poetry run flake8
|
||||||
|
- poetry run mypy .
|
||||||
|
- poetry run djlint .
|
||||||
|
|
||||||
- name: docker
|
- name: docker
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
@ -17,6 +21,7 @@ steps:
|
|||||||
from_secret: docker_username
|
from_secret: docker_username
|
||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: docker_password
|
||||||
|
|
||||||
- name: pypi
|
- name: pypi
|
||||||
image: plugins/pypi
|
image: plugins/pypi
|
||||||
settings:
|
settings:
|
||||||
@ -24,6 +29,7 @@ steps:
|
|||||||
from_secret: pypi_username
|
from_secret: pypi_username
|
||||||
password:
|
password:
|
||||||
from_secret: pypi_password
|
from_secret: pypi_password
|
||||||
|
skip_build: true
|
||||||
when:
|
when:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
|
162
.gitignore
vendored
162
.gitignore
vendored
@ -1,10 +1,154 @@
|
|||||||
.idea
|
# https://github.com/github/gitignore/blob/main/Python.gitignore
|
||||||
.venv
|
# Byte-compiled / optimized / DLL files
|
||||||
.vscode
|
__pycache__/
|
||||||
.db
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
.env
|
.env
|
||||||
dist
|
.venv
|
||||||
build
|
env/
|
||||||
*.egg*
|
venv/
|
||||||
__pycache__
|
ENV/
|
||||||
test.py
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
15
Dockerfile
15
Dockerfile
@ -1,7 +1,12 @@
|
|||||||
|
FROM python:3.10.6-slim as build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN pip install poetry && poetry build
|
||||||
|
|
||||||
FROM python:3.10.6-slim
|
FROM python:3.10.6-slim
|
||||||
|
|
||||||
COPY pynyaata /app/pynyaata
|
COPY --from=build /app/dist /tmp/dist
|
||||||
COPY requirements.txt *.py /app/
|
RUN pip install /tmp/dist/*.whl && rm -rf /tmp/dist
|
||||||
WORKDIR /app
|
|
||||||
RUN pip install -r requirements.txt
|
CMD ["pynyaata"]
|
||||||
CMD ["python", "run.py"]
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
include run.py
|
|
||||||
include get404.py
|
|
||||||
include README.md
|
|
||||||
include requirements.txt
|
|
||||||
recursive-include pynyaata/static *
|
|
||||||
recursive-include pynyaata/templates *
|
|
@ -27,7 +27,7 @@ After a good rewrite in Python, it's time to show it to the public, and here it
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Search on [Nyaa.si](https://nyaa.si/), [Nyaa.net (codename Pantsu)](https://nyaa.net/), [YggTorrent](https://duckduckgo.com/?q=yggtorrent) and [Anime-Ultime](http://www.anime-ultime.net/index-0-1)
|
* Search on [Nyaa.si](https://nyaa.si/), [YggTorrent](https://duckduckgo.com/?q=yggtorrent) and [Anime-Ultime](http://www.anime-ultime.net/index-0-1)
|
||||||
* Provide useful links to [TheTVDB](https://www.thetvdb.com/) and [Nautiljon](https://www.nautiljon.com/) during a search
|
* Provide useful links to [TheTVDB](https://www.thetvdb.com/) and [Nautiljon](https://www.nautiljon.com/) during a search
|
||||||
* Color official and bad links
|
* Color official and bad links
|
||||||
* Add seeded links to a database
|
* Add seeded links to a database
|
||||||
|
21
get404.py
21
get404.py
@ -1,21 +0,0 @@
|
|||||||
from pynyaata.connectors.core import curl_content
|
|
||||||
from pynyaata.models import AnimeLink
|
|
||||||
|
|
||||||
links = AnimeLink.query.all()
|
|
||||||
|
|
||||||
for link in links:
|
|
||||||
html = curl_content(link.link, debug=False, cloudflare=True)
|
|
||||||
|
|
||||||
if html['http_code'] != 200 and html['http_code'] != 500:
|
|
||||||
print('(%d) %s %s : %s' % (
|
|
||||||
html['http_code'],
|
|
||||||
link.title.name,
|
|
||||||
link.season,
|
|
||||||
link.link
|
|
||||||
))
|
|
||||||
elif 'darkgray' in str(html['output']):
|
|
||||||
print('(darkgray) %s %s : %s' % (
|
|
||||||
link.title.name,
|
|
||||||
link.season,
|
|
||||||
link.link
|
|
||||||
))
|
|
1727
poetry.lock
generated
Normal file
1727
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,23 @@
|
|||||||
from asyncio import get_event_loop, set_event_loop, SelectorEventLoop
|
from asyncio import SelectorEventLoop, get_event_loop, set_event_loop
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from operator import attrgetter, itemgetter
|
from operator import attrgetter, itemgetter
|
||||||
|
|
||||||
from flask import redirect, render_template, request, url_for, abort
|
from flask import abort, redirect, render_template, request, url_for
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .config import app, auth, ADMIN_USERNAME, ADMIN_PASSWORD, DB_ENABLED, APP_PORT, IS_DEBUG, TRANSMISSION_ENABLED
|
from .config import (
|
||||||
from .connectors import get_instance, run_all, Nyaa
|
ADMIN_PASSWORD,
|
||||||
|
ADMIN_USERNAME,
|
||||||
|
APP_PORT,
|
||||||
|
DB_ENABLED,
|
||||||
|
IS_DEBUG,
|
||||||
|
TRANSMISSION_ENABLED,
|
||||||
|
app,
|
||||||
|
auth,
|
||||||
|
)
|
||||||
|
from .connectors import Nyaa, get_instance, run_all
|
||||||
from .connectors.core import ConnectorLang, ConnectorReturn
|
from .connectors.core import ConnectorLang, ConnectorReturn
|
||||||
from .forms import SearchForm, DeleteForm, EditForm, FolderDeleteForm, FolderEditForm
|
from .forms import DeleteForm, EditForm, FolderDeleteForm, FolderEditForm, SearchForm
|
||||||
|
|
||||||
if DB_ENABLED:
|
if DB_ENABLED:
|
||||||
from .config import db
|
from .config import db
|
||||||
@ -29,7 +38,8 @@ def db_required(f):
|
|||||||
|
|
||||||
|
|
||||||
def clean_titles():
|
def clean_titles():
|
||||||
db.engine.execute("""
|
db.engine.execute(
|
||||||
|
"""
|
||||||
DELETE
|
DELETE
|
||||||
FROM anime_title
|
FROM anime_title
|
||||||
WHERE id IN (
|
WHERE id IN (
|
||||||
@ -38,7 +48,8 @@ WHERE id IN (
|
|||||||
LEFT JOIN anime_link ON anime_title.id = anime_link.title_id
|
LEFT JOIN anime_link ON anime_title.id = anime_link.title_id
|
||||||
WHERE anime_link.id IS NULL
|
WHERE anime_link.id IS NULL
|
||||||
)
|
)
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@auth.verify_password
|
@auth.verify_password
|
||||||
@ -46,9 +57,9 @@ def verify_password(username, password):
|
|||||||
return username == ADMIN_USERNAME and ADMIN_PASSWORD == password
|
return username == ADMIN_USERNAME and ADMIN_PASSWORD == password
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('boldify')
|
@app.template_filter("boldify")
|
||||||
def boldify(name):
|
def boldify(name):
|
||||||
query = request.args.get('q', '')
|
query = request.args.get("q", "")
|
||||||
name = utils.boldify(name, query)
|
name = utils.boldify(name, query)
|
||||||
if DB_ENABLED:
|
if DB_ENABLED:
|
||||||
for keyword in db.session.query(AnimeTitle.keyword.distinct()).all():
|
for keyword in db.session.query(AnimeTitle.keyword.distinct()).all():
|
||||||
@ -57,12 +68,12 @@ def boldify(name):
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('flagify')
|
@app.template_filter("flagify")
|
||||||
def flagify(is_vf):
|
def flagify(is_vf):
|
||||||
return ConnectorLang.FR.value if is_vf else ConnectorLang.JP.value
|
return ConnectorLang.FR.value if is_vf else ConnectorLang.JP.value
|
||||||
|
|
||||||
|
|
||||||
@app.template_filter('colorify')
|
@app.template_filter("colorify")
|
||||||
def colorify(model):
|
def colorify(model):
|
||||||
return get_instance(model.link, model.title.keyword).color
|
return get_instance(model.link, model.title.keyword).color
|
||||||
|
|
||||||
@ -72,53 +83,62 @@ def inject_user():
|
|||||||
return dict(db_disabled=not DB_ENABLED)
|
return dict(db_disabled=not DB_ENABLED)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route("/")
|
||||||
def home():
|
def home():
|
||||||
return render_template('layout.html', search_form=SearchForm(), title='Anime torrents search engine')
|
return render_template(
|
||||||
|
"layout.html", search_form=SearchForm(), title="Anime torrents search engine"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/search')
|
@app.route("/search")
|
||||||
def search():
|
def search():
|
||||||
query = request.args.get('q')
|
query = request.args.get("q")
|
||||||
if not query:
|
if not query:
|
||||||
return redirect(url_for('home'))
|
return redirect(url_for("home"))
|
||||||
|
|
||||||
set_event_loop(SelectorEventLoop())
|
set_event_loop(SelectorEventLoop())
|
||||||
torrents = get_event_loop().run_until_complete(run_all(query))
|
torrents = get_event_loop().run_until_complete(run_all(query))
|
||||||
return render_template('search.html', search_form=SearchForm(), connectors=torrents)
|
return render_template("search.html", search_form=SearchForm(), connectors=torrents)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/latest')
|
@app.route("/latest")
|
||||||
@app.route('/latest/<int:page>')
|
@app.route("/latest/<int:page>")
|
||||||
def latest(page=1):
|
def latest(page=1):
|
||||||
set_event_loop(SelectorEventLoop())
|
set_event_loop(SelectorEventLoop())
|
||||||
torrents = get_event_loop().run_until_complete(
|
torrents = get_event_loop().run_until_complete(
|
||||||
run_all('', return_type=ConnectorReturn.HISTORY, page=page)
|
run_all("", return_type=ConnectorReturn.HISTORY, page=page)
|
||||||
)
|
)
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for torrent in torrents:
|
for torrent in torrents:
|
||||||
results = results + torrent.data
|
results = results + torrent.data
|
||||||
for result in results:
|
for result in results:
|
||||||
result['self'] = get_instance(result['href'])
|
result["self"] = get_instance(result["href"])
|
||||||
results.sort(key=itemgetter('date'), reverse=True)
|
results.sort(key=itemgetter("date"), reverse=True)
|
||||||
|
|
||||||
return render_template('latest.html', search_form=SearchForm(), torrents=results, page=page)
|
return render_template(
|
||||||
|
"latest.html", search_form=SearchForm(), torrents=results, page=page
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/list')
|
@app.route("/list")
|
||||||
@app.route('/list/<url_filters>')
|
@app.route("/list/<url_filters>")
|
||||||
@db_required
|
@db_required
|
||||||
def list_animes(url_filters='nyaa,yggtorrent'):
|
def list_animes(url_filters="nyaa,yggtorrent"):
|
||||||
filters = None
|
filters = None
|
||||||
for i, to_filter in enumerate(url_filters.split(',')):
|
for i, to_filter in enumerate(url_filters.split(",")):
|
||||||
if not i:
|
if not i:
|
||||||
filters = AnimeLink.link.contains(to_filter)
|
filters = AnimeLink.link.contains(to_filter)
|
||||||
else:
|
else:
|
||||||
filters = filters | AnimeLink.link.contains(to_filter)
|
filters = filters | AnimeLink.link.contains(to_filter)
|
||||||
|
|
||||||
titles = db.session.query(AnimeTitle, AnimeLink).join(
|
titles = (
|
||||||
AnimeLink).filter(filters).order_by(AnimeTitle.name).all()
|
db.session.query(AnimeTitle, AnimeLink)
|
||||||
|
.join(AnimeLink)
|
||||||
|
.filter(filters)
|
||||||
|
.order_by(AnimeTitle.name)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
for title, link in titles:
|
for title, link in titles:
|
||||||
@ -127,10 +147,10 @@ def list_animes(url_filters='nyaa,yggtorrent'):
|
|||||||
else:
|
else:
|
||||||
results[title.id].append(link)
|
results[title.id].append(link)
|
||||||
|
|
||||||
return render_template('list.html', search_form=SearchForm(), titles=results)
|
return render_template("list.html", search_form=SearchForm(), titles=results)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admin', methods=['GET', 'POST'])
|
@app.route("/admin", methods=["GET", "POST"])
|
||||||
@db_required
|
@db_required
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def admin():
|
def admin():
|
||||||
@ -139,9 +159,9 @@ def admin():
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
link = AnimeLink.query.filter_by(id=int(form.id.data)).first()
|
link = AnimeLink.query.filter_by(id=int(form.id.data)).first()
|
||||||
if link:
|
if link:
|
||||||
form.message = '%s (%s) has been successfully deleted' % (
|
form.message = "%s (%s) has been successfully deleted" % (
|
||||||
link.title.name,
|
link.title.name,
|
||||||
link.season
|
link.season,
|
||||||
)
|
)
|
||||||
db.session.delete(link)
|
db.session.delete(link)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -152,19 +172,21 @@ def admin():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
else:
|
else:
|
||||||
form._errors = {
|
form._errors = {
|
||||||
'id': ['Id %s was not found in the database' % form.id.data]
|
"id": ["Id %s was not found in the database" % form.id.data]
|
||||||
}
|
}
|
||||||
|
|
||||||
folders = AnimeFolder.query.all()
|
folders = AnimeFolder.query.all()
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
for title in folder.titles:
|
for title in folder.titles:
|
||||||
title.links.sort(key=attrgetter('season'))
|
title.links.sort(key=attrgetter("season"))
|
||||||
folder.titles.sort(key=attrgetter('name'))
|
folder.titles.sort(key=attrgetter("name"))
|
||||||
|
|
||||||
return render_template('admin/list.html', search_form=SearchForm(), folders=folders, action_form=form)
|
return render_template(
|
||||||
|
"admin/list.html", search_form=SearchForm(), folders=folders, action_form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admin/folder', methods=['GET', 'POST'])
|
@app.route("/admin/folder", methods=["GET", "POST"])
|
||||||
@db_required
|
@db_required
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def folder_list():
|
def folder_list():
|
||||||
@ -173,31 +195,33 @@ def folder_list():
|
|||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
folder = AnimeFolder.query.filter_by(id=int(form.id.data)).first()
|
folder = AnimeFolder.query.filter_by(id=int(form.id.data)).first()
|
||||||
if folder:
|
if folder:
|
||||||
form.message = '%s has been successfully deleted' % folder.name
|
form.message = "%s has been successfully deleted" % folder.name
|
||||||
db.session.delete(folder)
|
db.session.delete(folder)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
else:
|
else:
|
||||||
form._errors = {
|
form._errors = {
|
||||||
'id': ['Id %s was not found in the database' % form.id.data]
|
"id": ["Id %s was not found in the database" % form.id.data]
|
||||||
}
|
}
|
||||||
|
|
||||||
folders = AnimeFolder.query.all()
|
folders = AnimeFolder.query.all()
|
||||||
|
|
||||||
return render_template('admin/folder/list.html', search_form=SearchForm(), folders=folders, action_form=form)
|
return render_template(
|
||||||
|
"admin/folder/list.html",
|
||||||
|
search_form=SearchForm(),
|
||||||
|
folders=folders,
|
||||||
|
action_form=form,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admin/folder/edit', methods=['GET', 'POST'])
|
@app.route("/admin/folder/edit", methods=["GET", "POST"])
|
||||||
@app.route('/admin/folder/edit/<int:folder_id>', methods=['GET', 'POST'])
|
@app.route("/admin/folder/edit/<int:folder_id>", methods=["GET", "POST"])
|
||||||
@db_required
|
@db_required
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def folder_edit(folder_id=None):
|
def folder_edit(folder_id=None):
|
||||||
folder = AnimeFolder.query.filter_by(id=folder_id).first()
|
folder = AnimeFolder.query.filter_by(id=folder_id).first()
|
||||||
folder = folder if folder else AnimeFolder()
|
folder = folder if folder else AnimeFolder()
|
||||||
form = FolderEditForm(
|
form = FolderEditForm(
|
||||||
request.form,
|
request.form, id=folder.id, name=folder.name, path=folder.path
|
||||||
id=folder.id,
|
|
||||||
name=folder.name,
|
|
||||||
path=folder.path
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
@ -206,13 +230,15 @@ def folder_edit(folder_id=None):
|
|||||||
folder.path = form.path.data
|
folder.path = form.path.data
|
||||||
db.session.add(folder)
|
db.session.add(folder)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect(url_for('folder_list'))
|
return redirect(url_for("folder_list"))
|
||||||
|
|
||||||
return render_template('admin/folder/edit.html', search_form=SearchForm(), action_form=form)
|
return render_template(
|
||||||
|
"admin/folder/edit.html", search_form=SearchForm(), action_form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/admin/edit', methods=['GET', 'POST'])
|
@app.route("/admin/edit", methods=["GET", "POST"])
|
||||||
@app.route('/admin/edit/<int:link_id>', methods=['GET', 'POST'])
|
@app.route("/admin/edit/<int:link_id>", methods=["GET", "POST"])
|
||||||
@db_required
|
@db_required
|
||||||
@auth.login_required
|
@auth.login_required
|
||||||
def admin_edit(link_id=None):
|
def admin_edit(link_id=None):
|
||||||
@ -228,9 +254,9 @@ def admin_edit(link_id=None):
|
|||||||
link=link.link,
|
link=link.link,
|
||||||
season=link.season,
|
season=link.season,
|
||||||
comment=link.comment,
|
comment=link.comment,
|
||||||
keyword=link.title.keyword if link.title else None
|
keyword=link.title.keyword if link.title else None,
|
||||||
)
|
)
|
||||||
form.folder.choices = [('', '')] + [(g.id, g.name) for g in folders]
|
form.folder.choices = [("", "")] + [(g.id, g.name) for g in folders]
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
# Instance for VF tag
|
# Instance for VF tag
|
||||||
@ -238,9 +264,9 @@ def admin_edit(link_id=None):
|
|||||||
|
|
||||||
# Title
|
# Title
|
||||||
title = AnimeTitle.query.filter_by(id=link.title_id).first()
|
title = AnimeTitle.query.filter_by(id=link.title_id).first()
|
||||||
title = title if title else AnimeTitle.query.filter_by(
|
title = (
|
||||||
name=form.name.data
|
title if title else AnimeTitle.query.filter_by(name=form.name.data).first()
|
||||||
).first()
|
)
|
||||||
title = title if title else AnimeTitle()
|
title = title if title else AnimeTitle()
|
||||||
title.folder_id = form.folder.data
|
title.folder_id = form.folder.data
|
||||||
title.name = form.name.data
|
title.name = form.name.data
|
||||||
@ -262,23 +288,21 @@ def admin_edit(link_id=None):
|
|||||||
|
|
||||||
# Transmission
|
# Transmission
|
||||||
if TRANSMISSION_ENABLED and isinstance(instance, Nyaa):
|
if TRANSMISSION_ENABLED and isinstance(instance, Nyaa):
|
||||||
if title.folder.path is not None and title.folder.path != '':
|
if title.folder.path is not None and title.folder.path != "":
|
||||||
download_url = link.link.replace(
|
download_url = link.link.replace("/view/", "/download/") + ".torrent"
|
||||||
'/view/',
|
torrent_path = "%s/%s" % (title.folder.path, title.name)
|
||||||
'/download/'
|
|
||||||
) + '.torrent'
|
|
||||||
torrent_path = '%s/%s' % (title.folder.path, title.name)
|
|
||||||
torrent = transmission.add_torrent(
|
torrent = transmission.add_torrent(
|
||||||
download_url,
|
download_url, download_dir=torrent_path
|
||||||
download_dir=torrent_path
|
|
||||||
)
|
)
|
||||||
transmission.move_torrent_data(torrent.id, torrent_path)
|
transmission.move_torrent_data(torrent.id, torrent_path)
|
||||||
transmission.start_torrent(torrent.id)
|
transmission.start_torrent(torrent.id)
|
||||||
|
|
||||||
return redirect(url_for('admin'))
|
return redirect(url_for("admin"))
|
||||||
|
|
||||||
return render_template('admin/edit.html', search_form=SearchForm(), folders=folders, action_form=form)
|
return render_template(
|
||||||
|
"admin/edit.html", search_form=SearchForm(), folders=folders, action_form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
app.run('0.0.0.0', APP_PORT, IS_DEBUG)
|
app.run("0.0.0.0", APP_PORT, IS_DEBUG)
|
||||||
|
@ -10,19 +10,23 @@ from transmission_rpc.client import Client
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
IS_DEBUG = environ.get('FLASK_ENV', 'production') == 'development'
|
IS_DEBUG = environ.get("FLASK_ENV", "production") == "development"
|
||||||
ADMIN_USERNAME = environ.get('ADMIN_USERNAME', 'admin')
|
ADMIN_USERNAME = environ.get("ADMIN_USERNAME", "admin")
|
||||||
ADMIN_PASSWORD = environ.get('ADMIN_PASSWORD', 'secret')
|
ADMIN_PASSWORD = environ.get("ADMIN_PASSWORD", "secret")
|
||||||
APP_PORT = int(environ.get('FLASK_PORT', 5000))
|
APP_PORT = int(environ.get("FLASK_PORT", 5000))
|
||||||
CACHE_TIMEOUT = int(environ.get('CACHE_TIMEOUT', 60 * 60))
|
CACHE_TIMEOUT = int(environ.get("CACHE_TIMEOUT", 60 * 60))
|
||||||
REQUESTS_TIMEOUT = int(environ.get('REQUESTS_TIMEOUT', 5))
|
REQUESTS_TIMEOUT = int(environ.get("REQUESTS_TIMEOUT", 5))
|
||||||
BLACKLIST_WORDS = environ.get('BLACKLIST_WORDS', '').split(',') if environ.get('BLACKLIST_WORDS', '') else []
|
BLACKLIST_WORDS = (
|
||||||
|
environ.get("BLACKLIST_WORDS", "").split(",")
|
||||||
|
if environ.get("BLACKLIST_WORDS", "")
|
||||||
|
else []
|
||||||
|
)
|
||||||
DB_ENABLED = False
|
DB_ENABLED = False
|
||||||
REDIS_ENABLED = False
|
REDIS_ENABLED = False
|
||||||
TRANSMISSION_ENABLED = False
|
TRANSMISSION_ENABLED = False
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.name = 'PyNyaaTa'
|
app.name = "PyNyaaTa"
|
||||||
app.debug = IS_DEBUG
|
app.debug = IS_DEBUG
|
||||||
app.secret_key = urandom(24).hex()
|
app.secret_key = urandom(24).hex()
|
||||||
app.url_map.strict_slashes = False
|
app.url_map.strict_slashes = False
|
||||||
@ -30,33 +34,31 @@ auth = HTTPBasicAuth()
|
|||||||
logging.basicConfig(level=(logging.DEBUG if IS_DEBUG else logging.INFO))
|
logging.basicConfig(level=(logging.DEBUG if IS_DEBUG else logging.INFO))
|
||||||
logger = logging.getLogger(app.name)
|
logger = logging.getLogger(app.name)
|
||||||
|
|
||||||
db_uri = environ.get('DATABASE_URI')
|
db_uri = environ.get("DATABASE_URI")
|
||||||
if db_uri:
|
if db_uri:
|
||||||
DB_ENABLED = True
|
DB_ENABLED = True
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = db_uri
|
app.config["SQLALCHEMY_DATABASE_URI"] = db_uri
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
|
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = True
|
||||||
app.config['SQLALCHEMY_ECHO'] = IS_DEBUG
|
app.config["SQLALCHEMY_ECHO"] = IS_DEBUG
|
||||||
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {"pool_recycle": 200}
|
||||||
'pool_recycle': 200
|
|
||||||
}
|
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
from .models import create_all
|
from .models import create_all
|
||||||
|
|
||||||
create_all()
|
create_all()
|
||||||
|
|
||||||
cache_host = environ.get('REDIS_SERVER')
|
cache_host = environ.get("REDIS_SERVER")
|
||||||
if cache_host:
|
if cache_host:
|
||||||
REDIS_ENABLED = True
|
REDIS_ENABLED = True
|
||||||
cache = Redis(cache_host)
|
cache = Redis(cache_host)
|
||||||
|
|
||||||
transmission_host = environ.get('TRANSMISSION_SERVER')
|
transmission_host = environ.get("TRANSMISSION_SERVER")
|
||||||
if transmission_host:
|
if transmission_host:
|
||||||
TRANSMISSION_ENABLED = True
|
TRANSMISSION_ENABLED = True
|
||||||
transmission_username = environ.get('TRANSMISSION_RPC_USERNAME')
|
transmission_username = environ.get("TRANSMISSION_RPC_USERNAME")
|
||||||
transmission_password = environ.get('TRANSMISSION_RPC_PASSWORD')
|
transmission_password = environ.get("TRANSMISSION_RPC_PASSWORD")
|
||||||
transmission = Client(
|
transmission = Client(
|
||||||
username=transmission_username,
|
username=transmission_username,
|
||||||
password=transmission_password,
|
password=transmission_password,
|
||||||
host=transmission_host,
|
host=transmission_host,
|
||||||
logger=logger
|
logger=logger,
|
||||||
)
|
)
|
||||||
|
@ -3,24 +3,26 @@ from asyncio import gather
|
|||||||
from .animeultime import AnimeUltime
|
from .animeultime import AnimeUltime
|
||||||
from .core import Other
|
from .core import Other
|
||||||
from .nyaa import Nyaa
|
from .nyaa import Nyaa
|
||||||
from .yggtorrent import YggTorrent, YggAnimation
|
from .yggtorrent import YggAnimation, YggTorrent
|
||||||
|
|
||||||
|
|
||||||
async def run_all(*args, **kwargs):
|
async def run_all(*args, **kwargs):
|
||||||
coroutines = [Nyaa(*args, **kwargs).run(),
|
coroutines = [
|
||||||
|
Nyaa(*args, **kwargs).run(),
|
||||||
AnimeUltime(*args, **kwargs).run(),
|
AnimeUltime(*args, **kwargs).run(),
|
||||||
YggTorrent(*args, **kwargs).run(),
|
YggTorrent(*args, **kwargs).run(),
|
||||||
YggAnimation(*args, **kwargs).run()]
|
YggAnimation(*args, **kwargs).run(),
|
||||||
|
]
|
||||||
|
|
||||||
return list(await gather(*coroutines))
|
return list(await gather(*coroutines))
|
||||||
|
|
||||||
|
|
||||||
def get_instance(url, query=''):
|
def get_instance(url, query=""):
|
||||||
if 'nyaa.si' in url:
|
if "nyaa.si" in url:
|
||||||
return Nyaa(query)
|
return Nyaa(query)
|
||||||
elif 'anime-ultime' in url:
|
elif "anime-ultime" in url:
|
||||||
return AnimeUltime(query)
|
return AnimeUltime(query)
|
||||||
elif 'ygg' in url:
|
elif "ygg" in url:
|
||||||
return YggTorrent(query)
|
return YggTorrent(query)
|
||||||
else:
|
else:
|
||||||
return Other(query)
|
return Other(query)
|
||||||
|
@ -2,80 +2,79 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from .core import ConnectorCore, ConnectorReturn, ConnectorCache, curl_content
|
from .core import ConnectorCache, ConnectorCore, ConnectorReturn, curl_content
|
||||||
from ..utils import parse_date, link_exist_in_db
|
from ..utils import link_exist_in_db, parse_date
|
||||||
|
|
||||||
|
|
||||||
class AnimeUltime(ConnectorCore):
|
class AnimeUltime(ConnectorCore):
|
||||||
color = 'is-warning'
|
color = "is-warning"
|
||||||
title = 'Anime-Ultime'
|
title = "Anime-Ultime"
|
||||||
favicon = 'animeultime.png'
|
favicon = "animeultime.png"
|
||||||
base_url = 'http://www.anime-ultime.net'
|
base_url = "http://www.anime-ultime.net"
|
||||||
is_light = True
|
is_light = True
|
||||||
|
|
||||||
def get_full_search_url(self):
|
def get_full_search_url(self):
|
||||||
from_date = ''
|
from_date = ""
|
||||||
sort_type = 'search'
|
sort_type = "search"
|
||||||
|
|
||||||
if self.return_type is ConnectorReturn.HISTORY:
|
if self.return_type is ConnectorReturn.HISTORY:
|
||||||
try:
|
try:
|
||||||
page_date = datetime.now() - timedelta((int(self.page) - 1) * 365 / 12)
|
page_date = datetime.now() - timedelta((int(self.page) - 1) * 365 / 12)
|
||||||
except OverflowError:
|
except OverflowError:
|
||||||
page_date = datetime.fromtimestamp(0)
|
page_date = datetime.fromtimestamp(0)
|
||||||
from_date = page_date.strftime('%m%Y')
|
from_date = page_date.strftime("%m%Y")
|
||||||
sort_type = 'history'
|
sort_type = "history"
|
||||||
|
|
||||||
return '%s/%s-0-1/%s' % (self.base_url, sort_type, from_date)
|
return "%s/%s-0-1/%s" % (self.base_url, sort_type, from_date)
|
||||||
|
|
||||||
@ConnectorCache.cache_data
|
@ConnectorCache.cache_data
|
||||||
def search(self):
|
def search(self):
|
||||||
response = curl_content(self.get_full_search_url(), {
|
response = curl_content(self.get_full_search_url(), {"search": self.query})
|
||||||
'search': self.query
|
|
||||||
})
|
|
||||||
|
|
||||||
if response['http_code'] == 200:
|
if response["http_code"] == 200:
|
||||||
html = BeautifulSoup(response['output'], 'html.parser')
|
html = BeautifulSoup(response["output"], "html.parser")
|
||||||
title = html.select('div.title')
|
title = html.select("div.title")
|
||||||
player = html.select('div.AUVideoPlayer')
|
player = html.select("div.AUVideoPlayer")
|
||||||
|
|
||||||
if len(title) > 0 and 'Recherche' in title[0].get_text():
|
if len(title) > 0 and "Recherche" in title[0].get_text():
|
||||||
trs = html.select('table.jtable tr')
|
trs = html.select("table.jtable tr")
|
||||||
|
|
||||||
for i, tr in enumerate(trs):
|
for i, tr in enumerate(trs):
|
||||||
if not i:
|
if not i:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tds = tr.findAll('td')
|
tds = tr.findAll("td")
|
||||||
|
|
||||||
if len(tds) < 2:
|
if len(tds) < 2:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
url = tds[0].a
|
url = tds[0].a
|
||||||
href = '%s/%s' % (self.base_url, url['href'])
|
href = "%s/%s" % (self.base_url, url["href"])
|
||||||
|
|
||||||
if not any(href == d['href'] for d in self.data):
|
if not any(href == d["href"] for d in self.data):
|
||||||
self.data.append({
|
self.data.append(
|
||||||
'vf': self.is_vf(),
|
{
|
||||||
'href': href,
|
"vf": self.is_vf(),
|
||||||
'name': url.get_text(),
|
"href": href,
|
||||||
'type': tds[1].get_text(),
|
"name": url.get_text(),
|
||||||
'class': self.color if link_exist_in_db(href) else ''
|
"type": tds[1].get_text(),
|
||||||
})
|
"class": self.color if link_exist_in_db(href) else "",
|
||||||
elif len(player) > 0:
|
}
|
||||||
name = html.select('h1')
|
|
||||||
ani_type = html.select('div.titre')
|
|
||||||
href = '%s/file-0-1/%s' % (
|
|
||||||
self.base_url,
|
|
||||||
player[0]['data-serie']
|
|
||||||
)
|
)
|
||||||
|
elif len(player) > 0:
|
||||||
|
name = html.select("h1")
|
||||||
|
ani_type = html.select("div.titre")
|
||||||
|
href = "%s/file-0-1/%s" % (self.base_url, player[0]["data-serie"])
|
||||||
|
|
||||||
self.data.append({
|
self.data.append(
|
||||||
'vf': self.is_vf(),
|
{
|
||||||
'href': href,
|
"vf": self.is_vf(),
|
||||||
'name': name[0].get_text(),
|
"href": href,
|
||||||
'type': ani_type[0].get_text().replace(':', ''),
|
"name": name[0].get_text(),
|
||||||
'class': self.color if link_exist_in_db(href) else ''
|
"type": ani_type[0].get_text().replace(":", ""),
|
||||||
})
|
"class": self.color if link_exist_in_db(href) else "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.on_error = False
|
self.on_error = False
|
||||||
|
|
||||||
@ -83,31 +82,33 @@ class AnimeUltime(ConnectorCore):
|
|||||||
def get_history(self):
|
def get_history(self):
|
||||||
response = curl_content(self.get_full_search_url())
|
response = curl_content(self.get_full_search_url())
|
||||||
|
|
||||||
if response['http_code'] == 200:
|
if response["http_code"] == 200:
|
||||||
html = BeautifulSoup(response['output'], 'html.parser')
|
html = BeautifulSoup(response["output"], "html.parser")
|
||||||
tables = html.select('table.jtable')
|
tables = html.select("table.jtable")
|
||||||
h3s = html.findAll('h3')
|
h3s = html.findAll("h3")
|
||||||
|
|
||||||
for i, table in enumerate(tables):
|
for i, table in enumerate(tables):
|
||||||
for j, tr in enumerate(table.findAll('tr')):
|
for j, tr in enumerate(table.findAll("tr")):
|
||||||
if not j:
|
if not j:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tds = tr.findAll('td')
|
tds = tr.findAll("td")
|
||||||
link = tds[0].a
|
link = tds[0].a
|
||||||
href = '%s/%s' % (self.base_url, link['href'])
|
href = "%s/%s" % (self.base_url, link["href"])
|
||||||
|
|
||||||
self.data.append({
|
self.data.append(
|
||||||
'vf': self.is_vf(),
|
{
|
||||||
'href': href,
|
"vf": self.is_vf(),
|
||||||
'name': link.get_text(),
|
"href": href,
|
||||||
'type': tds[4].get_text(),
|
"name": link.get_text(),
|
||||||
'date': parse_date(h3s[i].string[:-3], '%A %d %B %Y'),
|
"type": tds[4].get_text(),
|
||||||
'class': self.color if link_exist_in_db(href) else ''
|
"date": parse_date(h3s[i].string[:-3], "%A %d %B %Y"),
|
||||||
})
|
"class": self.color if link_exist_in_db(href) else "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.on_error = False
|
self.on_error = False
|
||||||
|
|
||||||
@ConnectorCache.cache_data
|
@ConnectorCache.cache_data
|
||||||
def is_vf(self, url=''):
|
def is_vf(self, url=""):
|
||||||
return False
|
return False
|
||||||
|
@ -3,11 +3,11 @@ from enum import Enum
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from json import dumps, loads
|
from json import dumps, loads
|
||||||
|
|
||||||
|
from redis.exceptions import RedisError
|
||||||
import requests
|
import requests
|
||||||
from requests import RequestException
|
from requests import RequestException
|
||||||
from redis.exceptions import RedisError
|
|
||||||
|
|
||||||
from ..config import CACHE_TIMEOUT, REQUESTS_TIMEOUT, logger, REDIS_ENABLED
|
from ..config import CACHE_TIMEOUT, REDIS_ENABLED, REQUESTS_TIMEOUT, logger
|
||||||
|
|
||||||
if REDIS_ENABLED:
|
if REDIS_ENABLED:
|
||||||
from ..config import cache
|
from ..config import cache
|
||||||
@ -21,8 +21,8 @@ class ConnectorReturn(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class ConnectorLang(Enum):
|
class ConnectorLang(Enum):
|
||||||
FR = '🇫🇷'
|
FR = "🇫🇷"
|
||||||
JP = '🇯🇵'
|
JP = "🇯🇵"
|
||||||
|
|
||||||
|
|
||||||
class Cache:
|
class Cache:
|
||||||
@ -30,11 +30,11 @@ class Cache:
|
|||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(*args, **kwds):
|
def wrapper(*args, **kwds):
|
||||||
connector = args[0]
|
connector = args[0]
|
||||||
key = 'pynyaata.%s.%s.%s.%s' % (
|
key = "pynyaata.%s.%s.%s.%s" % (
|
||||||
connector.__class__.__name__,
|
connector.__class__.__name__,
|
||||||
f.__name__,
|
f.__name__,
|
||||||
connector.query,
|
connector.query,
|
||||||
connector.page
|
connector.page,
|
||||||
)
|
)
|
||||||
|
|
||||||
if REDIS_ENABLED:
|
if REDIS_ENABLED:
|
||||||
@ -47,8 +47,8 @@ class Cache:
|
|||||||
|
|
||||||
if json:
|
if json:
|
||||||
data = loads(json)
|
data = loads(json)
|
||||||
connector.data = data['data']
|
connector.data = data["data"]
|
||||||
connector.is_more = data['is_more']
|
connector.is_more = data["is_more"]
|
||||||
connector.on_error = False
|
connector.on_error = False
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -56,10 +56,11 @@ class Cache:
|
|||||||
|
|
||||||
if not connector.on_error and REDIS_ENABLED:
|
if not connector.on_error and REDIS_ENABLED:
|
||||||
try:
|
try:
|
||||||
cache.set(key, dumps({
|
cache.set(
|
||||||
'data': connector.data,
|
key,
|
||||||
'is_more': connector.is_more
|
dumps({"data": connector.data, "is_more": connector.is_more}),
|
||||||
}), CACHE_TIMEOUT)
|
CACHE_TIMEOUT,
|
||||||
|
)
|
||||||
except RedisError:
|
except RedisError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -72,31 +73,24 @@ ConnectorCache = Cache()
|
|||||||
|
|
||||||
|
|
||||||
def curl_content(url, params=None, ajax=False, debug=True, cloudflare=False):
|
def curl_content(url, params=None, ajax=False, debug=True, cloudflare=False):
|
||||||
output = ''
|
output = ""
|
||||||
http_code = 500
|
http_code = 500
|
||||||
method = 'post' if (params is not None) else 'get'
|
method = "post" if (params is not None) else "get"
|
||||||
headers = {}
|
headers = {}
|
||||||
|
|
||||||
if ajax:
|
if ajax:
|
||||||
headers['X-Requested-With'] = 'XMLHttpRequest'
|
headers["X-Requested-With"] = "XMLHttpRequest"
|
||||||
|
|
||||||
if cloudflare:
|
if cloudflare:
|
||||||
headers['User-Agent'] = 'Googlebot/2.1 (+http://www.google.com/bot.html)'
|
headers["User-Agent"] = "Googlebot/2.1 (+http://www.google.com/bot.html)"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if method == 'post':
|
if method == "post":
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
url,
|
url, params, timeout=REQUESTS_TIMEOUT, headers=headers
|
||||||
params,
|
|
||||||
timeout=REQUESTS_TIMEOUT,
|
|
||||||
headers=headers
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
response = requests.get(
|
response = requests.get(url, timeout=REQUESTS_TIMEOUT, headers=headers)
|
||||||
url,
|
|
||||||
timeout=REQUESTS_TIMEOUT,
|
|
||||||
headers=headers
|
|
||||||
)
|
|
||||||
|
|
||||||
output = response.text
|
output = response.text
|
||||||
http_code = response.status_code
|
http_code = response.status_code
|
||||||
@ -104,7 +98,7 @@ def curl_content(url, params=None, ajax=False, debug=True, cloudflare=False):
|
|||||||
if debug:
|
if debug:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
||||||
return {'http_code': http_code, 'output': output}
|
return {"http_code": http_code, "output": output}
|
||||||
|
|
||||||
|
|
||||||
class ConnectorCore(ABC):
|
class ConnectorCore(ABC):
|
||||||
@ -167,10 +161,10 @@ class ConnectorCore(ABC):
|
|||||||
|
|
||||||
|
|
||||||
class Other(ConnectorCore):
|
class Other(ConnectorCore):
|
||||||
color = 'is-danger'
|
color = "is-danger"
|
||||||
title = 'Other'
|
title = "Other"
|
||||||
favicon = 'blank.png'
|
favicon = "blank.png"
|
||||||
base_url = ''
|
base_url = ""
|
||||||
is_light = True
|
is_light = True
|
||||||
|
|
||||||
def get_full_search_url(self):
|
def get_full_search_url(self):
|
||||||
|
@ -1,28 +1,33 @@
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from .core import ConnectorCore, ConnectorReturn, ConnectorCache, curl_content
|
from .core import ConnectorCache, ConnectorCore, ConnectorReturn, curl_content
|
||||||
from ..utils import link_exist_in_db, check_blacklist_words, check_if_vf
|
from ..utils import check_blacklist_words, check_if_vf, link_exist_in_db
|
||||||
|
|
||||||
|
|
||||||
class Nyaa(ConnectorCore):
|
class Nyaa(ConnectorCore):
|
||||||
color = 'is-link'
|
color = "is-link"
|
||||||
title = 'Nyaa'
|
title = "Nyaa"
|
||||||
favicon = 'nyaa.png'
|
favicon = "nyaa.png"
|
||||||
base_url = 'https://nyaa.si'
|
base_url = "https://nyaa.si"
|
||||||
is_light = False
|
is_light = False
|
||||||
|
|
||||||
def get_full_search_url(self):
|
def get_full_search_url(self):
|
||||||
sort_type = 'size'
|
sort_type = "size"
|
||||||
if self.return_type is ConnectorReturn.HISTORY:
|
if self.return_type is ConnectorReturn.HISTORY:
|
||||||
sort_type = 'id'
|
sort_type = "id"
|
||||||
|
|
||||||
to_query = '(%s vf)|(%s vostfr)|(%s multi)|(%s french)' % (
|
to_query = "(%s vf)|(%s vostfr)|(%s multi)|(%s french)" % (
|
||||||
|
self.query,
|
||||||
self.query,
|
self.query,
|
||||||
self.query,
|
self.query,
|
||||||
self.query,
|
self.query,
|
||||||
self.query
|
|
||||||
)
|
)
|
||||||
return '%s/?f=0&c=1_3&s=%s&o=desc&q=%s&p=%s' % (self.base_url, sort_type, to_query, self.page)
|
return "%s/?f=0&c=1_3&s=%s&o=desc&q=%s&p=%s" % (
|
||||||
|
self.base_url,
|
||||||
|
sort_type,
|
||||||
|
to_query,
|
||||||
|
self.page,
|
||||||
|
)
|
||||||
|
|
||||||
def get_history(self):
|
def get_history(self):
|
||||||
self.search()
|
self.search()
|
||||||
@ -31,21 +36,21 @@ class Nyaa(ConnectorCore):
|
|||||||
def search(self):
|
def search(self):
|
||||||
response = curl_content(self.get_full_search_url())
|
response = curl_content(self.get_full_search_url())
|
||||||
|
|
||||||
if response['http_code'] == 200:
|
if response["http_code"] == 200:
|
||||||
html = BeautifulSoup(response['output'], 'html.parser')
|
html = BeautifulSoup(response["output"], "html.parser")
|
||||||
trs = html.select('table.torrent-list tr')
|
trs = html.select("table.torrent-list tr")
|
||||||
valid_trs = 0
|
valid_trs = 0
|
||||||
|
|
||||||
for i, tr in enumerate(trs):
|
for i, tr in enumerate(trs):
|
||||||
if not i:
|
if not i:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tds = tr.findAll('td')
|
tds = tr.findAll("td")
|
||||||
check_downloads = int(tds[7].get_text())
|
check_downloads = int(tds[7].get_text())
|
||||||
check_seeds = int(tds[5].get_text())
|
check_seeds = int(tds[5].get_text())
|
||||||
|
|
||||||
if check_downloads or check_seeds:
|
if check_downloads or check_seeds:
|
||||||
urls = tds[1].findAll('a')
|
urls = tds[1].findAll("a")
|
||||||
|
|
||||||
if len(urls) > 1:
|
if len(urls) > 1:
|
||||||
url = urls[1]
|
url = urls[1]
|
||||||
@ -60,21 +65,31 @@ class Nyaa(ConnectorCore):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
valid_trs = valid_trs + 1
|
valid_trs = valid_trs + 1
|
||||||
href = self.base_url + url['href']
|
href = self.base_url + url["href"]
|
||||||
|
|
||||||
self.data.append({
|
self.data.append(
|
||||||
'vf': check_if_vf(url_safe),
|
{
|
||||||
'href': href,
|
"vf": check_if_vf(url_safe),
|
||||||
'name': url_safe,
|
"href": href,
|
||||||
'comment': str(urls[0]).replace('/view/', self.base_url + '/view/') if has_comment else '',
|
"name": url_safe,
|
||||||
'link': tds[2].decode_contents().replace('/download/', self.base_url + '/download/'),
|
"comment": str(urls[0]).replace(
|
||||||
'size': tds[3].get_text(),
|
"/view/", self.base_url + "/view/"
|
||||||
'date': tds[4].get_text(),
|
)
|
||||||
'seeds': check_seeds,
|
if has_comment
|
||||||
'leechs': tds[6].get_text(),
|
else "",
|
||||||
'downloads': check_downloads,
|
"link": tds[2]
|
||||||
'class': self.color if link_exist_in_db(href) else 'is-%s' % tr['class'][0]
|
.decode_contents()
|
||||||
})
|
.replace("/download/", self.base_url + "/download/"),
|
||||||
|
"size": tds[3].get_text(),
|
||||||
|
"date": tds[4].get_text(),
|
||||||
|
"seeds": check_seeds,
|
||||||
|
"leechs": tds[6].get_text(),
|
||||||
|
"downloads": check_downloads,
|
||||||
|
"class": self.color
|
||||||
|
if link_exist_in_db(href)
|
||||||
|
else "is-%s" % tr["class"][0],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.on_error = False
|
self.on_error = False
|
||||||
self.is_more = valid_trs and valid_trs != len(trs) - 1
|
self.is_more = valid_trs and valid_trs != len(trs) - 1
|
||||||
@ -83,9 +98,9 @@ class Nyaa(ConnectorCore):
|
|||||||
def is_vf(self, url):
|
def is_vf(self, url):
|
||||||
response = curl_content(url)
|
response = curl_content(url)
|
||||||
|
|
||||||
if response['http_code'] == 200:
|
if response["http_code"] == 200:
|
||||||
html = BeautifulSoup(response['output'], 'html.parser')
|
html = BeautifulSoup(response["output"], "html.parser")
|
||||||
title = html.select('h3.panel-title')
|
title = html.select("h3.panel-title")
|
||||||
return check_if_vf(title[0].get_text())
|
return check_if_vf(title[0].get_text())
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
@ -4,28 +4,27 @@ from urllib.parse import quote
|
|||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from .core import ConnectorCore, ConnectorReturn, ConnectorCache, curl_content
|
from .core import ConnectorCache, ConnectorCore, ConnectorReturn, curl_content
|
||||||
from ..utils import parse_date, link_exist_in_db, check_blacklist_words, check_if_vf
|
from ..utils import check_blacklist_words, check_if_vf, link_exist_in_db, parse_date
|
||||||
|
|
||||||
|
|
||||||
class YggTorrent(ConnectorCore):
|
class YggTorrent(ConnectorCore):
|
||||||
color = 'is-success'
|
color = "is-success"
|
||||||
title = 'YggTorrent'
|
title = "YggTorrent"
|
||||||
favicon = 'yggtorrent.png'
|
favicon = "yggtorrent.png"
|
||||||
base_url = 'https://www5.yggtorrent.fi'
|
base_url = "https://www5.yggtorrent.fi"
|
||||||
is_light = False
|
is_light = False
|
||||||
category = 2179
|
category = 2179
|
||||||
|
|
||||||
def get_full_search_url(self):
|
def get_full_search_url(self):
|
||||||
sort_type = 'size'
|
sort_type = "size"
|
||||||
if self.return_type is ConnectorReturn.HISTORY:
|
if self.return_type is ConnectorReturn.HISTORY:
|
||||||
sort_type = 'publish_date'
|
sort_type = "publish_date"
|
||||||
sort_page = '&page=%s' % (
|
sort_page = "&page=%s" % ((self.page - 1) * 50) if self.page > 1 else ""
|
||||||
(self.page - 1) * 50
|
|
||||||
) if self.page > 1 else ''
|
|
||||||
|
|
||||||
return '%s/engine/search?name=%s&category=2145&sub_category=%s&do=search&order=desc&sort=%s%s' % (
|
return (
|
||||||
self.base_url, self.query, self.category, sort_type, sort_page
|
"%s/engine/search?name=%s&category=2145&sub_category=%s&do=search&order=desc&sort=%s%s"
|
||||||
|
% (self.base_url, self.query, self.category, sort_type, sort_page)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_history(self):
|
def get_history(self):
|
||||||
@ -34,20 +33,18 @@ class YggTorrent(ConnectorCore):
|
|||||||
@ConnectorCache.cache_data
|
@ConnectorCache.cache_data
|
||||||
def search(self):
|
def search(self):
|
||||||
if self.category:
|
if self.category:
|
||||||
response = curl_content(
|
response = curl_content(self.get_full_search_url(), cloudflare=True)
|
||||||
self.get_full_search_url(), cloudflare=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if response['http_code'] == 200:
|
if response["http_code"] == 200:
|
||||||
html = BeautifulSoup(response['output'], 'html.parser')
|
html = BeautifulSoup(response["output"], "html.parser")
|
||||||
trs = html.select('table.table tr')
|
trs = html.select("table.table tr")
|
||||||
valid_trs = 0
|
valid_trs = 0
|
||||||
|
|
||||||
for i, tr in enumerate(trs):
|
for i, tr in enumerate(trs):
|
||||||
if not i:
|
if not i:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tds = tr.findAll('td')
|
tds = tr.findAll("td")
|
||||||
check_downloads = int(tds[6].get_text())
|
check_downloads = int(tds[6].get_text())
|
||||||
check_seeds = int(tds[7].get_text())
|
check_seeds = int(tds[7].get_text())
|
||||||
|
|
||||||
@ -60,23 +57,35 @@ class YggTorrent(ConnectorCore):
|
|||||||
|
|
||||||
valid_trs = valid_trs + 1
|
valid_trs = valid_trs + 1
|
||||||
|
|
||||||
self.data.append({
|
self.data.append(
|
||||||
'vf': check_if_vf(url_safe),
|
{
|
||||||
'href': url['href'],
|
"vf": check_if_vf(url_safe),
|
||||||
'name': url_safe,
|
"href": url["href"],
|
||||||
'comment': '<a href="%s#comm" target="_blank"><i class="fa fa-comments-o"></i>%s</a>' %
|
"name": url_safe,
|
||||||
(url['href'], tds[3].decode_contents()),
|
"comment": (
|
||||||
'link': '<a href="%s/engine/download_torrent?id=%s">'
|
'<a href="%s#comm" target="_blank">'
|
||||||
|
'<i class="fa fa-comments-o"></i>%s</a>'
|
||||||
|
)
|
||||||
|
% (url["href"], tds[3].decode_contents()),
|
||||||
|
"link": '<a href="%s/engine/download_torrent?id=%s">'
|
||||||
'<i class="fa fa-fw fa-download"></i>'
|
'<i class="fa fa-fw fa-download"></i>'
|
||||||
'</a>' % (self.base_url,
|
"</a>"
|
||||||
re.search(r'/(\d+)', url['href']).group(1)),
|
% (
|
||||||
'size': tds[5].get_text(),
|
self.base_url,
|
||||||
'date': parse_date(datetime.fromtimestamp(int(tds[4].div.get_text()))),
|
re.search(r"/(\d+)", url["href"]).group(1),
|
||||||
'seeds': check_seeds,
|
),
|
||||||
'leechs': tds[8].get_text(),
|
"size": tds[5].get_text(),
|
||||||
'downloads': check_downloads,
|
"date": parse_date(
|
||||||
'class': self.color if link_exist_in_db(quote(url['href'], '/+:')) else ''
|
datetime.fromtimestamp(int(tds[4].div.get_text()))
|
||||||
})
|
),
|
||||||
|
"seeds": check_seeds,
|
||||||
|
"leechs": tds[8].get_text(),
|
||||||
|
"downloads": check_downloads,
|
||||||
|
"class": self.color
|
||||||
|
if link_exist_in_db(quote(url["href"], "/+:"))
|
||||||
|
else "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
self.on_error = False
|
self.on_error = False
|
||||||
self.is_more = valid_trs and valid_trs != len(trs) - 1
|
self.is_more = valid_trs and valid_trs != len(trs) - 1
|
||||||
@ -85,14 +94,14 @@ class YggTorrent(ConnectorCore):
|
|||||||
def is_vf(self, url):
|
def is_vf(self, url):
|
||||||
response = curl_content(url)
|
response = curl_content(url)
|
||||||
|
|
||||||
if response['http_code'] == 200:
|
if response["http_code"] == 200:
|
||||||
html = BeautifulSoup(response['output'], 'html.parser')
|
html = BeautifulSoup(response["output"], "html.parser")
|
||||||
title = html.select('#title h1')
|
title = html.select("#title h1")
|
||||||
return check_if_vf(title[0].get_text())
|
return check_if_vf(title[0].get_text())
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class YggAnimation(YggTorrent):
|
class YggAnimation(YggTorrent):
|
||||||
title = 'YggAnimation'
|
title = "YggAnimation"
|
||||||
category = 2178
|
category = 2178
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import HiddenField, StringField, SelectField
|
from wtforms import HiddenField, SelectField, StringField
|
||||||
from wtforms.fields.html5 import SearchField, URLField
|
from wtforms.fields.html5 import SearchField, URLField
|
||||||
from wtforms.validators import DataRequired
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
|
||||||
class SearchForm(FlaskForm):
|
class SearchForm(FlaskForm):
|
||||||
q = SearchField('search', validators=[DataRequired()])
|
q = SearchField("search", validators=[DataRequired()])
|
||||||
|
|
||||||
|
|
||||||
class DeleteForm(FlaskForm):
|
class DeleteForm(FlaskForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
csrf = False
|
csrf = False
|
||||||
|
|
||||||
id = HiddenField('id', validators=[DataRequired()])
|
id = HiddenField("id", validators=[DataRequired()])
|
||||||
|
|
||||||
|
|
||||||
class EditForm(FlaskForm):
|
class EditForm(FlaskForm):
|
||||||
id = HiddenField('id')
|
id = HiddenField("id")
|
||||||
folder = SelectField('folder', validators=[DataRequired()])
|
folder = SelectField("folder", validators=[DataRequired()])
|
||||||
name = StringField('name', validators=[DataRequired()])
|
name = StringField("name", validators=[DataRequired()])
|
||||||
link = URLField('link', validators=[DataRequired()])
|
link = URLField("link", validators=[DataRequired()])
|
||||||
season = StringField('season', validators=[DataRequired()])
|
season = StringField("season", validators=[DataRequired()])
|
||||||
comment = StringField('comment')
|
comment = StringField("comment")
|
||||||
keyword = StringField('keyword', validators=[DataRequired()])
|
keyword = StringField("keyword", validators=[DataRequired()])
|
||||||
|
|
||||||
|
|
||||||
class FolderEditForm(FlaskForm):
|
class FolderEditForm(FlaskForm):
|
||||||
id = HiddenField('id')
|
id = HiddenField("id")
|
||||||
name = StringField('name', validators=[DataRequired()])
|
name = StringField("name", validators=[DataRequired()])
|
||||||
path = StringField('path')
|
path = StringField("path")
|
||||||
|
|
||||||
|
|
||||||
class FolderDeleteForm(FlaskForm):
|
class FolderDeleteForm(FlaskForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
csrf = False
|
csrf = False
|
||||||
|
|
||||||
id = HiddenField('id', validators=[DataRequired()])
|
id = HiddenField("id", validators=[DataRequired()])
|
||||||
|
15
pynyaata/get404.py
Normal file
15
pynyaata/get404.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from .connectors.core import curl_content
|
||||||
|
from .models import AnimeLink
|
||||||
|
|
||||||
|
links = AnimeLink.query.all()
|
||||||
|
|
||||||
|
for link in links:
|
||||||
|
html = curl_content(link.link, debug=False, cloudflare=True)
|
||||||
|
|
||||||
|
if html["http_code"] != 200 and html["http_code"] != 500:
|
||||||
|
print(
|
||||||
|
"(%d) %s %s : %s"
|
||||||
|
% (html["http_code"], link.title.name, link.season, link.link)
|
||||||
|
)
|
||||||
|
elif "darkgray" in str(html["output"]):
|
||||||
|
print("(darkgray) %s %s : %s" % (link.title.name, link.season, link.link))
|
@ -6,9 +6,7 @@ class AnimeFolder(db.Model):
|
|||||||
name = db.Column(db.String(length=100), unique=True, nullable=False)
|
name = db.Column(db.String(length=100), unique=True, nullable=False)
|
||||||
path = db.Column(db.String(length=100))
|
path = db.Column(db.String(length=100))
|
||||||
titles = db.relationship(
|
titles = db.relationship(
|
||||||
"AnimeTitle",
|
"AnimeTitle", backref="folder", cascade="all,delete-orphan"
|
||||||
backref="folder",
|
|
||||||
cascade='all,delete-orphan'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -16,12 +14,8 @@ class AnimeTitle(db.Model):
|
|||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(length=100), unique=True, nullable=False)
|
name = db.Column(db.String(length=100), unique=True, nullable=False)
|
||||||
keyword = db.Column(db.Text(), nullable=False)
|
keyword = db.Column(db.Text(), nullable=False)
|
||||||
folder_id = db.Column(db.Integer, db.ForeignKey('anime_folder.id'))
|
folder_id = db.Column(db.Integer, db.ForeignKey("anime_folder.id"))
|
||||||
links = db.relationship(
|
links = db.relationship("AnimeLink", backref="title", cascade="all,delete-orphan")
|
||||||
'AnimeLink',
|
|
||||||
backref="title",
|
|
||||||
cascade='all,delete-orphan'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AnimeLink(db.Model):
|
class AnimeLink(db.Model):
|
||||||
@ -30,7 +24,7 @@ class AnimeLink(db.Model):
|
|||||||
season = db.Column(db.Text(), nullable=False)
|
season = db.Column(db.Text(), nullable=False)
|
||||||
comment = db.Column(db.Text())
|
comment = db.Column(db.Text())
|
||||||
vf = db.Column(db.Boolean, nullable=False)
|
vf = db.Column(db.Boolean, nullable=False)
|
||||||
title_id = db.Column(db.Integer, db.ForeignKey('anime_title.id'))
|
title_id = db.Column(db.Integer, db.ForeignKey("anime_title.id"))
|
||||||
|
|
||||||
|
|
||||||
def create_all():
|
def create_all():
|
||||||
|
@ -2,17 +2,18 @@ import re
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateparser import parse
|
from dateparser import parse
|
||||||
|
|
||||||
from .config import DB_ENABLED, BLACKLIST_WORDS
|
from .config import BLACKLIST_WORDS, DB_ENABLED
|
||||||
|
|
||||||
|
|
||||||
def link_exist_in_db(href):
|
def link_exist_in_db(href):
|
||||||
if DB_ENABLED:
|
if DB_ENABLED:
|
||||||
from .models import AnimeLink
|
from .models import AnimeLink
|
||||||
|
|
||||||
return AnimeLink.query.filter_by(link=href).first()
|
return AnimeLink.query.filter_by(link=href).first()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def parse_date(str_to_parse, date_format=''):
|
def parse_date(str_to_parse, date_format=""):
|
||||||
if str_to_parse is None:
|
if str_to_parse is None:
|
||||||
date_to_format = datetime.fromtimestamp(0)
|
date_to_format = datetime.fromtimestamp(0)
|
||||||
elif isinstance(str_to_parse, datetime):
|
elif isinstance(str_to_parse, datetime):
|
||||||
@ -24,12 +25,14 @@ def parse_date(str_to_parse, date_format=''):
|
|||||||
else:
|
else:
|
||||||
date_to_format = datetime.fromtimestamp(0)
|
date_to_format = datetime.fromtimestamp(0)
|
||||||
|
|
||||||
return date_to_format.isoformat(' ', 'minutes')
|
return date_to_format.isoformat(" ", "minutes")
|
||||||
|
|
||||||
|
|
||||||
def boldify(str_to_replace, keyword):
|
def boldify(str_to_replace, keyword):
|
||||||
if keyword:
|
if keyword:
|
||||||
return re.sub('(%s)' % keyword, r'<b>\1</b>', str_to_replace, flags=re.IGNORECASE)
|
return re.sub(
|
||||||
|
"(%s)" % keyword, r"<b>\1</b>", str_to_replace, flags=re.IGNORECASE
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return str_to_replace
|
return str_to_replace
|
||||||
|
|
||||||
@ -39,4 +42,4 @@ def check_blacklist_words(url):
|
|||||||
|
|
||||||
|
|
||||||
def check_if_vf(title):
|
def check_if_vf(title):
|
||||||
return any(word.lower() in title.lower() for word in ['vf', 'multi', 'french'])
|
return any(word.lower() in title.lower() for word in ["vf", "multi", "french"])
|
||||||
|
56
pyproject.toml
Normal file
56
pyproject.toml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "pynyaata"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "π 😼た, Xéfir's personal animes torrent search engine"
|
||||||
|
authors = ["Xéfir Destiny <xefir@crystalyx.net>"]
|
||||||
|
license = "WTFPL"
|
||||||
|
readme = "README.md"
|
||||||
|
homepage = "https://nyaa.crystalyx.net/"
|
||||||
|
repository = "https://git.crystalyx.net/Xefir/PyNyaaTa"
|
||||||
|
classifiers = [
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Operating System :: OS Independent"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
pynyaata = 'pynyaata:run'
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.7"
|
||||||
|
Flask = "^2.2.2"
|
||||||
|
Flask-SQLAlchemy = "^2.5.1"
|
||||||
|
Flask-HTTPAuth = "^4.7.0"
|
||||||
|
Flask-WTF = "^1.0.1"
|
||||||
|
WTForms = "^3.0.1"
|
||||||
|
PyMySQL = "^1.0.2"
|
||||||
|
pg8000 = "^1.29.1"
|
||||||
|
requests = "^2.28.1"
|
||||||
|
beautifulsoup4 = "^4.11.1"
|
||||||
|
python-dotenv = "^0.20.0"
|
||||||
|
dateparser = "^1.1.1"
|
||||||
|
redis = "^4.3.4"
|
||||||
|
transmission-rpc = "^3.3.2"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
flake8 = "3.9.2"
|
||||||
|
black = "^22.8.0"
|
||||||
|
mypy = "^0.971"
|
||||||
|
djlint = "1.9.3"
|
||||||
|
pytest = "^7.1.2"
|
||||||
|
pytest-cov = "^3.0.0"
|
||||||
|
flake8-black = "^0.3.3"
|
||||||
|
flake8-alphabetize = "^0.0.17"
|
||||||
|
types-dateparser = "^1.1.4"
|
||||||
|
types-redis = "^4.3.19"
|
||||||
|
types-requests = "^2.28.9"
|
||||||
|
Flask-HTTPAuth-stubs = "^0.1.5"
|
||||||
|
types-Flask-SQLAlchemy = "^2.5.9"
|
||||||
|
types-beautifulsoup4 = "^4.11.5"
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
@ -1,13 +0,0 @@
|
|||||||
Flask==2.2.2
|
|
||||||
Flask-SQLAlchemy==2.5.1
|
|
||||||
Flask-HTTPAuth==4.7.0
|
|
||||||
Flask-WTF==1.0.1
|
|
||||||
WTForms==2.3.3
|
|
||||||
PyMySQL==1.0.2
|
|
||||||
pg8000==1.29.1
|
|
||||||
requests==2.28.1
|
|
||||||
beautifulsoup4==4.11.1
|
|
||||||
python-dotenv==0.20.0
|
|
||||||
dateparser==1.1.1
|
|
||||||
redis==4.3.4
|
|
||||||
transmission-rpc==3.3.2
|
|
5
run.py
5
run.py
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
from pynyaata import run
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
run()
|
|
31
setup.py
31
setup.py
@ -1,31 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from setuptools import setup, find_packages
|
|
||||||
|
|
||||||
with open("README.md") as readme_file:
|
|
||||||
long_description = readme_file.read()
|
|
||||||
|
|
||||||
with open("requirements.txt") as requirements_file:
|
|
||||||
requirements = requirements_file.read().splitlines()
|
|
||||||
|
|
||||||
setup(
|
|
||||||
name="PyNyaaTa",
|
|
||||||
version=datetime.now().strftime("%Y%m%d%H%M"),
|
|
||||||
author="Xéfir Destiny",
|
|
||||||
author_email="xefir@crystalyx.net",
|
|
||||||
description="π 😼た, Xéfir's personal animes torrent search engine",
|
|
||||||
long_description=long_description,
|
|
||||||
long_description_content_type="text/markdown",
|
|
||||||
url="https://git.crystalyx.net/Xefir/PyNyaaTa",
|
|
||||||
packages=find_packages(),
|
|
||||||
install_requires=requirements,
|
|
||||||
include_package_data=True,
|
|
||||||
entry_points={
|
|
||||||
"console_scripts": ["pynyaata=pynyaata:run"],
|
|
||||||
},
|
|
||||||
classifiers=[
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
],
|
|
||||||
python_requires=">=3.5",
|
|
||||||
)
|
|
Reference in New Issue
Block a user