diff --git a/.gitignore b/.gitignore index 06647ff..d2c5a66 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ __pycache__ .coverage *.egg-info sample -.tox \ No newline at end of file +.tox +*.sqlite \ No newline at end of file diff --git a/static/css/yoink.css b/static/css/yoink.css new file mode 100644 index 0000000..2eabf48 --- /dev/null +++ b/static/css/yoink.css @@ -0,0 +1,3 @@ +* { + box-sizing: border-box; +} \ No newline at end of file diff --git a/static/js/yoink.js b/static/js/yoink.js new file mode 100644 index 0000000..e69de29 diff --git a/templates/404.html b/templates/404.html new file mode 100644 index 0000000..1445757 --- /dev/null +++ b/templates/404.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block meta %} +{{ super() }} + +{% endblock %} + +{% block title %}Testing{% endblock %} + +{% block page_content %} + +{% endblock %} \ No newline at end of file diff --git a/templates/500.html b/templates/500.html new file mode 100644 index 0000000..e0bd7b8 --- /dev/null +++ b/templates/500.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block meta %} +{{ super() }} + +{% endblock %} + +{% block title %}Testing{% endblock %} + +{% block page_content %} + +{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..48cb4a8 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,61 @@ + + + + {% block meta %} + + + + {% endblock %} + {% block title %}Y!oink Web App{% endblock %} + {% block styles %} + + + {% endblock %} + {% block scripts %} + + {{ moment.include_moment() }} + {% endblock %} + + + + {% block navbar %} + + {% endblock %} + + {% block content %} +
+ {% for message in get_flashed_messages() %} + + {% endfor %} + {% block page_content %} + {% endblock %} +
+ {% endblock %} + + + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..d25fca7 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} +{% import 'macros/library.html' as library %} + +{% block meta %} +{{ super() }} + +{% endblock %} + +{% block title %}Testing{% endblock %} + +{% block page_content %} + + +
+ {{ form.hidden_tag() }} +
+ {{ form.series.label(class_='input-group-text') }} +
+ {{ form.series() }} +
+ + {{ form.url(class_='form-control') }} + {{ form.download(class_='btn btn-outline-primary') }} +
+
+ +{{ library.latest_downloads(latest) }} + +{% endblock %} \ No newline at end of file diff --git a/templates/macros/comments.html b/templates/macros/comments.html new file mode 100644 index 0000000..35dddb0 --- /dev/null +++ b/templates/macros/comments.html @@ -0,0 +1,3 @@ +{% macro render_comment(comment) %} +
  • {{ comment }}
  • +{% endmacro %} diff --git a/templates/macros/library.html b/templates/macros/library.html new file mode 100644 index 0000000..1b29b91 --- /dev/null +++ b/templates/macros/library.html @@ -0,0 +1,38 @@ + +{% macro latest_downloads(comics) %} + {% if comics %} +
    +

    Latest Downloads

    + {% for comic in comics %} +
    +
    + {{ comic['title'] }} +
    +

    {{ comic['title'] }}

    +

    {{ comic['title'] }}

    +
    + +
    +
    + {% endfor %} +
    + {% else %} +

    Empty Comic Library

    + {% endif %} +{% endmacro %} + + +{% macro all_downloads(comics) %} + {% if comics %} + {% for comic in comics %} + +

    {{ comic['title'] }}

    + + + {% endfor %} + {% else %} +

    Empty Comic Library

    + {% endif %} +{% endmacro %} \ No newline at end of file diff --git a/web.py b/web.py new file mode 100644 index 0000000..e60c867 --- /dev/null +++ b/web.py @@ -0,0 +1,141 @@ +import os +import threading +from flask import Flask, render_template, url_for, request, flash, make_response, redirect, send_from_directory +from flask_moment import Moment +from flask_sqlalchemy import SQLAlchemy +from flask_wtf import FlaskForm +from wtforms import StringField, SubmitField, BooleanField +from wtforms.validators import DataRequired + +from yoink.config import config +from yoink.comic import Comic + + +class DownloadForm(FlaskForm): + url = StringField('Comic URL', validators=[DataRequired()]) + series = BooleanField('Series? ') + download = SubmitField('Download') + +app = Flask(__name__) +moment = Moment(app) +app.config['SECRET_KEY'] = 'snapekilleddumpledork' +app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{os.path.join(config.app_root, "data.sqlite")}' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +db = SQLAlchemy(app) + + + +class Role(db.Model): + __tablename__ = 'roles' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(64), unique=True) + users = db.relationship('User', backref='role', lazy='dynamic') + + def __repr__(self): + return f'' + + +class User(db.Model): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(64), unique=True, index=True) + role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) + + def __repr__(self): return f'' + +class ComicMeta(db.Model): + __tablename__ = 'comicmeta' + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(256), unique=True, index=True) + issue = db.Column(db.Integer, nullable=True, index=True) + category = db.Column(db.String(128), index=True, nullable=True) + previous_issue = db.Column(db.String(256), nullable=True) + next_issue = db.Column(db.String(256), nullable=True) + cover_path = db.Column(db.String(256)) + archive_path = db.Column(db.String(256)) + + +@app.errorhandler(404) +def not_found(e): + return render_template('404.html'), 404 + +@app.errorhandler(500) +def server_error(e): + return render_template('500.html'), 500 + +@app.route('/uploads/') +def download_file(filename): + return send_from_directory(os.path.join(config.library_path, 'comics'), filename, as_attachment=True) + + +def get_cover_path(comic): + return [image for image in os.listdir(os.path.join(config.library_path, 'comics', comic.title)) if image.endswith('000.jpg')][0] + + +def get_archive_path(comic): + return [image for image in os.listdir(os.path.join(config.library_path, 'comics', comic.title)) if image.endswith('.cbr')][0] + + +def get_comic_library_meta(): + comic_meta = [] + + for comic in ComicMeta.query.all(): + comic_meta.append({ + 'cover': comic.cover_path, + 'title': comic.title, + 'archive': comic.archive_path + }) + + return comic_meta + + +@app.route('/', methods=['post','get']) +def index(): + url = None + series = False + form = DownloadForm() + latest = get_comic_library_meta() + + if form.validate_on_submit(): + url = form.url.data.strip() + series = form.series.data + + comic = Comic(url) + comic_meta = ComicMeta.query.filter_by(title=comic.title).first() + + comic.archiver.download() + comic.archiver.generate_archive() + + + if comic_meta is None: + comic_meta = ComicMeta() + comic_meta.title = comic.title + comic_meta.category = comic.category + comic_meta.issue = comic.issue_number + comic_meta.next_issue = comic.next + comic_meta.previous_issue = comic.prev + comic_meta.cover_path = os.path.join(comic.title, get_cover_path(comic)) + comic_meta.archive_path = os.path.join(comic.title, get_archive_path(comic)) + + db.session.add(comic_meta) + db.session.commit() + else: + flash(f'Comic {comic.title} exists') + + latest = get_comic_library_meta() + form.url.data = '' + + return render_template('index.html', form=form, url=url, series=series, latest=latest), 200 + + + if form.series.data: + print('Download the whole damn lot') + + flash(f'{comic.title} downloaded to {os.path.join(config.library_path, "comics/" + comic.title)}') + + latest = get_comic_library_meta() + comic.archiver.cleanup_worktree() + form.url.data = '' + + return render_template('index.html', form=form, url=url, series=series, latest=latest), 200 + \ No newline at end of file diff --git a/yoink/comic.py b/yoink/comic.py index 866f4e3..b1e52a3 100644 --- a/yoink/comic.py +++ b/yoink/comic.py @@ -42,7 +42,7 @@ class Comic(Scrapable): if len(comics) > 0: return comics - + @property def filelist(self) -> list: comics = self.__parse_soup() diff --git a/yoink/tests/test_basic.py b/yoink/tests/test_basic.py index 27c710e..798402a 100644 --- a/yoink/tests/test_basic.py +++ b/yoink/tests/test_basic.py @@ -15,7 +15,6 @@ class BasicTestCase(unittest.TestCase): self.test_comic = 'http://readallcomics.com/static-season-one-6-2022/' self.test_comic_b = 'http://readallcomics.com/captain-marvel-vs-rogue-2021-part-1/' self.comic = Comic(self.test_comic) - self.archiver = ComicArchiver(self.comic) self.remove_queue = [] self.expected_title = 'Static Season One 6 (2022)' self.expected_title_b = 'Captain Marvel vs. Rogue (2021 – Part 1)' @@ -46,16 +45,16 @@ class BasicTestCase(unittest.TestCase): self.assertEqual(len(os.listdir(os.path.join(library_path, 'comics'))), 0) def test_004_comic_folder_created_and_populated(self): - self.archiver.download() + self.comic.archiver.download() self.assertTrue(os.path.exists(os.path.join(library_path, f'comics/{self.comic.title}'))) self.assertGreater(len(os.listdir(os.path.join(library_path, f'comics/{self.comic.title}'))), 0) def test_005_comic_archive_generated(self): - self.archiver.generate_archive() + self.comic.archiver.generate_archive() self.assertTrue(os.path.exists(os.path.join(library_path, f'comics/{self.comic.title}/{self.comic.title}.cbr'))) def test_006_folder_cleaned_after_archive_generation(self): - self.archiver.cleanup_worktree() + self.comic.archiver.cleanup_worktree() self.assertLessEqual(len(os.listdir(os.path.join(library_path, f'comics/{self.comic.title}'))), 3) def test_007_comic_instance_has_archiver(self): diff --git a/yoink/yoink.json b/yoink/yoink.json index 49e1b47..0e74ba1 100644 --- a/yoink/yoink.json +++ b/yoink/yoink.json @@ -11,7 +11,7 @@ { "name": "readallcomics", "url": "http://readallcomics.com", - "search": { + "filters": { "default": { "element": "div", "class": "separator", @@ -34,7 +34,7 @@ { "name": "dragonballsupermanga", "url": "https://www.dragonballsupermanga.net", - "search": { + "filters": { "dbsuper": { "element": "meta", "class": null, @@ -47,7 +47,7 @@ { "name": "mangadex", "url": "https://www.mangadex.tv", - "search": { + "filters": { "mangadex": { "element": "img", "class": null,