Compare commits

..

No commits in common. "e5fa3a4434b43b7a1b03334e073e5a63123bb4f7" and "d94a7e186456c4e9fbe7a409c5630d97055e5309" have entirely different histories.

19 changed files with 72 additions and 148 deletions

View file

@ -1,13 +1,13 @@
# NyaaV3 [![python](https://img.shields.io/badge/Python-3.14-3776AB.svg?style=flat&logo=python&logoColor=white)](https://www.python.org) ![Maintenance](https://img.shields.io/maintenance/yes/2025) # NyaaV3 [![python](https://img.shields.io/badge/Python-3.13-3776AB.svg?style=flat&logo=python&logoColor=white)](https://www.python.org) ![Maintenance](https://img.shields.io/maintenance/yes/2025)
## Setting up for development ## Setting up for development
This project uses Python 3.14. The codebase has been updated from the original Python 3.7 version to use modern Python features and updated dependencies. This project uses Python 3.13. The codebase has been updated from the original Python 3.7 version to use modern Python features and updated dependencies.
This guide assumes you are using Linux and are somewhat capable with the commandline. This guide assumes you are using Linux and are somewhat capable with the commandline.
Running Nyaa on Windows may be possible, but it's currently unsupported. Running Nyaa on Windows may be possible, but it's currently unsupported.
### Major changes from NyaaV2 ### Major changes from NyaaV2
- Updated from Python 3.7 to Python 3.14 - Updated from Python 3.7 to Python 3.13
- Updated all dependencies - Updated all dependencies to their latest versions
- Modernized code patterns for Flask 3.0 and SQLAlchemy 2.0 - Modernized code patterns for Flask 3.0 and SQLAlchemy 2.0
- Replaced deprecated Flask-Script, orderedset and `flask.Markup` with Flask CLI, orderly-set and markupsafe - Replaced deprecated Flask-Script, orderedset and `flask.Markup` with Flask CLI, orderly-set and markupsafe
- Implemented mail error handling - Implemented mail error handling
@ -23,13 +23,13 @@ The `tests` folder contains tests for the the `nyaa` module and the webserver. T
- Run `python dev.py test` while in the repository directory. - Run `python dev.py test` while in the repository directory.
### Setting up Pyenv ### Setting up Pyenv
pyenv eases the use of different Python versions, and as not all Linux distros offer 3.14 packages, it's right up our alley. pyenv eases the use of different Python versions, and as not all Linux distros offer 3.13 packages, it's right up our alley.
- Install [dependencies](https://github.com/pyenv/pyenv/wiki/Common-build-problems) - Install [dependencies](https://github.com/pyenv/pyenv/wiki/Common-build-problems)
- Install [pyenv](https://github.com/pyenv/pyenv/blob/master/README.md#installation) - Install [pyenv](https://github.com/pyenv/pyenv/blob/master/README.md#installation)
- Install [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv/blob/master/README.md) - Install [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv/blob/master/README.md)
- Install Python 3.14 with `pyenv` and create a virtualenv for the project: - Install Python 3.13 with `pyenv` and create a virtualenv for the project:
- `pyenv install 3.14.2` - `pyenv install 3.13.2`
- `pyenv virtualenv 3.14.2 nyaa` - `pyenv virtualenv 3.13.2 nyaa`
- `pyenv activate nyaa` - `pyenv activate nyaa`
- Install dependencies with `pip install -r requirements.txt` - Install dependencies with `pip install -r requirements.txt`
- Copy `config.example.py` into `config.py` - Copy `config.example.py` into `config.py`
@ -59,7 +59,7 @@ Continue below to learn about database migrations and enabling the advanced sear
## Database migrations ## Database migrations
> [!WARNING] > [!WARNING]
> The database migration feature has been updated but will no longer be supported in NyaaV3. > The database migration feature has been updated but will no longer be supported in NyaaV3.
- Database migrations are done using [Flask-Migrate](https://flask-migrate.readthedocs.io/), a wrapper around [Alembic](http://alembic.zzzcomputing.com/en/latest/). - Database migrations are done with [Flask-Migrate](https://flask-migrate.readthedocs.io/), a wrapper around [Alembic](http://alembic.zzzcomputing.com/en/latest/).
- The migration system has been updated to use Flask CLI instead of the deprecated Flask-Script. - The migration system has been updated to use Flask CLI instead of the deprecated Flask-Script.
- If someone has made changes in the database schema and included a new migration script: - If someone has made changes in the database schema and included a new migration script:
- If your database has never been marked by Alembic (you're on a database from before the migrations), run `python db_migrate.py db stamp head` before pulling the new migration script(s). - If your database has never been marked by Alembic (you're on a database from before the migrations), run `python db_migrate.py db stamp head` before pulling the new migration script(s).
@ -77,7 +77,7 @@ Continue below to learn about database migrations and enabling the advanced sear
## Setting up and enabling Elasticsearch ## Setting up and enabling Elasticsearch
### Installing Elasticsearch ### Installing Elasticsearch
- Install JDK with `sudo apt-get install openjdk-21-jdk` - Install JDK with `sudo apt-get install openjdk-8-jdk`
- Install Elasticsearch - Install Elasticsearch
- [From packages](https://www.elastic.co/guide/en/elasticsearch/reference/current/deb.html) - [From packages](https://www.elastic.co/guide/en/elasticsearch/reference/current/deb.html)
- Enable the service: - Enable the service:
@ -85,7 +85,6 @@ Continue below to learn about database migrations and enabling the advanced sear
- `sudo systemctl start elasticsearch.service` - `sudo systemctl start elasticsearch.service`
- or [simply extracting the archives and running the files](https://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html), if you don't feel like permanently installing ES - or [simply extracting the archives and running the files](https://www.elastic.co/guide/en/elasticsearch/reference/current/_installation.html), if you don't feel like permanently installing ES
- Run `curl -XGET 'localhost:9200'` and make sure ES is running - Run `curl -XGET 'localhost:9200'` and make sure ES is running
- You may need to set `xpack.security.enabled: false` in your `elasticsearch.yml` file if curl output is empty
- Install [Kibana](https://www.elastic.co/products/kibana) as a search debug frontend for ES (*optional*) - Install [Kibana](https://www.elastic.co/products/kibana) as a search debug frontend for ES (*optional*)
### Enabling MySQL Binlogging ### Enabling MySQL Binlogging
@ -100,10 +99,7 @@ Continue below to learn about database migrations and enabling the advanced sear
- Copy the example configuration (`es_sync_config.example.json`) as `es_sync_config.json` and adjust options in it to your liking (verify the connection options!) - Copy the example configuration (`es_sync_config.example.json`) as `es_sync_config.json` and adjust options in it to your liking (verify the connection options!)
- Connect to mysql as root - Connect to mysql as root
- Verify that the result of `SHOW VARIABLES LIKE 'binlog_format';` is `ROW` - Verify that the result of `SHOW VARIABLES LIKE 'binlog_format';` is `ROW`
- Execute `GRANT REPLICATION SLAVE ON *.* TO 'nyaav3'@'localhost';` to allow your configured user access to the binlog and one of the following: - Execute `GRANT REPLICATION SLAVE ON *.* TO 'username'@'localhost';` to allow your configured user access to the binlog
- For MySQL: `GRANT REPLICATION CLIENT ON *.* TO 'nyaauser'@'localhost';`
- For MariaDB: `GRANT BINLOG MONITOR ON *.* TO 'nyaauser'@'localhost';`
- Run `./create_es.sh` to create the indices for the torrents: `nyaa` and `sukebei`
### Setting up ES ### Setting up ES
- Run `./create_es.sh` to create the indices for the torrents: `nyaa` and `sukebei` - Run `./create_es.sh` to create the indices for the torrents: `nyaa` and `sukebei`
@ -120,8 +116,8 @@ However, take note that binglog is not necessary for simple ES testing and devel
### Setting up sync_es.py ### Setting up sync_es.py
`sync_es.py` keeps the Elasticsearch indices updated by reading the binlog and pushing the changes to the ES indices. `sync_es.py` keeps the Elasticsearch indices updated by reading the binlog and pushing the changes to the ES indices.
- Make sure `es_sync_config.json` is configured with the user you granted the `REPLICATION` permissions - Make sure `es_sync_config.json` is configured with the user you grated the `REPLICATION` permissions
- Run `python import_to_es.py /path/to/file.json` and copy the outputted JSON into the file specified by `save_loc` in your `es_sync_config.json` file - Run `import_to_es.py` and copy the outputted JSON into the file specified by `save_loc` in your `es_sync_config.json`
- Run `sync_es.py` as-is *or*, for actual deployment, set it up as a service and run it, preferably as the system/root - Run `sync_es.py` as-is *or*, for actual deployment, set it up as a service and run it, preferably as the system/root
- Make sure `sync_es.py` runs within the venv with the right dependencies! - Make sure `sync_es.py` runs within the venv with the right dependencies!

View file

@ -1,12 +0,0 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 2025-12 | :white_check_mark: |
| 2025-03 | :x: |
## Reporting a Vulnerability
Please report high risk security vulnerabilities privately.

View file

@ -88,7 +88,7 @@ RECAPTCHA_PRIVATE_KEY = '***'
BASE_DIR = os.path.abspath(os.path.dirname(__file__)) BASE_DIR = os.path.abspath(os.path.dirname(__file__))
if USE_MYSQL: if USE_MYSQL:
SQLALCHEMY_DATABASE_URI = ('mysql://nyaauser:nyaapass@localhost/nyaav3?charset=utf8mb4') SQLALCHEMY_DATABASE_URI = ('mysql://test:test123@localhost/nyaav3?charset=utf8mb4')
else: else:
SQLALCHEMY_DATABASE_URI = ( SQLALCHEMY_DATABASE_URI = (
'sqlite:///' + os.path.join(BASE_DIR, 'test.db') + '?check_same_thread=False') 'sqlite:///' + os.path.join(BASE_DIR, 'test.db') + '?check_same_thread=False')
@ -98,7 +98,7 @@ else:
########### ###########
# 'smtp' or 'mailgun' # 'smtp' or 'mailgun'
MAIL_BACKEND = 'smtp' MAIL_BACKEND = 'mailgun'
MAIL_FROM_ADDRESS = 'Sender Name <sender@domain.com>' MAIL_FROM_ADDRESS = 'Sender Name <sender@domain.com>'
# Mailgun settings # Mailgun settings
@ -180,7 +180,7 @@ ES_MAX_SEARCH_RESULT = 1000
# ES index name generally (nyaa or sukebei) # ES index name generally (nyaa or sukebei)
ES_INDEX_NAME = SITE_FLAVOR ES_INDEX_NAME = SITE_FLAVOR
# ES hosts # ES hosts
ES_HOSTS = ['http://localhost:9200'] ES_HOSTS = ['localhost:9200']
################ ################
## Commenting ## ## Commenting ##
@ -232,6 +232,5 @@ CACHE_THRESHOLD = 8192
# RATELIMIT_STORAGE_URL="redis://host:port" # RATELIMIT_STORAGE_URL="redis://host:port"
RATELIMIT_KEY_PREFIX="nyaaratelimit_" RATELIMIT_KEY_PREFIX="nyaaratelimit_"
# The commit hash is automatically shown in the footer if .git folder is present (see layout.html) # Use this to show the commit hash in the footer (see layout.html)
# Use this to manually show the hash
# COMMIT_HASH="[enter your commit hash here]"; # COMMIT_HASH="[enter your commit hash here]";

View file

@ -2,8 +2,8 @@
"save_loc": "/tmp/pos.json", "save_loc": "/tmp/pos.json",
"mysql_host": "127.0.0.1", "mysql_host": "127.0.0.1",
"mysql_port": 3306, "mysql_port": 3306,
"mysql_user": "nyaauser", "mysql_user": "nyaa",
"mysql_password": "nyaapass", "mysql_password": "some_password",
"database": "nyaav3", "database": "nyaav3",
"internal_queue_depth": 10000, "internal_queue_depth": 10000,
"es_chunk_size": 10000, "es_chunk_size": 10000,

View file

@ -13,13 +13,12 @@ import progressbar
from elasticsearch import Elasticsearch from elasticsearch import Elasticsearch
from elasticsearch.client import IndicesClient from elasticsearch.client import IndicesClient
from elasticsearch import helpers from elasticsearch import helpers
from sqlalchemy import text
from nyaa import create_app, models from nyaa import create_app, models
from nyaa.extensions import db from nyaa.extensions import db
app = create_app('config') app = create_app('config')
es = Elasticsearch(hosts=app.config['ES_HOSTS'], request_timeout=30) es = Elasticsearch(hosts=app.config['ES_HOSTS'], timeout=30)
ic = IndicesClient(es) ic = IndicesClient(es)
def pad_bytes(in_bytes, size): def pad_bytes(in_bytes, size):
@ -99,15 +98,14 @@ FLAVORS = [
# Get binlog status from mysql # Get binlog status from mysql
with app.app_context(): with app.app_context():
with db.engine.begin() as connection: master_status = db.engine.execute('SHOW MASTER STATUS;').fetchone()
master_status = connection.execute(text('SHOW MASTER STATUS;')).fetchone()
position_json = { position_json = {
'log_file': master_status[0], 'log_file': master_status[0],
'log_pos': master_status[1] 'log_pos': master_status[1]
} }
print('Save the following in the file configured in es_sync_config.json:') print('Save the following in the file configured in your ES sync config JSON:')
print(json.dumps(position_json)) print(json.dumps(position_json))
for flavor, torrent_class in FLAVORS: for flavor, torrent_class in FLAVORS:

View file

@ -352,7 +352,6 @@ a.text-purple:hover, a.text-purple:active, a.text-purple:focus {
#comment { #comment {
height: 8em; height: 8em;
min-height: 8em;
} }
.avatar { .avatar {
@ -455,8 +454,7 @@ h6:hover .header-anchor {
/* Dark theme */ /* Dark theme */
body.dark, body.dark {
body.dark .form-control {
color: #afafaf; color: #afafaf;
} }
@ -638,12 +636,6 @@ blockquote {
font-size: inherit; font-size: inherit;
} }
/* Make <mark>s a bit more prominent */
mark, .mark {
background-color: #fff6c9;
padding: .2em;
}
/* Hide and resize some things on tiny screens to improve usability. */ /* Hide and resize some things on tiny screens to improve usability. */
@media (max-width: 767px) { @media (max-width: 767px) {
.hdr-size, .hdr-date, .hdr-downloads, .hdr-size, .hdr-date, .hdr-downloads,

View file

@ -804,10 +804,8 @@
title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText; title = typeof this.options.title !== 'undefined' ? this.options.title : this.options.noneSelectedText;
} }
//strip all HTML tags in a DOM-safe way and trim the result, then unescape any escaped tags //strip all HTML tags and trim the result, then unescape any escaped tags
var $tmp = $('<div>').html(title); this.$button.attr('title', htmlUnescape($.trim(title.replace(/<[^>]*>?/g, ''))));
var plainTitle = $tmp.text();
this.$button.attr('title', htmlUnescape($.trim(plainTitle)));
this.$button.children('.filter-option').html(title); this.$button.children('.filter-option').html(title);
this.$element.trigger('rendered.bs.select'); this.$element.trigger('rendered.bs.select');

View file

@ -1,2 +0,0 @@
/*! markdown-it-ins 2.0.0 https://github.com//markdown-it/markdown-it-ins @license MIT */
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;n="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,n.markdownitIns=e()}}(function(){return function e(n,t,o){function r(s,f){if(!t[s]){if(!n[s]){var u="function"==typeof require&&require;if(!f&&u)return u(s,!0);if(i)return i(s,!0);var l=new Error("Cannot find module '"+s+"'");throw l.code="MODULE_NOT_FOUND",l}var p=t[s]={exports:{}};n[s][0].call(p.exports,function(e){var t=n[s][1][e];return r(t?t:e)},p,p.exports,e,n,t,o)}return t[s].exports}for(var i="function"==typeof require&&require,s=0;s<o.length;s++)r(o[s]);return r}({1:[function(e,n,t){"use strict";n.exports=function(e){function n(e,n){var t,o,r,i,s,f=e.pos,u=e.src.charCodeAt(f);if(n)return!1;if(43!==u)return!1;if(o=e.scanDelims(e.pos,!0),i=o.length,s=String.fromCharCode(u),2>i)return!1;for(i%2&&(r=e.push("text","",0),r.content=s,i--),t=0;i>t;t+=2)r=e.push("text","",0),r.content=s+s,e.delimiters.push({marker:u,jump:t,token:e.tokens.length-1,level:e.level,end:-1,open:o.can_open,close:o.can_close});return e.pos+=o.length,!0}function t(e){var n,t,o,r,i,s=[],f=e.delimiters,u=e.delimiters.length;for(n=0;u>n;n++)o=f[n],43===o.marker&&-1!==o.end&&(r=f[o.end],i=e.tokens[o.token],i.type="ins_open",i.tag="ins",i.nesting=1,i.markup="++",i.content="",i=e.tokens[r.token],i.type="ins_close",i.tag="ins",i.nesting=-1,i.markup="++",i.content="","text"===e.tokens[r.token-1].type&&"+"===e.tokens[r.token-1].content&&s.push(r.token-1));for(;s.length;){for(n=s.pop(),t=n+1;t<e.tokens.length&&"ins_close"===e.tokens[t].type;)t++;t--,n!==t&&(i=e.tokens[t],e.tokens[t]=e.tokens[n],e.tokens[n]=i)}}e.inline.ruler.before("emphasis","ins",n),e.inline.ruler2.before("emphasis","ins",t)}},{}]},{},[1])(1)});

View file

@ -1,2 +0,0 @@
/*! markdown-it-mark 2.0.0 https://github.com//markdown-it/markdown-it-mark @license MIT */
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var n;n="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,n.markdownitMark=e()}}(function(){return function e(n,t,o){function r(s,f){if(!t[s]){if(!n[s]){var u="function"==typeof require&&require;if(!f&&u)return u(s,!0);if(i)return i(s,!0);var l=new Error("Cannot find module '"+s+"'");throw l.code="MODULE_NOT_FOUND",l}var p=t[s]={exports:{}};n[s][0].call(p.exports,function(e){var t=n[s][1][e];return r(t?t:e)},p,p.exports,e,n,t,o)}return t[s].exports}for(var i="function"==typeof require&&require,s=0;s<o.length;s++)r(o[s]);return r}({1:[function(e,n,t){"use strict";n.exports=function(e){function n(e,n){var t,o,r,i,s,f=e.pos,u=e.src.charCodeAt(f);if(n)return!1;if(61!==u)return!1;if(o=e.scanDelims(e.pos,!0),i=o.length,s=String.fromCharCode(u),2>i)return!1;for(i%2&&(r=e.push("text","",0),r.content=s,i--),t=0;i>t;t+=2)r=e.push("text","",0),r.content=s+s,e.delimiters.push({marker:u,jump:t,token:e.tokens.length-1,level:e.level,end:-1,open:o.can_open,close:o.can_close});return e.pos+=o.length,!0}function t(e){var n,t,o,r,i,s=[],f=e.delimiters,u=e.delimiters.length;for(n=0;u>n;n++)o=f[n],61===o.marker&&-1!==o.end&&(r=f[o.end],i=e.tokens[o.token],i.type="mark_open",i.tag="mark",i.nesting=1,i.markup="==",i.content="",i=e.tokens[r.token],i.type="mark_close",i.tag="mark",i.nesting=-1,i.markup="==",i.content="","text"===e.tokens[r.token-1].type&&"="===e.tokens[r.token-1].content&&s.push(r.token-1));for(;s.length;){for(n=s.pop(),t=n+1;t<e.tokens.length&&"mark_close"===e.tokens[t].type;)t++;t--,n!==t&&(i=e.tokens[t],e.tokens[t]=e.tokens[n],e.tokens[n]=i)}}e.inline.ruler.before("emphasis","mark",n),e.inline.ruler2.before("emphasis","mark",t)}},{}]},{},[1])(1)});

View file

@ -4,23 +4,8 @@ document.addEventListener("DOMContentLoaded", function(event) { // wait for cont
toggleDarkMode(); // toggle theme toggleDarkMode(); // toggle theme
}); });
// needs to be done here as <body> is not available when the script in the head runs // needs to be done here as <body> is not available when the script in the head runs
if (typeof(Storage) !== 'undefined' && localStorage.getItem('theme') === 'dark') { if (typeof(Storage) !== 'undefined' && localStorage.getItem('theme') === 'dark')
document.body.classList.add('dark'); document.body.classList.add('dark');
} else if (window.matchMedia) {
const darkMedia = window.matchMedia('(prefers-color-scheme: dark)');
if (darkMedia.matches)
document.body.classList.add('dark');
// Some operating systems switch to dark mode only during the evening/night:
darkMedia.addEventListener('change', function(event) {
// Do not overwrite if the user made a manual choice:
if (localStorage.getItem('theme') === null) {
if (event.matches)
document.body.classList.add('dark');
else
document.body.classList.remove('dark');
}
});
}
}); });
@ -225,7 +210,7 @@ var markdownOptions = {
linkify: true, linkify: true,
typographer: true typographer: true
} }
var markdown = window.markdownit(markdownOptions).use(window.markdownitIns).use(window.markdownitMark); var markdown = window.markdownit(markdownOptions);
markdown.renderer.rules.table_open = function (tokens, idx) { markdown.renderer.rules.table_open = function (tokens, idx) {
// Format tables nicer (bootstrap). Force auto-width (default is 100%) // Format tables nicer (bootstrap). Force auto-width (default is 100%)
return '<table class="table table-striped table-bordered" style="width: auto;">'; return '<table class="table table-striped table-bordered" style="width: auto;">';
@ -283,9 +268,6 @@ document.addEventListener("DOMContentLoaded", function() {
// Info bubble stuff // Info bubble stuff
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
var bubble = document.getElementById('infobubble'); var bubble = document.getElementById('infobubble');
if (!bubble) {
return;
}
if (Number(localStorage.getItem('infobubble_dismiss_ts')) < Number(bubble.dataset.ts)) { if (Number(localStorage.getItem('infobubble_dismiss_ts')) < Number(bubble.dataset.ts)) {
bubble.removeAttribute('hidden'); bubble.removeAttribute('hidden');
} }

View file

@ -35,7 +35,7 @@
{% endif %} {% endif %}
<div class="markdown-editor" id="{{ field_name }}-markdown-editor" data-field-name="{{ field_name }}"> <div class="markdown-editor" id="{{ field_name }}-markdown-editor" data-field-name="{{ field_name }}">
{{ field.label(class='control-label') }} {{ field.label(class='control-label') }}
<a href="{{ url_for('site.help', _anchor='styling') }}" class="small" target="_blank">Markdown supported</a> <a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" class="small" target="_blank">Markdown supported</a>
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"> <li role="presentation" class="active">
<a href="#{{ field_name }}-tab" role="tab" data-toggle="tab"> <a href="#{{ field_name }}-tab" role="tab" data-toggle="tab">

View file

@ -85,23 +85,6 @@
to have an image embedded in your comment or description. Note the <kbd>!</kbd> to have an image embedded in your comment or description. Note the <kbd>!</kbd>
exclamation mark at the beginning, denoting that this link is an image. exclamation mark at the beginning, denoting that this link is an image.
</div> </div>
<div>
Quick examples:
</div>
<div>
<ul>
<li><kbd>*italic text*</kbd> &rarr; <em>italic text</em></li>
<li><kbd>**bold text**</kbd> &rarr; <strong>bold text</strong></li>
<li><kbd>++underlined text++</kbd> &rarr; <ins>underlined text</ins></li>
<li><kbd>==marked text==</kbd> &rarr; <mark>marked text</mark></li>
<li><kbd>[Front page]({{url_for('main.home')}})</kbd> &rarr; <a href="{{url_for('main.home')}}">Front page</a></li>
<li><kbd>[Spoilers](# "The spider-girl kills everyone")</kbd> &rarr; <a href="#" title="The spider-girl kills everyone">Spoilers</a></li>
<li><kbd># Header 1</kbd> &rarr; <h1 style="display: inline-block;">Header 1</h1></li>
<li><kbd>## Header 2</kbd> &rarr; <h2 style="display: inline-block;">Header 2</h2></li>
<li><kbd>### Header 3</kbd> &rarr; <h3 style="display: inline-block;">Header 3</h3></li>
<li><kbd>#### Header 4</kbd> &rarr; <h4 style="display: inline-block;">Header 4</h4></li>
</ul>
</div>
{{ linkable_header("Changing Your User's Avatar", "avatar") }} {{ linkable_header("Changing Your User's Avatar", "avatar") }}
<div> <div>

View file

@ -44,9 +44,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha256-U5ZEeKfGNOja007MMD3YBI0A3OSZOQbeG6z2f2Y0hu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/8.3.1/markdown-it.min.js" integrity="sha256-3WZyZQOe+ql3pLo90lrkRtALrlniGdnf//gRpW0UQks=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/8.3.1/markdown-it.min.js" integrity="sha256-3WZyZQOe+ql3pLo90lrkRtALrlniGdnf//gRpW0UQks=" crossorigin="anonymous"></script>
<!-- markdown-it extensions (self-hosted because no cdnjs releases) -->
<script src="{{ static_cachebuster('js/lib/markdown-it-ins.min.js') }}"></script>
<script src="{{ static_cachebuster('js/lib/markdown-it-mark.min.js') }}"></script>
<!-- Modified to not apply border-radius to selectpickers and stuff so our navbar looks cool --> <!-- Modified to not apply border-radius to selectpickers and stuff so our navbar looks cool -->
{% assets "bs_js" %} {% assets "bs_js" %}
<script src="{{ static_cachebuster('js/bootstrap-select.min.js') }}"></script> <script src="{{ static_cachebuster('js/bootstrap-select.min.js') }}"></script>

View file

@ -5,7 +5,7 @@
<meta property="og:description" content="{{ category_name(torrent.sub_category.id_as_string) }} | {{ torrent.filesize | filesizeformat(True) }} | Uploaded by {{ uploader_name }} on {{ torrent.created_time.strftime('%Y-%m-%d') }}"> <meta property="og:description" content="{{ category_name(torrent.sub_category.id_as_string) }} | {{ torrent.filesize | filesizeformat(True) }} | Uploaded by {{ uploader_name }} on {{ torrent.created_time.strftime('%Y-%m-%d') }}">
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% from "_formhelpers.html" import render_field, render_markdown_editor %} {% from "_formhelpers.html" import render_field %}
<div class="panel panel-{% if torrent.deleted %}deleted{% elif torrent.remake %}danger{% elif torrent.trusted %}success{% else %}default{% endif %}"> <div class="panel panel-{% if torrent.deleted %}deleted{% elif torrent.remake %}danger{% elif torrent.trusted %}success{% else %}default{% endif %}">
<div class="panel-heading"{% if torrent.hidden %} style="background-color: darkgray;"{% endif %}> <div class="panel-heading"{% if torrent.hidden %} style="background-color: darkgray;"{% endif %}>
<h3 class="panel-title"> <h3 class="panel-title">
@ -177,7 +177,7 @@
<div class="row comment-body"> <div class="row comment-body">
{# Escape newlines into html entities because CF strips blank newlines #} {# Escape newlines into html entities because CF strips blank newlines #}
<div markdown-text class="comment-content" id="torrent-comment{{ comment.id }}">{{- comment.text | escape | replace('\r\n', '\n') | replace('\n', '&#10;'|safe) -}}</div> <div markdown-text class="comment-content" id="torrent-comment{{ comment.id }}">{{- comment.text | escape | replace('\r\n', '\n') | replace('\n', '&#10;'|safe) -}}</div>
{% if g.user.id == comment.user_id and comment_form and not comment.editing_limit_exceeded and (not torrent.comment_locked or g.user.is_moderator) %} {% if g.user.id == comment.user_id and comment_form %}
<form class="edit-comment-box" action="{{ url_for('torrents.edit_comment', torrent_id=torrent.id, comment_id=comment.id) }}" method="POST"> <form class="edit-comment-box" action="{{ url_for('torrents.edit_comment', torrent_id=torrent.id, comment_id=comment.id) }}" method="POST">
{{ comment_form.csrf_token }} {{ comment_form.csrf_token }}
<div class="form-group"> <div class="form-group">
@ -225,7 +225,7 @@
{{ comment_form.csrf_token }} {{ comment_form.csrf_token }}
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
{{ render_markdown_editor(comment_form.comment) }} {{ render_field(comment_form.comment, class_='form-control') }}
</div> </div>
</div> </div>
{% if config.USE_RECAPTCHA and g.user.age < config['ACCOUNT_RECAPTCHA_AGE'] %} {% if config.USE_RECAPTCHA and g.user.age < config['ACCOUNT_RECAPTCHA_AGE'] %}

View file

@ -219,6 +219,4 @@ def render_rss(label, query, use_elastic, magnet_links=False):
response.headers['Content-Type'] = 'application/xml' response.headers['Content-Type'] = 'application/xml'
# Cache for an hour # Cache for an hour
response.headers['Cache-Control'] = 'max-age={}'.format(1 * 5 * 60) response.headers['Cache-Control'] = 'max-age={}'.format(1 * 5 * 60)
# Allow cross origin access
response.headers['Access-Control-Allow-Origin'] = '*'
return response return response

View file

@ -333,8 +333,6 @@ def download_torrent(torrent_id):
resp.headers['Content-Type'] = 'application/x-bittorrent' resp.headers['Content-Type'] = 'application/x-bittorrent'
resp.headers['Content-Disposition'] = disposition resp.headers['Content-Disposition'] = disposition
resp.headers['Content-Length'] = torrent_file_size resp.headers['Content-Length'] = torrent_file_size
# Allow cross origin access
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp return resp

View file

@ -1,16 +1,16 @@
alembic==1.17.2 alembic==1.14.1
appdirs==1.4.4 appdirs==1.4.4
argon2-cffi==25.1.0 argon2-cffi==23.1.0
autopep8==2.3.2 autopep8==2.3.2
blinker==1.9.0 blinker==1.9.0
cffi==2.0.0 cffi==1.17.1
click==8.3.1 click==8.1.8
dnspython==2.8.0 dnspython==2.7.0
elasticsearch==8.19.2 elasticsearch==8.17.1
elasticsearch-dsl==8.18.0 elasticsearch-dsl==8.17.1
flake8==7.3.0 flake8==7.1.2
flake8-isort==7.0.0 flake8-isort==6.1.2
Flask==3.1.2 Flask==3.1.0
Flask-Assets==2.1.0 Flask-Assets==2.1.0
Flask-DebugToolbar==0.16.0 Flask-DebugToolbar==0.16.0
Flask-Migrate==4.1.0 Flask-Migrate==4.1.0
@ -18,42 +18,42 @@ flask-paginate==2024.4.12
# Flask-Script removed as it's deprecated and replaced with Flask CLI # Flask-Script removed as it's deprecated and replaced with Flask CLI
Flask-SQLAlchemy==3.1.1 Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.2 Flask-WTF==1.2.2
gevent==25.9.1 gevent==24.11.1
greenlet==3.3.0 greenlet==3.1.1
isort==7.0.0 isort==6.0.1
itsdangerous==2.2.0 itsdangerous==2.2.0
Jinja2==3.1.6 Jinja2==3.1.5
Mako==1.3.10 Mako==1.3.9
MarkupSafe==3.0.3 MarkupSafe==3.0.2
mysql-replication==1.0.12 mysql-replication==1.0.9
mysqlclient==2.2.7 mysqlclient==2.2.7
# orderedset removed as it's deprecated and replaced with Flask CLI # orderedset removed as it's deprecated and replaced with Flask CLI
orderly-set==5.5.0 orderly-set==5.3.0
packaging==25.0 packaging==24.2
passlib==1.7.4 passlib==1.7.4
progressbar33==2.4 progressbar33==2.4
py==1.11.0 py==1.11.0
pycodestyle==2.14.0 pycodestyle==2.12.1
pycparser==2.23 pycparser==2.22
PyMySQL==1.1.2 PyMySQL==1.1.1
pyparsing==3.2.5 pyparsing==3.2.1
pytest==9.0.2 pytest==8.3.4
python-dateutil==2.9.0.post0 python-dateutil==2.9.0
python-editor==1.0.4 python-editor==1.0.4
python-utils==3.9.1 python-utils==3.9.1
requests==2.32.5 requests==2.32.3
SQLAlchemy==2.0.45 SQLAlchemy==2.0.38
SQLAlchemy-FullText-Search==0.3.0 SQLAlchemy-FullText-Search==0.3.0
SQLAlchemy-Utils==0.42.1 SQLAlchemy-Utils==0.41.2
statsd==4.0.1 statsd==4.0.1
urllib3==2.6.2 urllib3==2.3.0
uWSGI==2.0.31 uWSGI==2.0.28
redis==7.1.0 redis==5.2.1
webassets==3.0.0 webassets==2.0
Werkzeug==3.1.4 Werkzeug==3.1.3
WTForms==3.2.1 WTForms==3.2.1
Flask-Caching==2.3.1 Flask-Caching==2.3.1
Flask-Limiter==4.1.1 Flask-Limiter==3.10.1
mypy==1.19.1 mypy==1.15.0
typing-extensions==4.15.0 typing-extensions==4.12.2
email-validator==2.3.0 email-validator==2.2.0

View file

@ -68,9 +68,9 @@ stats = StatsClient('localhost', 8125, prefix="sync_es")
SAVE_LOC = config.get('save_loc', "/tmp/pos.json") SAVE_LOC = config.get('save_loc', "/tmp/pos.json")
MYSQL_HOST = config.get('mysql_host', '127.0.0.1') MYSQL_HOST = config.get('mysql_host', '127.0.0.1')
MYSQL_PORT = config.get('mysql_port', 3306) MYSQL_PORT = config.get('mysql_port', 3306)
MYSQL_USER = config.get('mysql_user', 'nyaauser') MYSQL_USER = config.get('mysql_user', 'root')
MYSQL_PW = config.get('mysql_password', 'nyaapass') MYSQL_PW = config.get('mysql_password', 'dunnolol')
NT_DB = config.get('database', 'nyaav3') NT_DB = config.get('database', 'nyaav2')
INTERNAL_QUEUE_DEPTH = config.get('internal_queue_depth', 10000) INTERNAL_QUEUE_DEPTH = config.get('internal_queue_depth', 10000)
ES_CHUNK_SIZE = config.get('es_chunk_size', 10000) ES_CHUNK_SIZE = config.get('es_chunk_size', 10000)
# seconds since no events happening to flush to es. remember this also # seconds since no events happening to flush to es. remember this also
@ -263,7 +263,7 @@ class EsPoster(ExitingThread):
self.flush_interval = flush_interval self.flush_interval = flush_interval
def run_happy(self): def run_happy(self):
es = Elasticsearch(hosts=app.config['ES_HOSTS'], request_timeout=30) es = Elasticsearch(hosts=app.config['ES_HOSTS'], timeout=30)
last_save = time.time() last_save = time.time()
since_last = 0 since_last = 0

View file

@ -5,4 +5,3 @@ udp://open.stealth.si:80/announce
udp://tracker.opentrackr.org:1337/announce udp://tracker.opentrackr.org:1337/announce
udp://tracker.coppersurfer.tk:6969/announce udp://tracker.coppersurfer.tk:6969/announce
udp://exodus.desync.com:6969/announce udp://exodus.desync.com:6969/announce
wss://tracker.openwebtorrent.com