Compare commits

...

28 commits

Author SHA1 Message Date
sb745
e5fa3a4434
Updated to Python 3.14.2
Some checks failed
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Merge pull request #14 from sb745/py3.14
2025-12-22 14:41:56 +02:00
sb745
e97b3add03
Added Markdown underline & highlight
Merge pull request #13 from sb745/markdown-ins-and-mark
2025-12-22 14:41:17 +02:00
sb745
8f1b59d705
Fix form text color on dark theme
Merge pull request #12 from sfan5/sfan5-patch-1
2025-12-22 14:40:54 +02:00
sb745
1823254ce7
Added automatic dark mode detection
Merge pull request #11 from wsldankers/dark-mode-detection
2025-12-22 14:40:21 +02:00
sb745
42ec86b8d7
Allow cross origin access for RSS
Merge pull request #10 from Kirdow/master
2025-12-22 14:39:55 +02:00
sb745
4270ca8dd7
View template improvements
Merge pull request #9 from CounterPillow/view-template-improvements
2025-12-22 14:39:07 +02:00
sb745
9250b742e1
Add websocket tracker 2025-12-22 14:36:50 +02:00
sb745
d87adc528c
Incomplete multi-character sanitization fix 2025-12-22 14:35:02 +02:00
sb745
e50efa5449
Added commit hash explanation 2025-12-22 13:58:52 +02:00
sb745
6f4634c0f6
Merge branch 'master' into markdown-ins-and-mark 2025-12-22 06:08:01 +02:00
sb745
9630016e4e
Resolve conflict 2025-12-22 06:03:58 +02:00
sb745
fdaa324135
Merge branch 'py3.14' of https://github.com/sb745/NyaaV3 into py3.14 2025-12-22 05:54:44 +02:00
sb745
ff4dc572f3
Added security policy 2025-12-22 05:54:03 +02:00
sb745
5bb38ce959
Incomplete multi-character sanitization fix
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-12-22 04:14:02 +02:00
sb745
27cc656842
Incomplete multi-character sanitization fix
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-12-22 04:08:31 +02:00
sb745
653cf1af86
Minor wording change 2025-12-22 03:48:50 +02:00
sb745
4fe13c1df3
Fixed Elasticsearch 2025-12-22 03:35:11 +02:00
sb745
0c7733ca83
Update dependencies and config example 2025-12-22 02:13:36 +02:00
sfan5
d207184183
Fix form text color on dark theme 2024-07-02 23:28:46 +02:00
Dennis Rönn
f5a7820e44
Merge pull request #2 from ThaUnknown/patch-2 2021-10-12 21:15:52 +02:00
Cas
146e629895 Allow cross origin access for .torrent files 2021-10-12 21:04:11 +02:00
Wessel Dankers
b1ae89fa08
Add automatic dark mode detection
Also switches automatically whenever the OS does
2021-07-01 17:10:21 +02:00
Kirdow
e9cd4598d0
Allow cross origin access for RSS
Allows browser-based code to access the RSS feed without browser interrupting the connection.
2021-01-30 02:09:43 +01:00
ThaUnknown
4b3d506e15 added openwebtorrent websocket tracker 2020-09-24 23:36:18 +02:00
Nicolas F
97aef583d6 view: various small improvements
1. Don't render an edit form for comments that can no longer be edited.
2. Use the markdown editor that has a preview, because duh.
3. Don't make the infobubble JS error out on pages without the infobubble.
2019-10-12 20:07:17 +02:00
TheAMM
9106977fad Change "Markdown supported" to link to our own help
...because <ins> and <mark>.
2017-12-10 23:08:44 +02:00
TheAMM
f898ad3d56 Add more markdown examples to help
Because we now have extra non-GFM features (ins, mark)
2017-12-10 23:02:23 +02:00
TheAMM
c2513fd931 Add underline and hilight support to Markdown renderer
++underline++ and ==hilight==, also known as <ins> and <mark>

The markdown-it-ins and markdown-it-mark are not available
on CF's cdnjs, so selfhost them. (They are on jsdeliver, but
it's better to rely on our own host than add another 3rd party.)

Improves visibility (color) of <mark> a bit as well.
2017-12-10 22:59:18 +02:00
19 changed files with 148 additions and 72 deletions

View file

@ -1,13 +1,13 @@
# 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)
# 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)
## Setting up for development
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 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 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.
### Major changes from NyaaV2
- Updated from Python 3.7 to Python 3.13
- Updated all dependencies to their latest versions
- Updated from Python 3.7 to Python 3.14
- Updated all dependencies
- 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
- 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.
### Setting up Pyenv
pyenv eases the use of different Python versions, and as not all Linux distros offer 3.13 packages, it's right up our alley.
pyenv eases the use of different Python versions, and as not all Linux distros offer 3.14 packages, it's right up our alley.
- 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-virtualenv](https://github.com/pyenv/pyenv-virtualenv/blob/master/README.md)
- Install Python 3.13 with `pyenv` and create a virtualenv for the project:
- `pyenv install 3.13.2`
- `pyenv virtualenv 3.13.2 nyaa`
- Install Python 3.14 with `pyenv` and create a virtualenv for the project:
- `pyenv install 3.14.2`
- `pyenv virtualenv 3.14.2 nyaa`
- `pyenv activate nyaa`
- Install dependencies with `pip install -r requirements.txt`
- 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
> [!WARNING]
> The database migration feature has been updated but will no longer be supported in NyaaV3.
- Database migrations are done with [Flask-Migrate](https://flask-migrate.readthedocs.io/), a wrapper around [Alembic](http://alembic.zzzcomputing.com/en/latest/).
- Database migrations are done using [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.
- 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).
@ -77,7 +77,7 @@ Continue below to learn about database migrations and enabling the advanced sear
## Setting up and enabling Elasticsearch
### Installing Elasticsearch
- Install JDK with `sudo apt-get install openjdk-8-jdk`
- Install JDK with `sudo apt-get install openjdk-21-jdk`
- Install Elasticsearch
- [From packages](https://www.elastic.co/guide/en/elasticsearch/reference/current/deb.html)
- Enable the service:
@ -85,6 +85,7 @@ Continue below to learn about database migrations and enabling the advanced sear
- `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
- 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*)
### Enabling MySQL Binlogging
@ -99,7 +100,10 @@ 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!)
- Connect to mysql as root
- Verify that the result of `SHOW VARIABLES LIKE 'binlog_format';` is `ROW`
- Execute `GRANT REPLICATION SLAVE ON *.* TO 'username'@'localhost';` to allow your configured user access to the binlog
- Execute `GRANT REPLICATION SLAVE ON *.* TO 'nyaav3'@'localhost';` to allow your configured user access to the binlog and one of the following:
- 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
- Run `./create_es.sh` to create the indices for the torrents: `nyaa` and `sukebei`
@ -116,8 +120,8 @@ However, take note that binglog is not necessary for simple ES testing and devel
### 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.
- Make sure `es_sync_config.json` is configured with the user you grated the `REPLICATION` permissions
- Run `import_to_es.py` and copy the outputted JSON into the file specified by `save_loc` in your `es_sync_config.json`
- Make sure `es_sync_config.json` is configured with the user you granted 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 `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!

12
SECURITY.md Normal file
View file

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

View file

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

View file

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

View file

@ -352,6 +352,7 @@ a.text-purple:hover, a.text-purple:active, a.text-purple:focus {
#comment {
height: 8em;
min-height: 8em;
}
.avatar {
@ -454,7 +455,8 @@ h6:hover .header-anchor {
/* Dark theme */
body.dark {
body.dark,
body.dark .form-control {
color: #afafaf;
}
@ -636,6 +638,12 @@ blockquote {
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. */
@media (max-width: 767px) {
.hdr-size, .hdr-date, .hdr-downloads,

View file

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

View file

@ -0,0 +1,2 @@
/*! 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

@ -0,0 +1,2 @@
/*! 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,8 +4,23 @@ document.addEventListener("DOMContentLoaded", function(event) { // wait for cont
toggleDarkMode(); // toggle theme
});
// 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');
} 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');
}
});
}
});
@ -210,7 +225,7 @@ var markdownOptions = {
linkify: true,
typographer: true
}
var markdown = window.markdownit(markdownOptions);
var markdown = window.markdownit(markdownOptions).use(window.markdownitIns).use(window.markdownitMark);
markdown.renderer.rules.table_open = function (tokens, idx) {
// Format tables nicer (bootstrap). Force auto-width (default is 100%)
return '<table class="table table-striped table-bordered" style="width: auto;">';
@ -268,6 +283,9 @@ document.addEventListener("DOMContentLoaded", function() {
// Info bubble stuff
document.addEventListener("DOMContentLoaded", function() {
var bubble = document.getElementById('infobubble');
if (!bubble) {
return;
}
if (Number(localStorage.getItem('infobubble_dismiss_ts')) < Number(bubble.dataset.ts)) {
bubble.removeAttribute('hidden');
}

View file

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

View file

@ -85,6 +85,23 @@
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.
</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") }}
<div>

View file

@ -44,6 +44,9 @@
<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/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 -->
{% assets "bs_js" %}
<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') }}">
{% endblock %}
{% block body %}
{% from "_formhelpers.html" import render_field %}
{% from "_formhelpers.html" import render_field, render_markdown_editor %}
<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 %}>
<h3 class="panel-title">
@ -177,7 +177,7 @@
<div class="row comment-body">
{# 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>
{% if g.user.id == comment.user_id and comment_form %}
{% 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) %}
<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 }}
<div class="form-group">
@ -225,7 +225,7 @@
{{ comment_form.csrf_token }}
<div class="row">
<div class="col-md-12">
{{ render_field(comment_form.comment, class_='form-control') }}
{{ render_markdown_editor(comment_form.comment) }}
</div>
</div>
{% if config.USE_RECAPTCHA and g.user.age < config['ACCOUNT_RECAPTCHA_AGE'] %}

View file

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

View file

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

View file

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

View file

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

View file

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