implemented BEP 36 (Torrent RSS feeds) in magneticow
http://www.bittorrent.org/beps/bep_0036.html
This commit is contained in:
parent
d877ba2475
commit
65dc6737e1
@ -16,6 +16,7 @@ import collections
|
|||||||
import functools
|
import functools
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import os
|
import os
|
||||||
@ -41,7 +42,7 @@ magneticod_db = None
|
|||||||
# Adapted from: http://flask.pocoo.org/snippets/8/
|
# Adapted from: http://flask.pocoo.org/snippets/8/
|
||||||
# (c) Copyright 2010 - 2017 by Armin Ronacher
|
# (c) Copyright 2010 - 2017 by Armin Ronacher
|
||||||
# BEGINNING OF THE COPYRIGHTED CONTENT
|
# BEGINNING OF THE COPYRIGHTED CONTENT
|
||||||
def check_auth(supplied_username, supplied_password):
|
def is_authorized(supplied_username, supplied_password):
|
||||||
""" This function is called to check if a username / password combination is valid. """
|
""" This function is called to check if a username / password combination is valid. """
|
||||||
# Because we do monkey-patch! [in magneticow.__main__.py:main()]
|
# Because we do monkey-patch! [in magneticow.__main__.py:main()]
|
||||||
for username, password in app.arguments.user: # pylint: disable=maybe-no-member
|
for username, password in app.arguments.user: # pylint: disable=maybe-no-member
|
||||||
@ -64,13 +65,24 @@ def requires_auth(f):
|
|||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
auth = flask.request.authorization
|
auth = flask.request.authorization
|
||||||
if not auth or not check_auth(auth.username, auth.password):
|
if not auth or not is_authorized(auth.username, auth.password):
|
||||||
return authenticate()
|
return authenticate()
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated
|
return decorated
|
||||||
# END OF THE COPYRIGHTED CONTENT
|
# END OF THE COPYRIGHTED CONTENT
|
||||||
|
|
||||||
|
|
||||||
|
def generate_feed_hash(username: str, password: str, filter_: str) -> str:
|
||||||
|
"""
|
||||||
|
Deterministically generates the feed hash from given username, password, and filter.
|
||||||
|
Hash is the hex encoding of the SHA256 sum.
|
||||||
|
:param username:
|
||||||
|
:param password:
|
||||||
|
:param filter_:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return hashlib.sha256((username + "\0" + password + "\0" + filter_).encode()).digest().hex()
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@requires_auth
|
@requires_auth
|
||||||
def home_page():
|
def home_page():
|
||||||
@ -128,6 +140,9 @@ def search_torrents():
|
|||||||
else:
|
else:
|
||||||
context["next_page_exists"] = True
|
context["next_page_exists"] = True
|
||||||
|
|
||||||
|
username, password = flask.request.authorization.username, flask.request.authorization.password
|
||||||
|
context["subscription_url"] = "/feed?filter=%s&hash=%s" % (search, generate_feed_hash(username, password, search))
|
||||||
|
|
||||||
return flask.render_template("torrents.html", **context)
|
return flask.render_template("torrents.html", **context)
|
||||||
|
|
||||||
|
|
||||||
@ -158,6 +173,9 @@ def newest_torrents():
|
|||||||
else:
|
else:
|
||||||
context["next_page_exists"] = True
|
context["next_page_exists"] = True
|
||||||
|
|
||||||
|
username, password = flask.request.authorization.username, flask.request.authorization.password
|
||||||
|
context["subscription_url"] = "/feed?filter=&hash=%s" % (generate_feed_hash(username, password, ""),)
|
||||||
|
|
||||||
return flask.render_template("torrents.html", **context)
|
return flask.render_template("torrents.html", **context)
|
||||||
|
|
||||||
|
|
||||||
@ -241,6 +259,56 @@ def statistics():
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/feed")
|
||||||
|
def feed():
|
||||||
|
filter_ = flask.request.args["filter"]
|
||||||
|
hash_ = flask.request.args["hash"]
|
||||||
|
# Check for all possible users who might be requesting.
|
||||||
|
# pylint disabled: because we do monkey-patch! [in magneticow.__main__.py:main()]
|
||||||
|
for username, password in app.arguments.user: # pylint: disable=maybe-no-member
|
||||||
|
if generate_feed_hash(username, password, filter_) == hash_:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return flask.Response(
|
||||||
|
"Could not verify your access level for that URL (wrong hash).\n",
|
||||||
|
401
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
|
||||||
|
if filter_:
|
||||||
|
context["title"] = "`%s` - magneticow" % (filter_,)
|
||||||
|
with magneticod_db:
|
||||||
|
cur = magneticod_db.execute(
|
||||||
|
"SELECT "
|
||||||
|
" name, "
|
||||||
|
" info_hash "
|
||||||
|
"FROM torrents "
|
||||||
|
"INNER JOIN ("
|
||||||
|
" SELECT docid AS id, rank(matchinfo(fts_torrents, 'pcnxal')) AS rank "
|
||||||
|
" FROM fts_torrents "
|
||||||
|
" WHERE name MATCH ? "
|
||||||
|
" ORDER BY rank ASC"
|
||||||
|
" LIMIT 50"
|
||||||
|
") AS ranktable USING(id);",
|
||||||
|
(filter_, )
|
||||||
|
)
|
||||||
|
context["items"] = [{"title": r[0], "info_hash": r[1].hex()} for r in cur]
|
||||||
|
else:
|
||||||
|
context["title"] = "The Newest Torrents - magneticow"
|
||||||
|
with magneticod_db:
|
||||||
|
cur = magneticod_db.execute(
|
||||||
|
"SELECT "
|
||||||
|
" name, "
|
||||||
|
" info_hash "
|
||||||
|
"FROM torrents "
|
||||||
|
"ORDER BY id DESC LIMIT 50"
|
||||||
|
)
|
||||||
|
context["items"] = [{"title": r[0], "info_hash": r[1].hex()} for r in cur]
|
||||||
|
|
||||||
|
return flask.render_template("feed.xml", **context), 200, {"Content-Type": "application/rss+xml; charset=utf-8"}
|
||||||
|
|
||||||
|
|
||||||
def initialize_magneticod_db() -> None:
|
def initialize_magneticod_db() -> None:
|
||||||
global magneticod_db
|
global magneticod_db
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ header form {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
13
magneticow/magneticow/templates/feed.xml
Normal file
13
magneticow/magneticow/templates/feed.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
|
||||||
|
<rss version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
{% for item in items %}
|
||||||
|
<item>
|
||||||
|
<title>{{ item.title }}</title>
|
||||||
|
<guid>{{ item.info_hash }}</guid>
|
||||||
|
<enclosure url="magnet:?xt=urn:btih:{{ item.info_hash }}&dn={{ item.title }}" type="application/x-bittorrent" />
|
||||||
|
</item>
|
||||||
|
{% endfor %}
|
||||||
|
</channel>
|
||||||
|
</rss>
|
@ -14,6 +14,10 @@
|
|||||||
<form action="/torrents" method="get" autocomplete="off" role="search">
|
<form action="/torrents" method="get" autocomplete="off" role="search">
|
||||||
<input type="search" name="search" placeholder="Search the BitTorrent DHT" value="{{ search }}">
|
<input type="search" name="search" placeholder="Search the BitTorrent DHT" value="{{ search }}">
|
||||||
</form>
|
</form>
|
||||||
|
<div>
|
||||||
|
<a href="{{ subscription_url }}"><img src="{{ url_for('static', filename='assets/feed.png') }}"
|
||||||
|
alt="feed icon" title="subscribe" /> subscribe</a>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<table>
|
<table>
|
||||||
|
Loading…
Reference in New Issue
Block a user