You've already forked amazing-ytdlp-archive
							
							Compare commits
	
		
			2 Commits
		
	
	
		
			7dfc340cd0
			...
			cb2bcc972c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					cb2bcc972c | ||
| 
						 | 
					6b1e5b719d | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -142,3 +142,6 @@ cython_debug/
 | 
			
		||||
.idea/
 | 
			
		||||
/learning/api testing/youtube_api_key.py
 | 
			
		||||
/learning/api testing/youtube_api_key.py
 | 
			
		||||
 | 
			
		||||
# secrets
 | 
			
		||||
client_secrets.json
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
import os
 | 
			
		||||
import secrets
 | 
			
		||||
from flask import Flask
 | 
			
		||||
from flask_caching import Cache
 | 
			
		||||
from .filters import pretty_time
 | 
			
		||||
 | 
			
		||||
from flask_limiter import Limiter
 | 
			
		||||
from flask_limiter.util import get_remote_address
 | 
			
		||||
 | 
			
		||||
from . import filters
 | 
			
		||||
 | 
			
		||||
def create_app(test_config=None):
 | 
			
		||||
    config = {'MONGO_CONNECTION': os.environ.get('AYTA_MONGOCONNECTION', 'mongodb://root:example@192.168.66.140:27017'),
 | 
			
		||||
@@ -11,25 +13,36 @@ def create_app(test_config=None):
 | 
			
		||||
              'S3_ACCESSKEY': os.environ.get('AYTA_S3ACCESSKEY', 'lnUiGClFVXVuZbsr'),
 | 
			
		||||
              'S3_SECRETKEY': os.environ.get('AYTA_S3SECRETKEY', 'Qz9NG7rpcOWdK2WL'),
 | 
			
		||||
              'CACHE_TYPE': os.environ.get('AYTA_CACHETYPE', 'SimpleCache'),
 | 
			
		||||
              'CACHE_DEFAULT_TIMEOUT': os.environ.get('AYTA_CACHETIMEOUT', 300)
 | 
			
		||||
              'CACHE_DEFAULT_TIMEOUT': os.environ.get('AYTA_CACHETIMEOUT', 300),
 | 
			
		||||
              'SECRET_KEY': os.environ.get('AYTA_SECRETKEY', secrets.token_hex(32))
 | 
			
		||||
             }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    app = Flask(__name__)
 | 
			
		||||
    app.config.from_mapping(config)
 | 
			
		||||
    
 | 
			
		||||
    app.jinja_env.filters['pretty_time'] = pretty_time
 | 
			
		||||
    limiter = Limiter(
 | 
			
		||||
        get_remote_address,
 | 
			
		||||
        app=app,
 | 
			
		||||
        default_limits=['1000 per day', '100 per hour'],
 | 
			
		||||
        storage_uri="memory://",
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
    app.jinja_env.filters['pretty_time'] = filters.pretty_time
 | 
			
		||||
 | 
			
		||||
    from .blueprints import watch
 | 
			
		||||
    from .blueprints import index
 | 
			
		||||
    from .blueprints import admin
 | 
			
		||||
    from .blueprints import search
 | 
			
		||||
    from .blueprints import channel
 | 
			
		||||
    from .blueprints import auth
 | 
			
		||||
 | 
			
		||||
    app.register_blueprint(watch.bp)
 | 
			
		||||
    app.register_blueprint(index.bp)
 | 
			
		||||
    app.register_blueprint(admin.bp)
 | 
			
		||||
    app.register_blueprint(search.bp)
 | 
			
		||||
    app.register_blueprint(channel.bp)
 | 
			
		||||
    app.register_blueprint(auth.bp)
 | 
			
		||||
    
 | 
			
		||||
    app.add_url_rule("/", endpoint="base")
 | 
			
		||||
 | 
			
		||||
    return app
 | 
			
		||||
@@ -10,14 +10,17 @@ from flask import url_for
 | 
			
		||||
from ..nosql import get_nosql
 | 
			
		||||
from ..s3 import get_s3
 | 
			
		||||
from ..dlp import checkChannelId, getChannelInfo
 | 
			
		||||
from ..decorators import login_required
 | 
			
		||||
 | 
			
		||||
bp = Blueprint('admin', __name__, url_prefix='/admin')
 | 
			
		||||
 | 
			
		||||
@bp.route('')
 | 
			
		||||
@login_required
 | 
			
		||||
def base():
 | 
			
		||||
    return render_template('admin/index.html')
 | 
			
		||||
 | 
			
		||||
@bp.route('/channels', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def channels():
 | 
			
		||||
    channels = {}
 | 
			
		||||
    generic = {}
 | 
			
		||||
@@ -49,6 +52,7 @@ def channels():
 | 
			
		||||
    return render_template('admin/channels.html', channels=channels, generic=generic)
 | 
			
		||||
 | 
			
		||||
@bp.route('/channel/<channelId>', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def channel(channelId):
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        task = request.form.get('task', None)
 | 
			
		||||
@@ -56,11 +60,9 @@ def channel(channelId):
 | 
			
		||||
        value = request.form.get('value', None)
 | 
			
		||||
        
 | 
			
		||||
        if task == 'update-value':
 | 
			
		||||
            if key == 'active' and value is None: # html checkbox is not present if not checked
 | 
			
		||||
                value = False
 | 
			
		||||
            elif key == 'active' and value is not None:  # html checkbox is False if checked
 | 
			
		||||
                value = True
 | 
			
		||||
                
 | 
			
		||||
            if key == 'active':
 | 
			
		||||
                value = True if value else False
 | 
			
		||||
 | 
			
		||||
            if key == 'added_date':
 | 
			
		||||
                value = datetime.strptime(value, '%Y-%m-%d')
 | 
			
		||||
              
 | 
			
		||||
@@ -77,6 +79,7 @@ def channel(channelId):
 | 
			
		||||
    return render_template('admin/channel.html', channelInfo=channelInfo)
 | 
			
		||||
 | 
			
		||||
@bp.route('/runs', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def runs():
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        task = request.form.get('task', None)
 | 
			
		||||
@@ -90,11 +93,13 @@ def runs():
 | 
			
		||||
    return render_template('admin/runs.html', runs=runs)
 | 
			
		||||
 | 
			
		||||
@bp.route('/run/<runId>', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def run(runId):
 | 
			
		||||
    run = get_nosql().get_run(runId)
 | 
			
		||||
    return render_template('admin/run.html', run=run)
 | 
			
		||||
 | 
			
		||||
@bp.route('/files', methods=['GET', 'POST'])
 | 
			
		||||
@login_required
 | 
			
		||||
def files():
 | 
			
		||||
    run = get_s3().list_objects()
 | 
			
		||||
    return str(run)
 | 
			
		||||
							
								
								
									
										44
									
								
								ayta/blueprints/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								ayta/blueprints/auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
from flask import Blueprint, redirect, url_for, render_template, request, session, flash
 | 
			
		||||
from argon2 import PasswordHasher
 | 
			
		||||
from argon2.exceptions import VerifyMismatchError
 | 
			
		||||
 | 
			
		||||
corr = '$argon2id$v=19$m=64,t=3,p=4$YmY5RTV0bU9tRkx3Q0FvUw$VfPI6BowKvsO4pI1aRslXfbigerssHrHQnQNDhgR8Og'
 | 
			
		||||
 | 
			
		||||
bp = Blueprint('auth', __name__, url_prefix='/auth')
 | 
			
		||||
 | 
			
		||||
@bp.route('')
 | 
			
		||||
def base():
 | 
			
		||||
    return redirect(url_for('auth.login'))
 | 
			
		||||
    
 | 
			
		||||
@bp.route('/logout')
 | 
			
		||||
def logout():
 | 
			
		||||
    session.pop('username', None)
 | 
			
		||||
    flash('You have been logged out')
 | 
			
		||||
    return redirect(url_for('index.base'))
 | 
			
		||||
    
 | 
			
		||||
@bp.route('/login', methods=['GET', 'POST'])
 | 
			
		||||
#@app.limit('10 per day', override_defaults=False)
 | 
			
		||||
def login():
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        username = request.form.get('username', None)
 | 
			
		||||
        password = request.form.get('password', None)
 | 
			
		||||
        
 | 
			
		||||
        if not password:
 | 
			
		||||
            flash('Password was empty')
 | 
			
		||||
            return 'password required!'
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        try:
 | 
			
		||||
            ph = PasswordHasher()
 | 
			
		||||
            if ph.verify(corr, password):
 | 
			
		||||
                session['username'] = 'admin'
 | 
			
		||||
                flash('You have been logged in')
 | 
			
		||||
                return redirect(url_for('admin.base'))
 | 
			
		||||
            
 | 
			
		||||
            
 | 
			
		||||
        except VerifyMismatchError:
 | 
			
		||||
            flash('Wrong password')
 | 
			
		||||
        except:
 | 
			
		||||
            flash('Something went wrong')
 | 
			
		||||
            
 | 
			
		||||
    return render_template('login.html')
 | 
			
		||||
@@ -2,6 +2,7 @@ from flask import Blueprint
 | 
			
		||||
from flask import render_template
 | 
			
		||||
from flask import request
 | 
			
		||||
from flask import url_for
 | 
			
		||||
from flask import flash
 | 
			
		||||
from werkzeug.security import check_password_hash
 | 
			
		||||
from werkzeug.security import generate_password_hash
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								ayta/decorators.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ayta/decorators.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
from functools import wraps
 | 
			
		||||
from flask import session, request, redirect, url_for, flash
 | 
			
		||||
 | 
			
		||||
def login_required(f):
 | 
			
		||||
    @wraps(f)
 | 
			
		||||
    def decorated_function(*args, **kwargs):
 | 
			
		||||
        if 'username' in session:
 | 
			
		||||
            return f(*args, **kwargs)
 | 
			
		||||
        flash('Login required')
 | 
			
		||||
        return redirect(url_for('auth.login', next=request.url))
 | 
			
		||||
    return decorated_function
 | 
			
		||||
@@ -1,46 +1,49 @@
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
{% block title %}Admin page | AYTA{% endblock %}
 | 
			
		||||
{% extends 'material_base.html' %}
 | 
			
		||||
{% block title %}Administration page | AYTA{% endblock %}
 | 
			
		||||
{% block description %}Administration page of the AYTA system{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="container channels">
 | 
			
		||||
  <div class="head">
 | 
			
		||||
    <div class="title">
 | 
			
		||||
      <h1 style="display: inline-block;">Administration page</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
	<div>
 | 
			
		||||
        <select name="sort" class="sort{sort}" oninput="channelSort()">
 | 
			
		||||
          <option value="name-a">Name (asc)</option>
 | 
			
		||||
          <option value="name-d">Name (desc)</option>
 | 
			
		||||
        </select>
 | 
			
		||||
        <input type="text" class="search" oninput="channelSearch()" placeholder="Search...">
 | 
			
		||||
    </div>
 | 
			
		||||
<div class="row">
 | 
			
		||||
  <div class="col s12">
 | 
			
		||||
    <h4>Administration page</h4>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="channels flex-grid">
 | 
			
		||||
	 <div class="card" data-search="system">
 | 
			
		||||
       <a href="{{ url_for('admin.channels') }}" class="inner">
 | 
			
		||||
         <div class="content">
 | 
			
		||||
           <div class="title">System</div>
 | 
			
		||||
           <div class="meta">Internal system settings</div>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="divider"></div>
 | 
			
		||||
<div class="row">
 | 
			
		||||
  <div class="col s12">
 | 
			
		||||
	<h5>Global channel options</h5>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="row">
 | 
			
		||||
  <div class="col s6 l4 m-4">
 | 
			
		||||
	<a href="{{ url_for('admin.channels') }}">
 | 
			
		||||
	  <div class="card black-text">
 | 
			
		||||
        <div class="card-content">
 | 
			
		||||
          <span class="card-title">System</span>
 | 
			
		||||
		  <p class="grey-text">Internal system settings</p>
 | 
			
		||||
        </div>
 | 
			
		||||
       </a>
 | 
			
		||||
     </div>
 | 
			
		||||
	 <div class="card" data-search="channels">
 | 
			
		||||
       <a href="{{ url_for('admin.channels') }}" class="inner">
 | 
			
		||||
         <div class="content">
 | 
			
		||||
           <div class="title">Channels</div>
 | 
			
		||||
           <div class="meta">Manage channels in the system</div>
 | 
			
		||||
      </div>
 | 
			
		||||
	</a>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="col s6 l4 m-4">
 | 
			
		||||
	<a href="{{ url_for('admin.channels') }}">
 | 
			
		||||
	  <div class="card black-text">
 | 
			
		||||
        <div class="card-content">
 | 
			
		||||
          <span class="card-title">Channels</span>
 | 
			
		||||
		  <p class="grey-text">Manage channels in the system</p>
 | 
			
		||||
        </div>
 | 
			
		||||
       </a>
 | 
			
		||||
     </div>
 | 
			
		||||
	 <div class="card" data-search="runs">
 | 
			
		||||
       <a href="{{ url_for('admin.runs') }}" class="inner">
 | 
			
		||||
         <div class="content">
 | 
			
		||||
           <div class="title">Archive runs</div>
 | 
			
		||||
           <div class="meta">Look at the cron run logs</div>
 | 
			
		||||
      </div>
 | 
			
		||||
	</a>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="col s6 l4 m-4">
 | 
			
		||||
	<a href="{{ url_for('admin.runs') }}">
 | 
			
		||||
	  <div class="card black-text">
 | 
			
		||||
        <div class="card-content">
 | 
			
		||||
          <span class="card-title">Archive runs</span>
 | 
			
		||||
		  <p class="grey-text">Look at the cron run logs</p>
 | 
			
		||||
        </div>
 | 
			
		||||
       </a>
 | 
			
		||||
     </div>
 | 
			
		||||
      </div>
 | 
			
		||||
	</a>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
	<div class="card medium black-text">
 | 
			
		||||
	  <a href="{{ url_for('watch.base') }}?v={{ video }}">
 | 
			
		||||
	    <div class="card-image">
 | 
			
		||||
          <img loading="lazy" src="{{ url_for('static', filename='img/logo_text.png') }}">
 | 
			
		||||
          <img loading="lazy" src="https://archive.ventilaar.net/videos/automatic/{{ channel.get('id') }}/{{ videos[video].get('id') }}/{{ videos[video].get('title') }}.webp">
 | 
			
		||||
        </div>
 | 
			
		||||
	  </a>
 | 
			
		||||
      <div class="card-content activator">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								ayta/templates/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								ayta/templates/login.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
{% extends 'material_base.html' %}
 | 
			
		||||
{% block title %}Login Page | AYTA{% endblock %}
 | 
			
		||||
{% block description %}Login required for the requested page{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
<div class="row">
 | 
			
		||||
  <div class="col s3">
 | 
			
		||||
    <h4>pls login</h4>
 | 
			
		||||
	<form method="post">
 | 
			
		||||
      <div class="input-field">
 | 
			
		||||
        <input required placeholder="Password" name="password" type="password" class="validate">
 | 
			
		||||
      </div>
 | 
			
		||||
      <button class="btn mt-4" type="submit" name="action" value="login">Login</button>
 | 
			
		||||
	</form>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -22,6 +22,9 @@
 | 
			
		||||
          </ul>
 | 
			
		||||
	      <a href="{{ url_for('index.base') }}" class="brand-logo center">AYTA</a>
 | 
			
		||||
		  <ul id="nav-mobile" class="right">
 | 
			
		||||
		    {% if 'username' in session %}
 | 
			
		||||
		    <li><a href="{{ url_for('auth.logout') }}">Logged in as {{ session.username }} (click to logout)</a></li>
 | 
			
		||||
		    {% endif %}
 | 
			
		||||
            <li><a href="{{ url_for('search.base') }}">Search</a></li>
 | 
			
		||||
            <li><a href="{{ url_for('admin.base') }}">Help</a></li>
 | 
			
		||||
          </ul>
 | 
			
		||||
@@ -29,6 +32,13 @@
 | 
			
		||||
      </nav>
 | 
			
		||||
	</header>
 | 
			
		||||
	<main>
 | 
			
		||||
	  {% with messages = get_flashed_messages() %}
 | 
			
		||||
	  {% if messages %}
 | 
			
		||||
      {% for message in messages %}
 | 
			
		||||
	  <script>M.toast({text: '{{ message }}', displayLength: 5000, outDuration: 999, inDuration: 666})</script>
 | 
			
		||||
	  {% endfor %}
 | 
			
		||||
	  {% endif %}
 | 
			
		||||
	  {% endwith %}
 | 
			
		||||
	  <div class="container">
 | 
			
		||||
		  <noscript>Hey there, while I did build this in mind to minimize javascript usage, the experience would be much better if you would enable it!</noscript>
 | 
			
		||||
		  {% block content %}{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,8 @@
 | 
			
		||||
flask
 | 
			
		||||
flask-caching
 | 
			
		||||
flask-login
 | 
			
		||||
flask-oidc
 | 
			
		||||
flask-limiter
 | 
			
		||||
pymongo
 | 
			
		||||
yt-dlp
 | 
			
		||||
yt-dlp
 | 
			
		||||
argon2-cffi
 | 
			
		||||
		Reference in New Issue
	
	Block a user