2012-12-19 12:00:12 +00:00
|
|
|
from flask import Flask, request, g, redirect, url_for, abort, render_template, send_from_directory, safe_join
|
2012-09-17 01:19:57 +00:00
|
|
|
from flaskext.babel import Babel
|
2012-12-19 11:29:33 +00:00
|
|
|
from werkzeug.routing import BaseConverter
|
2012-06-03 01:06:09 +00:00
|
|
|
from docutils.core import publish_parts
|
2012-06-01 21:15:42 +00:00
|
|
|
import os.path
|
2012-06-01 23:15:41 +00:00
|
|
|
import os
|
2012-06-01 21:15:42 +00:00
|
|
|
|
2012-12-19 06:53:59 +00:00
|
|
|
from helpers import LazyView, Pagination
|
2012-06-01 21:15:42 +00:00
|
|
|
|
2012-12-17 21:59:41 +00:00
|
|
|
CURRENT_I2P_VERSION = '0.9.4'
|
|
|
|
|
2012-12-19 04:51:36 +00:00
|
|
|
CANONICAL_DOMAIN = 'www.i2p2.de'
|
|
|
|
|
2012-06-01 21:15:42 +00:00
|
|
|
TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'pages')
|
2012-06-01 23:15:41 +00:00
|
|
|
STATIC_DIR = os.path.join(os.path.dirname(__file__), 'static')
|
|
|
|
|
2012-06-03 01:06:09 +00:00
|
|
|
BLOG_DIR = os.path.join(os.path.dirname(__file__), 'blog')
|
2012-12-19 06:58:07 +00:00
|
|
|
MEETINGS_DIR = os.path.join(os.path.dirname(__file__), 'meetings/logs')
|
2012-06-01 21:15:42 +00:00
|
|
|
|
2012-12-14 06:26:21 +00:00
|
|
|
BLOG_ENTRIES_PER_PAGE = 20
|
|
|
|
MEETINGS_PER_PAGE = 20
|
|
|
|
|
2012-12-05 01:21:34 +00:00
|
|
|
MIRRORS_FILE = os.path.join(TEMPLATE_DIR, 'downloads/mirrors')
|
|
|
|
|
2012-09-12 06:16:18 +00:00
|
|
|
app = application = Flask('i2p2www', template_folder=TEMPLATE_DIR, static_url_path='/_static', static_folder=STATIC_DIR)
|
2012-06-01 21:15:42 +00:00
|
|
|
app.debug = bool(os.environ.get('APP_DEBUG', 'False'))
|
2012-09-17 01:19:57 +00:00
|
|
|
babel = Babel(app)
|
|
|
|
|
|
|
|
|
2012-12-19 11:29:33 +00:00
|
|
|
#######################
|
|
|
|
# Custom URL converters
|
|
|
|
|
|
|
|
class LangConverter(BaseConverter):
|
|
|
|
def __init__(self, url_map):
|
|
|
|
super(LangConverter, self).__init__(url_map)
|
2012-12-19 11:55:05 +00:00
|
|
|
self.regex = '(?:[a-z]{2})(-[a-z]{2})?'
|
2012-12-19 11:29:33 +00:00
|
|
|
|
|
|
|
def to_python(self, value):
|
2012-12-19 11:55:05 +00:00
|
|
|
parts = value.split('-')
|
|
|
|
if len(parts) == 2:
|
|
|
|
return parts[0] + '_' + parts[1].upper()
|
2012-12-19 11:29:33 +00:00
|
|
|
return value
|
|
|
|
|
|
|
|
def to_url(self, value):
|
2012-12-19 11:55:05 +00:00
|
|
|
parts = value.split('_')
|
|
|
|
if len(parts) == 2:
|
|
|
|
return parts[0] + '-' + parts[1].lower()
|
2012-12-19 11:29:33 +00:00
|
|
|
return value
|
|
|
|
|
|
|
|
app.url_map.converters['lang'] = LangConverter
|
|
|
|
|
|
|
|
|
2012-12-19 06:53:59 +00:00
|
|
|
######
|
|
|
|
# URLs
|
|
|
|
|
|
|
|
def url(url_rule, import_name, **options):
|
|
|
|
view = LazyView('i2p2www.' + import_name)
|
|
|
|
app.add_url_rule(url_rule, view_func=view, **options)
|
|
|
|
|
|
|
|
url('/', 'views.main_index')
|
2012-12-19 11:29:33 +00:00
|
|
|
url('/<lang:lang>/', 'views.site_show', defaults={'page': 'index'})
|
|
|
|
url('/<lang:lang>/<path:page>', 'views.site_show')
|
|
|
|
|
|
|
|
url('/<lang:lang>/blog/', 'blog.views.blog_index', defaults={'page': 1})
|
|
|
|
url('/<lang:lang>/blog/page/<int:page>', 'blog.views.blog_index')
|
|
|
|
url('/<lang:lang>/blog/entry/<path:slug>', 'blog.views.blog_entry')
|
|
|
|
url('/<lang:lang>/feed/blog/rss', 'blog.views.blog_rss')
|
|
|
|
url('/<lang:lang>/feed/blog/atom', 'blog.views.blog_atom')
|
|
|
|
|
|
|
|
url('/<lang:lang>/meetings/', 'meetings.views.meetings_index', defaults={'page': 1})
|
|
|
|
url('/<lang:lang>/meetings/page/<int:page>', 'meetings.views.meetings_index')
|
|
|
|
url('/<lang:lang>/meetings/<int:id>', 'meetings.views.meetings_show')
|
|
|
|
url('/<lang:lang>/meetings/<int:id>.log', 'meetings.views.meetings_show_log')
|
|
|
|
url('/<lang:lang>/meetings/<int:id>.rst', 'meetings.views.meetings_show_rst')
|
|
|
|
url('/<lang:lang>/feed/meetings/atom', 'meetings.views.meetings_atom')
|
2012-12-19 07:59:22 +00:00
|
|
|
|
2012-12-19 12:52:05 +00:00
|
|
|
url('/<lang:lang>/download', 'downloads.downloads_list')
|
|
|
|
url('/<lang:lang>/download/<path:file>', 'downloads.downloads_select')
|
|
|
|
url('/download/<string:protocol>/any/<path:file>', 'downloads.downloads_redirect', defaults={'mirror': None})
|
|
|
|
url('/download/<string:protocol>/<int:mirror>/<path:file>', 'downloads.downloads_redirect')
|
|
|
|
|
2012-12-19 10:30:49 +00:00
|
|
|
url('/meeting<int:id>', 'legacy.legacy_meeting')
|
|
|
|
url('/meeting<int:id>.html', 'legacy.legacy_meeting')
|
|
|
|
url('/status-<int:year>-<int:month>-<int:day>', 'legacy.legacy_status')
|
|
|
|
url('/status-<int:year>-<int:month>-<int:day>.html', 'legacy.legacy_status')
|
2012-12-19 11:29:33 +00:00
|
|
|
url('/<string:f>_<lang:lang>', 'legacy.legacy_show')
|
|
|
|
url('/<string:f>_<lang:lang>.html', 'legacy.legacy_show')
|
2012-12-19 11:37:11 +00:00
|
|
|
url('/<string:f>/', 'legacy.legacy_show')
|
2012-12-19 10:30:49 +00:00
|
|
|
url('/<string:f>.html', 'legacy.legacy_show')
|
|
|
|
|
2012-12-19 12:39:53 +00:00
|
|
|
url('/hosts.txt', 'views.hosts')
|
|
|
|
url('/robots.txt', 'views.robots')
|
|
|
|
url('/favicon.ico', 'views.favicon')
|
|
|
|
|
2012-12-19 06:53:59 +00:00
|
|
|
|
2012-09-17 01:19:57 +00:00
|
|
|
#################
|
|
|
|
# Babel selectors
|
|
|
|
|
|
|
|
@babel.localeselector
|
|
|
|
def get_locale():
|
|
|
|
# If the language is already set from the url, use that
|
|
|
|
if hasattr(g, 'lang'):
|
|
|
|
return g.lang
|
|
|
|
# otherwise try to guess the language from the user accept
|
|
|
|
# header the browser transmits. The best match wins.
|
|
|
|
return request.accept_languages.best_match(['en', 'es', 'zh', 'de', 'fr', 'it', 'nl', 'ru', 'sv', 'cs', 'ar'])
|
2012-06-01 21:15:42 +00:00
|
|
|
|
2012-09-10 12:14:29 +00:00
|
|
|
|
|
|
|
##########################
|
|
|
|
# Hooks - helper functions
|
2012-06-01 23:36:38 +00:00
|
|
|
|
|
|
|
def after_this_request(f):
|
|
|
|
if not hasattr(g, 'after_request_callbacks'):
|
|
|
|
g.after_request_callbacks = []
|
|
|
|
g.after_request_callbacks.append(f)
|
|
|
|
return f
|
|
|
|
|
2011-10-05 21:35:41 +00:00
|
|
|
|
2012-09-10 12:14:29 +00:00
|
|
|
###########################
|
|
|
|
# Hooks - url preprocessing
|
2011-10-05 21:35:41 +00:00
|
|
|
|
|
|
|
@app.url_value_preprocessor
|
|
|
|
def pull_lang(endpoint, values):
|
|
|
|
if not values:
|
|
|
|
return
|
|
|
|
g.lang=values.pop('lang', None)
|
|
|
|
|
2012-06-03 01:06:09 +00:00
|
|
|
@app.url_defaults
|
|
|
|
def set_lang(endpoint, values):
|
|
|
|
if not values:
|
|
|
|
return
|
2012-09-17 21:31:08 +00:00
|
|
|
if endpoint == 'static':
|
|
|
|
# Static urls shouldn't have a lang flag
|
|
|
|
# (causes complete reload on lang change)
|
|
|
|
return
|
2012-06-03 01:06:09 +00:00
|
|
|
if 'lang' in values:
|
|
|
|
return
|
|
|
|
if hasattr(g, 'lang'):
|
|
|
|
values['lang'] = g.lang
|
|
|
|
|
2012-09-10 12:14:29 +00:00
|
|
|
|
|
|
|
########################
|
|
|
|
# Hooks - before request
|
|
|
|
|
|
|
|
# Detect and store chosen theme
|
2012-06-01 23:36:38 +00:00
|
|
|
@app.before_request
|
|
|
|
def detect_theme():
|
2012-09-10 23:11:13 +00:00
|
|
|
theme = 'duck'
|
2012-06-01 23:36:38 +00:00
|
|
|
if 'style' in request.cookies:
|
|
|
|
theme = request.cookies['style']
|
|
|
|
if 'theme' in request.args.keys():
|
|
|
|
theme = request.args['theme']
|
2012-12-14 21:51:45 +00:00
|
|
|
if not os.path.isfile(safe_join(safe_join(STATIC_DIR, 'styles'), '%s.css' % theme)):
|
2012-09-10 23:11:13 +00:00
|
|
|
theme = 'duck'
|
2012-06-01 23:36:38 +00:00
|
|
|
g.theme = theme
|
|
|
|
@after_this_request
|
|
|
|
def remember_theme(resp):
|
2012-09-10 23:11:13 +00:00
|
|
|
if g.theme == 'duck' and 'style' in request.cookies:
|
2012-06-01 23:36:38 +00:00
|
|
|
resp.delete_cookie('style')
|
2012-09-10 23:11:13 +00:00
|
|
|
elif g.theme != 'duck':
|
2012-06-01 23:36:38 +00:00
|
|
|
resp.set_cookie('style', g.theme)
|
|
|
|
return resp
|
|
|
|
|
2012-06-01 23:15:41 +00:00
|
|
|
|
2012-09-10 12:14:29 +00:00
|
|
|
#######################
|
|
|
|
# Hooks - after request
|
|
|
|
|
|
|
|
@app.after_request
|
|
|
|
def call_after_request_callbacks(response):
|
|
|
|
for callback in getattr(g, 'after_request_callbacks', ()):
|
|
|
|
response = callback(response)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2012-09-15 04:54:16 +00:00
|
|
|
##################
|
|
|
|
# Template filters
|
|
|
|
|
|
|
|
@app.template_filter('restructuredtext')
|
|
|
|
def restructuredtext(value):
|
|
|
|
parts = publish_parts(source=value, writer_name="html")
|
|
|
|
return parts['html_body']
|
|
|
|
|
2012-12-03 10:45:35 +00:00
|
|
|
|
|
|
|
####################
|
|
|
|
# Context processors
|
|
|
|
|
|
|
|
@app.context_processor
|
|
|
|
def utility_processor():
|
2012-12-19 11:43:40 +00:00
|
|
|
# Shorthand for getting a site url
|
|
|
|
def get_site_url(path=None):
|
|
|
|
lang = 'en'
|
|
|
|
if hasattr(g, 'lang') and g.lang:
|
|
|
|
lang = g.lang
|
|
|
|
if path:
|
|
|
|
return url_for('site_show', lang=lang, page=path)
|
|
|
|
else:
|
|
|
|
return url_for('site_show', lang=lang)
|
|
|
|
|
2012-12-19 12:13:51 +00:00
|
|
|
# Shorthand for getting a language-specific url
|
|
|
|
def get_url_with_lang(endpoint, **args):
|
|
|
|
lang = 'en'
|
|
|
|
if hasattr(g, 'lang') and g.lang:
|
|
|
|
lang = g.lang
|
|
|
|
return url_for(endpoint, lang=lang, **args)
|
|
|
|
|
|
|
|
# Get a specific language flag, or the flag for the current language
|
|
|
|
def get_flag(lang=None):
|
|
|
|
if not lang:
|
|
|
|
if hasattr(g, 'lang') and g.lang:
|
|
|
|
lang = g.lang
|
|
|
|
else:
|
|
|
|
lang = 'en'
|
|
|
|
return url_for('static', filename='images/flags/'+lang+'.png')
|
|
|
|
|
2012-12-19 04:51:36 +00:00
|
|
|
# Provide the canonical link to the current page
|
|
|
|
def get_canonical_link():
|
|
|
|
protocol = request.url.split('//')[0]
|
|
|
|
return protocol + '//' + CANONICAL_DOMAIN + request.path
|
|
|
|
|
2012-12-03 10:45:35 +00:00
|
|
|
# Convert an I2P url to an equivalent clearnet one
|
|
|
|
i2ptoclear = {
|
|
|
|
'www.i2p2.i2p': 'www.i2p2.de',
|
2012-12-15 11:28:34 +00:00
|
|
|
#'forum.i2p': 'forum.i2p2.de',
|
2012-12-03 10:45:35 +00:00
|
|
|
'trac.i2p2.i2p': 'trac.i2p2.de',
|
2012-12-15 13:01:09 +00:00
|
|
|
'mail.i2p': 'i2pmail.org',
|
2012-12-03 10:45:35 +00:00
|
|
|
}
|
|
|
|
def convert_url_to_clearnet(value):
|
|
|
|
if not value.endswith('.i2p'):
|
|
|
|
# The url being passed in isn't an I2P url, so just return it
|
|
|
|
return value
|
|
|
|
if request.headers.get('X-I2P-Desthash') and not request.headers.get('X-Forwarded-Server'):
|
|
|
|
# The request is from within I2P, so use I2P url
|
|
|
|
return value
|
|
|
|
# The request is either directly from clearnet or through an inproxy
|
|
|
|
try:
|
|
|
|
# Return the known clearnet url corresponding to the I2P url
|
|
|
|
return i2ptoclear[value]
|
|
|
|
except KeyError:
|
|
|
|
# The I2P site has no known clearnet address, so use an inproxy
|
|
|
|
return value + '.to'
|
2012-12-14 06:26:21 +00:00
|
|
|
|
|
|
|
# Convert a paginated URL to that of another page
|
|
|
|
def url_for_other_page(page):
|
|
|
|
args = request.view_args.copy()
|
|
|
|
args['page'] = page
|
|
|
|
return url_for(request.endpoint, **args)
|
|
|
|
|
2012-12-15 21:57:32 +00:00
|
|
|
# Change the theme of the current page
|
|
|
|
def change_theme(theme):
|
2012-12-19 12:25:19 +00:00
|
|
|
args = {}
|
|
|
|
if request.view_args:
|
|
|
|
args = request.view_args.copy()
|
2012-12-15 21:57:32 +00:00
|
|
|
args['theme'] = theme
|
2012-12-19 12:25:19 +00:00
|
|
|
if request.endpoint:
|
|
|
|
return url_for(request.endpoint, **args)
|
|
|
|
# Probably a 404 error page
|
|
|
|
return url_for('main_index', **args)
|
2012-12-15 21:57:32 +00:00
|
|
|
|
2012-12-14 06:26:21 +00:00
|
|
|
return dict(i2pconv=convert_url_to_clearnet,
|
2012-12-15 21:57:32 +00:00
|
|
|
url_for_other_page=url_for_other_page,
|
2012-12-19 04:51:36 +00:00
|
|
|
change_theme=change_theme,
|
2012-12-19 11:43:40 +00:00
|
|
|
site_url=get_site_url,
|
2012-12-19 12:13:51 +00:00
|
|
|
get_url=get_url_with_lang,
|
|
|
|
get_flag=get_flag,
|
2012-12-19 04:51:36 +00:00
|
|
|
canonical=get_canonical_link)
|
2012-09-15 04:54:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
################
|
2012-09-10 11:56:51 +00:00
|
|
|
# Error handlers
|
|
|
|
|
2012-06-03 01:06:09 +00:00
|
|
|
@app.errorhandler(404)
|
|
|
|
def page_not_found(error):
|
|
|
|
return render_template('global/error_404.html'), 404
|
2012-09-10 11:28:34 +00:00
|
|
|
|
2012-09-11 00:17:00 +00:00
|
|
|
@app.errorhandler(500)
|
|
|
|
def server_error(error):
|
|
|
|
return render_template('global/error_500.html'), 500
|
|
|
|
|
2012-06-03 01:06:09 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
app.run(debug=True)
|