mirror of
https://gitlab.com/SIGBUS/nyaa.git
synced 2024-12-22 10:00:01 +00:00
Implement torrent nuking ability for mods (#377)
* Implement torrent nuking ability for mods This deletes all torrents of a specific user. A current caveat is that it will delete both sukebei and nyaa torrents, but will only leave a log entry in the current flavour's log. Also did some bootstrap untangling on the user view page. * Per-flavour logging Hopefully this works. Maybe. * Tracker API: chunk into 100-element sublists * isort * Restrict nuking to superadmins Also do a lint.sh.
This commit is contained in:
parent
de1fd2f1bc
commit
4019343d50
|
@ -333,25 +333,32 @@ def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
|
||||||
|
|
||||||
|
|
||||||
def tracker_api(info_hashes, method):
|
def tracker_api(info_hashes, method):
|
||||||
url = app.config.get('TRACKER_API_URL')
|
api_url = app.config.get('TRACKER_API_URL')
|
||||||
if not url:
|
if not api_url:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
qs = []
|
# Split list into at most 100 elements
|
||||||
qs.append(('auth', app.config.get('TRACKER_API_AUTH')))
|
chunk_size = 100
|
||||||
qs.append(('method', method))
|
chunk_range = range(0, len(info_hashes), chunk_size)
|
||||||
|
chunked_info_hashes = (info_hashes[i:i + chunk_size] for i in chunk_range)
|
||||||
|
|
||||||
for infohash in info_hashes:
|
for info_hashes_chunk in chunked_info_hashes:
|
||||||
qs.append(('info_hash', infohash))
|
qs = [
|
||||||
|
('auth', app.config.get('TRACKER_API_AUTH')),
|
||||||
|
('method', method)
|
||||||
|
]
|
||||||
|
|
||||||
qs = urlencode(qs)
|
qs.extend(('info_hash', info_hash) for info_hash in info_hashes_chunk)
|
||||||
url += '?' + qs
|
|
||||||
try:
|
|
||||||
req = urlopen(url)
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return req.status == 200
|
api_url += '?' + urlencode(qs)
|
||||||
|
try:
|
||||||
|
req = urlopen(api_url)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if req.status != 200:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _delete_cached_torrent_file(torrent_id):
|
def _delete_cached_torrent_file(torrent_id):
|
||||||
|
|
|
@ -265,6 +265,7 @@ class DeleteForm(FlaskForm):
|
||||||
class BanForm(FlaskForm):
|
class BanForm(FlaskForm):
|
||||||
ban_user = SubmitField("Delete & Ban and Ban User")
|
ban_user = SubmitField("Delete & Ban and Ban User")
|
||||||
ban_userip = SubmitField("Delete & Ban and Ban User+IP")
|
ban_userip = SubmitField("Delete & Ban and Ban User+IP")
|
||||||
|
nuke = SubmitField("Delete & Ban all torrents")
|
||||||
unban = SubmitField("Unban")
|
unban = SubmitField("Unban")
|
||||||
|
|
||||||
_validator = DataRequired()
|
_validator = DataRequired()
|
||||||
|
|
|
@ -55,47 +55,70 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
{{ ban_form.csrf_token }}
|
<div class="row">
|
||||||
{% if user.is_banned %}
|
<div class="col-md-12">
|
||||||
This user is <strong>banned</strong>.<br>
|
{{ ban_form.csrf_token }}
|
||||||
{% endif %}
|
{% if user.is_banned %}
|
||||||
{% if ipbanned %}
|
This user is <strong>banned</strong>.
|
||||||
This user is <strong>ip banned</strong>.<br>
|
{% endif %}
|
||||||
{% endif %}
|
{% if ipbanned %}
|
||||||
{% if not user.is_banned and not bans %}
|
This user is <strong>ip banned</strong>.
|
||||||
This user is <strong>not banned</strong>.<br>
|
{% endif %}
|
||||||
{% endif %}
|
{% if not user.is_banned and not bans %}
|
||||||
<br>
|
This user is <strong>not banned</strong>.
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% if user.is_banned or bans %}
|
{% if user.is_banned or bans %}
|
||||||
<p>
|
<div class="row">
|
||||||
{% for ban in bans %}
|
<div class="col-md-12">
|
||||||
#{{ ban.id }}
|
<p>
|
||||||
by <a href="{{ url_for('users.view_user', user_name=ban.admin.username) }}">{{ ban.admin.username }}</a>
|
{% for ban in bans %}
|
||||||
for <span markdown-text-inline>{{ ban.reason }}</span>
|
#{{ ban.id }}
|
||||||
<br>
|
by <a href="{{ url_for('users.view_user', user_name=ban.admin.username) }}">{{ ban.admin.username }}</a>
|
||||||
{% endfor %}
|
for <span markdown-text-inline>{{ ban.reason }}</span>
|
||||||
</p>
|
{% endfor %}
|
||||||
{{ ban_form.unban(class="btn btn-info") }}
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{{ ban_form.unban(class="btn btn-info") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not user.is_banned or not ipbanned %}
|
{% if not user.is_banned or not ipbanned %}
|
||||||
{% if user.is_banned or bans %}
|
{% if user.is_banned or bans %}
|
||||||
<hr>
|
<hr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ render_field(ban_form.reason, class_="form-control", placeholder="Specify a ban reason.") }}<br>
|
<div class="row" style="margin-top:1.5em">
|
||||||
<div class="pull-left">
|
<div class="col-md-12">
|
||||||
{% if not user.is_banned %}
|
{{ render_field(ban_form.reason, class_="form-control", placeholder="Specify a ban reason.") }}<br>
|
||||||
{{ ban_form.ban_user(value="Ban User", class="btn btn-danger") }}
|
</div>
|
||||||
{% else %}
|
|
||||||
<button type="button" class="btn btn-danger disabled">Already banned</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pull-right">
|
<div class="row">
|
||||||
{% if not ipbanned %}
|
<div class="col-md-4 text-left">
|
||||||
{{ ban_form.ban_userip(value="Ban User+IP", class="btn btn-danger") }}
|
{% if not user.is_banned %}
|
||||||
{% else %}
|
{{ ban_form.ban_user(value="Ban User", class="btn btn-danger") }}
|
||||||
<button type="button" class="btn btn-danger disabled">Already IP banned</button>
|
{% else %}
|
||||||
{% endif %}
|
<button type="button" class="btn btn-danger disabled">Already banned</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
{% if not ipbanned %}
|
||||||
|
{{ ban_form.ban_userip(value="Ban User+IP", class="btn btn-danger") }}
|
||||||
|
{% else %}
|
||||||
|
<button type="button" class="btn btn-danger disabled">Already IP banned</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-right">
|
||||||
|
{% if g.user.is_superadmin %}
|
||||||
|
{{ ban_form.nuke(value="\U0001F4A3 Nuke Torrents", class="btn btn-danger") }}
|
||||||
|
{% else %}
|
||||||
|
<button type="button" class="btn btn-danger disabled">💣 Nuke Torrents</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
@ -106,10 +129,12 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h3>
|
<div class="row">
|
||||||
Browsing <span class="text-{{ user.userlevel_color }}" data-toggle="tooltip" title="{{ user.userlevel_str }}">{{ user.username }}</span>'{{ '' if user.username[-1] == 's' else 's' }} torrents
|
<h3>
|
||||||
</h3>
|
Browsing <span class="text-{{ user.userlevel_color }}" data-toggle="tooltip" title="{{ user.userlevel_str }}">{{ user.username }}</span>'{{ '' if user.username[-1] == 's' else 's' }} torrents
|
||||||
|
</h3>
|
||||||
|
|
||||||
{% include "search_results.html" %}
|
{% include "search_results.html" %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
|
@ -2,13 +2,14 @@ import binascii
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask_paginate import Pagination
|
from flask_paginate import Pagination
|
||||||
|
|
||||||
from itsdangerous import BadSignature, URLSafeSerializer
|
from itsdangerous import BadSignature, URLSafeSerializer
|
||||||
|
|
||||||
from nyaa import forms, models
|
from nyaa import backend, forms, models
|
||||||
from nyaa.extensions import db
|
from nyaa.extensions import db
|
||||||
from nyaa.search import (DEFAULT_MAX_SEARCH_RESULT, DEFAULT_PER_PAGE, SERACH_PAGINATE_DISPLAY_MSG,
|
from nyaa.search import (DEFAULT_MAX_SEARCH_RESULT, DEFAULT_PER_PAGE, SERACH_PAGINATE_DISPLAY_MSG,
|
||||||
_generate_query_string, search_db, search_elastic)
|
_generate_query_string, search_db, search_elastic)
|
||||||
|
@ -102,6 +103,41 @@ def view_user(user_name):
|
||||||
flask.flash(flask.Markup('User has been successfully {0}.'.format(action)), 'success')
|
flask.flash(flask.Markup('User has been successfully {0}.'.format(action)), 'success')
|
||||||
return flask.redirect(url)
|
return flask.redirect(url)
|
||||||
|
|
||||||
|
if flask.request.method == 'POST' and ban_form and ban_form.nuke.data:
|
||||||
|
if flask.g.user.is_superadmin:
|
||||||
|
nyaa_banned = 0
|
||||||
|
sukebei_banned = 0
|
||||||
|
info_hashes = []
|
||||||
|
for t in chain(user.nyaa_torrents, user.sukebei_torrents):
|
||||||
|
t.deleted = True
|
||||||
|
t.banned = True
|
||||||
|
info_hashes.append([t.info_hash])
|
||||||
|
db.session.add(t)
|
||||||
|
if isinstance(t, models.NyaaTorrent):
|
||||||
|
nyaa_banned += 1
|
||||||
|
else:
|
||||||
|
sukebei_banned += 1
|
||||||
|
|
||||||
|
if info_hashes:
|
||||||
|
backend.tracker_api(info_hashes, 'ban')
|
||||||
|
|
||||||
|
for log_flavour, num in ((models.NyaaAdminLog, nyaa_banned),
|
||||||
|
(models.SukebeiAdminLog, sukebei_banned)):
|
||||||
|
if num > 0:
|
||||||
|
log = "Nuked {0} torrents of [{1}]({2})".format(num,
|
||||||
|
user.username,
|
||||||
|
url)
|
||||||
|
adminlog = log_flavour(log=log, admin_id=flask.g.user.id)
|
||||||
|
db.session.add(adminlog)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
flask.flash('Torrents of {0} have been nuked.'.format(user.username),
|
||||||
|
'success')
|
||||||
|
return flask.redirect(url)
|
||||||
|
else:
|
||||||
|
flask.flash('Insufficient permissions to nuke.', 'danger')
|
||||||
|
return flask.redirect(url)
|
||||||
|
|
||||||
req_args = flask.request.args
|
req_args = flask.request.args
|
||||||
|
|
||||||
search_term = chain_get(req_args, 'q', 'term')
|
search_term = chain_get(req_args, 'q', 'term')
|
||||||
|
|
Loading…
Reference in a new issue