Added captcha to lodgeit.
@ -24,11 +24,12 @@ from lodgeit.controllers import get_controller
|
||||
from lodgeit.lib.antispam import AntiSpam
|
||||
|
||||
|
||||
|
||||
class LodgeIt(object):
|
||||
"""The WSGI Application"""
|
||||
|
||||
def __init__(self, dburi):
|
||||
def __init__(self, dburi, secret_key):
|
||||
#: the secret key used by the captcha
|
||||
self.secret_key = secret_key
|
||||
#: name of the error handler
|
||||
self.not_found = ('static/not_found', {})
|
||||
self.engine = sqlalchemy.create_engine(dburi)
|
||||
@ -67,10 +68,10 @@ class LodgeIt(object):
|
||||
[_local_manager.cleanup, session.remove])
|
||||
|
||||
|
||||
def make_app(dburi, debug=False, shell=False):
|
||||
def make_app(dburi, secret_key, debug=False, shell=False):
|
||||
"""Apply the used middlewares and create the application."""
|
||||
static_path = os.path.join(os.path.dirname(__file__), 'static')
|
||||
app = LodgeIt(dburi)
|
||||
app = LodgeIt(dburi, secret_key)
|
||||
if debug:
|
||||
app.engine.echo = True
|
||||
if not shell:
|
||||
|
@ -16,6 +16,7 @@ from lodgeit.controllers import BaseController
|
||||
from lodgeit.database import session, Paste
|
||||
from lodgeit.lib.highlighting import LANGUAGES, STYLES, get_style
|
||||
from lodgeit.lib.pagination import generate_pagination
|
||||
from lodgeit.lib.captcha import check_hashed_solution, Captcha
|
||||
|
||||
|
||||
MAX_LINE_LENGTH = 300
|
||||
@ -29,6 +30,7 @@ class PasteController(BaseController):
|
||||
code = error = ''
|
||||
language = 'text'
|
||||
pastes = session.query(Paste)
|
||||
show_captcha = False
|
||||
|
||||
if ctx.request.method == 'POST':
|
||||
code = ctx.request.form.get('code')
|
||||
@ -41,7 +43,14 @@ class PasteController(BaseController):
|
||||
spam = ctx.request.form.get('webpage') or \
|
||||
ctx.application.antispam.is_spam(code)
|
||||
if spam:
|
||||
error = 'contains spam'
|
||||
error = 'your paste contains spam'
|
||||
captcha = ctx.request.form.get('captcha')
|
||||
if captcha:
|
||||
if check_hashed_solution(captcha):
|
||||
error = None
|
||||
else:
|
||||
error += ' and the CAPTCHA solution was incorrect'
|
||||
show_captcha = True
|
||||
if code and language and not error:
|
||||
paste = Paste(code, language, parent, ctx.request.user_hash)
|
||||
session.save(paste)
|
||||
@ -60,7 +69,8 @@ class PasteController(BaseController):
|
||||
parent=parent,
|
||||
code=code,
|
||||
language=language,
|
||||
error=error
|
||||
error=error,
|
||||
show_captcha=show_captcha
|
||||
)
|
||||
|
||||
def show_paste(self, paste_id, raw=False):
|
||||
@ -122,9 +132,7 @@ class PasteController(BaseController):
|
||||
)
|
||||
|
||||
def compare_paste(self, new_id=None, old_id=None):
|
||||
"""
|
||||
Render a diff view for two pastes.
|
||||
"""
|
||||
"""Render a diff view for two pastes."""
|
||||
# redirect for the compare form box
|
||||
if old_id is new_id is None:
|
||||
old_id = ctx.request.form.get('old', '-1').lstrip('#')
|
||||
@ -142,9 +150,7 @@ class PasteController(BaseController):
|
||||
)
|
||||
|
||||
def unidiff_paste(self, new_id=None, old_id=None):
|
||||
"""
|
||||
Render an udiff for the two pastes.
|
||||
"""
|
||||
"""Render an udiff for the two pastes."""
|
||||
pastes = session.query(Paste)
|
||||
old = pastes.filter(Paste.c.paste_id == old_id).first()
|
||||
new = pastes.filter(Paste.c.paste_id == new_id).first()
|
||||
@ -153,8 +159,7 @@ class PasteController(BaseController):
|
||||
return Response(old.compare_to(new), mimetype='text/plain')
|
||||
|
||||
def set_colorscheme(self):
|
||||
"""
|
||||
Minimal view that updates the style session cookie. Redirects
|
||||
"""Minimal view that updates the style session cookie. Redirects
|
||||
back to the page the user is coming from.
|
||||
"""
|
||||
style_name = ctx.request.form.get('style')
|
||||
@ -163,4 +168,8 @@ class PasteController(BaseController):
|
||||
resp.set_cookie('style', style_name)
|
||||
return resp
|
||||
|
||||
def show_captcha(self):
|
||||
"""Show a captcha."""
|
||||
return Captcha().get_response(set_cookie=True)
|
||||
|
||||
controller = PasteController
|
||||
|
@ -24,7 +24,7 @@ LINK_RE = re.compile(r'%s[^\s\'"]+\S' % _url_pattern)
|
||||
|
||||
|
||||
def percentize(matched, length):
|
||||
return matched * 100.0 / length
|
||||
return matched * 100.0 / (length or 1)
|
||||
|
||||
|
||||
class AntiSpam(object):
|
||||
@ -36,7 +36,7 @@ class AntiSpam(object):
|
||||
def check_for_link_spam(self, code):
|
||||
lengths = (x.span() for x in LINK_RE.finditer(code))
|
||||
return percentize(sum(i[1]-i[0] for i in lengths),
|
||||
len(code) or 1) > 50
|
||||
len(code)) > 50
|
||||
|
||||
def is_spam(self, code):
|
||||
"""Check if one of the fields provides contains spam."""
|
||||
|
415
lodgeit/lib/captcha.py
Normal file
@ -0,0 +1,415 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
lodgeit.captcha
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
A module that produces image and audio captchas. Uses some code of
|
||||
PyCAPTCHA by Micah Dowty and was originally used in inyoka.
|
||||
|
||||
|
||||
:copyright: Copyright 2007 by Armin Ronacher, Micah Dowty.
|
||||
:license: GNU GPL.
|
||||
"""
|
||||
import random
|
||||
import colorsys
|
||||
import math
|
||||
try:
|
||||
from hashlib import sha1
|
||||
except ImportError:
|
||||
from sha import new as sha1
|
||||
from os import listdir
|
||||
from os.path import abspath, join, dirname, pardir
|
||||
|
||||
from PIL import ImageFont, ImageDraw, Image, ImageChops, ImageColor
|
||||
from werkzeug import Response
|
||||
|
||||
from lodgeit.utils import ctx
|
||||
|
||||
|
||||
resource_path = abspath(join(dirname(__file__), pardir, 'res'))
|
||||
|
||||
|
||||
def check_hashed_solution(solution, hashed_solution=None, secret_key=None):
|
||||
"""Check a solution against the hashed solution from the first
|
||||
request by using the secret key.
|
||||
"""
|
||||
if hashed_solution is None:
|
||||
hashed_solution = ctx.request.cookies.get('captcha_id')
|
||||
if hashed_solution is None:
|
||||
return False
|
||||
return hashed_solution == calculate_hash(solution, secret_key)
|
||||
|
||||
|
||||
def calculate_hash(solution, secret_key=None):
|
||||
"""Calculate the hash."""
|
||||
if secret_key is None:
|
||||
secret_key = ctx.application.secret_key
|
||||
return sha1('%s|%s' % (
|
||||
secret_key,
|
||||
solution.encode('utf-8')
|
||||
)).hexdigest()
|
||||
|
||||
|
||||
def generate_word():
|
||||
"""This function returns a pronounceable word."""
|
||||
consonants = 'bcdfghjklmnprstvwz'
|
||||
vowels = 'aeiou'
|
||||
both = consonants + vowels
|
||||
length = random.randrange(8, 12)
|
||||
return ''.join(
|
||||
random.choice(consonants) +
|
||||
random.choice(vowels) +
|
||||
random.choice(both) for x in xrange(length // 3)
|
||||
)[:length]
|
||||
|
||||
|
||||
def get_random_resource(type, prefix=None):
|
||||
"""Return a random resource of a given type."""
|
||||
path = join(resource_path, type)
|
||||
choices = (x for x in listdir(path) if not x.startswith('.'))
|
||||
if prefix is not None:
|
||||
choices = (x for x in choices if x.startswith(prefix))
|
||||
return join(path, random.choice(tuple(choices)))
|
||||
|
||||
|
||||
def random_color(saturation=0.5, lumination=None):
|
||||
"""Return a random number with the given saturation."""
|
||||
hue = random.random()
|
||||
if lumination is None:
|
||||
lumination = random.random()
|
||||
r, g, b = colorsys.hls_to_rgb(hue, lumination, saturation)
|
||||
return '#%02x%02x%02x' % (
|
||||
int(r * 255) & 0xff,
|
||||
int(g * 255) & 0xff,
|
||||
int(b * 255) & 0xff
|
||||
)
|
||||
|
||||
|
||||
class Captcha(object):
|
||||
"""Represents a captcha."""
|
||||
default_size = (300, 100)
|
||||
|
||||
def __init__(self, solution=None):
|
||||
if solution is None:
|
||||
solution = generate_word()
|
||||
self.solution = solution
|
||||
self.layers = [
|
||||
RandomBackground(),
|
||||
RandomDistortion()
|
||||
]
|
||||
text_layer = TextLayer(self.solution, bg=self.layers[0].bg)
|
||||
self.layers.extend((text_layer, SineWarp()))
|
||||
|
||||
def hash_solution(self, secret_key=None):
|
||||
"""Return the solution as hashed value."""
|
||||
return calculate_hash(self.solution, secret_key)
|
||||
|
||||
def render_image(self, size=None):
|
||||
if size is None:
|
||||
size = self.default_size
|
||||
image = Image.new('RGBA', size)
|
||||
for layer in self.layers:
|
||||
image = layer.render(image)
|
||||
return image
|
||||
|
||||
def get_response(self, size=None, set_cookie=False):
|
||||
response = Response(mimetype='image/png')
|
||||
self.render_image(size=None).save(response.stream, 'PNG')
|
||||
if set_cookie:
|
||||
response.set_cookie('captcha_id', self.hash_solution())
|
||||
return response
|
||||
|
||||
|
||||
class Layer(object):
|
||||
"""Baseclass for a captcha layer."""
|
||||
bg = 'dark'
|
||||
|
||||
def render(self, image):
|
||||
return image
|
||||
|
||||
|
||||
class TextLayer(Layer):
|
||||
"""Add text to the captcha."""
|
||||
bg = 'transparent'
|
||||
|
||||
def __init__(self, text, min_size=32, max_size=48, bg='dark'):
|
||||
self.text = text
|
||||
self.alignment = (random.random(), random.random())
|
||||
if bg == 'dark':
|
||||
color = random_color(saturation=0.3, lumination=0.8)
|
||||
else:
|
||||
color = random_color(saturation=0.1, lumination=0.1)
|
||||
self.text_color = color
|
||||
self.transparency = random.randint(20, 60)
|
||||
f = get_random_resource('fonts')
|
||||
self.font = ImageFont.truetype(get_random_resource('fonts'),
|
||||
random.randrange(min_size, max_size))
|
||||
|
||||
def render(self, image):
|
||||
text_layer = Image.new('RGB', image.size, (0, 0, 0))
|
||||
alpha = Image.new('L', image.size, 0)
|
||||
|
||||
# draw grayscale image white on black
|
||||
text_image = Image.new('L', image.size, 0)
|
||||
draw = ImageDraw.Draw(text_image)
|
||||
text_size = self.font.getsize(self.text)
|
||||
x = int((image.size[0] - text_size[0]) * self.alignment[0] + 0.5)
|
||||
y = int((image.size[1] - text_size[1]) * self.alignment[1] + 0.5)
|
||||
draw.text((x, y), self.text, font=self.font,
|
||||
fill=255 - self.transparency)
|
||||
|
||||
# colorize the text and calculate the alpha channel
|
||||
alpha = ImageChops.lighter(alpha, text_image)
|
||||
color_layer = Image.new('RGBA', image.size, self.text_color)
|
||||
mask = Image.eval(text_image, lambda x: 255 * (x != 0))
|
||||
text_layer = Image.composite(color_layer, text_layer, mask)
|
||||
|
||||
# paste the text on the image with the correct alphachannel
|
||||
image.paste(text_layer, alpha)
|
||||
return image
|
||||
|
||||
|
||||
class CombinedLayer(Layer):
|
||||
"""Combines multiple layers."""
|
||||
|
||||
def __init__(self, layers):
|
||||
self.layers = layers
|
||||
if layers:
|
||||
self.bg = layers[0].bg
|
||||
|
||||
def render(self, image):
|
||||
for layer in self.layers:
|
||||
image = layer.render(image)
|
||||
return image
|
||||
|
||||
|
||||
class RandomBackground(CombinedLayer):
|
||||
"""Selects a random background."""
|
||||
|
||||
def __init__(self):
|
||||
layers = [random.choice([SolidColor, DarkBackground,
|
||||
LightBackground])()]
|
||||
for x in xrange(random.randrange(1, 4)):
|
||||
layers.append(random.choice([
|
||||
NoiseBackground,
|
||||
GridBackground
|
||||
])())
|
||||
CombinedLayer.__init__(self, layers)
|
||||
self.bg = layers[0].bg
|
||||
|
||||
|
||||
class RandomDistortion(CombinedLayer):
|
||||
"""Selects a random distortion."""
|
||||
background = 'transparent'
|
||||
|
||||
def __init__(self):
|
||||
layers = []
|
||||
for x in xrange(random.randrange(1, 3)):
|
||||
layers.append(random.choice((
|
||||
WigglyBlocks,
|
||||
SineWarp
|
||||
))())
|
||||
CombinedLayer.__init__(self, layers)
|
||||
|
||||
|
||||
class Picture(Layer):
|
||||
"""Add a background to the captcha."""
|
||||
|
||||
def __init__(self, picture):
|
||||
self.image = Image.open(picture)
|
||||
self.offset = (random.random(), random.random())
|
||||
|
||||
def render(self, image):
|
||||
tile = self.image
|
||||
for j in xrange(-1, int(image.size[1] / tile.size[1]) + 1):
|
||||
for i in xrange(-1, int(image.size[0] / tile.size[0]) + 1):
|
||||
dest = (int((self.offset[0] + i) * tile.size[0]),
|
||||
int((self.offset[1] + j) * tile.size[1]))
|
||||
image.paste(tile, dest)
|
||||
return image
|
||||
|
||||
|
||||
class LightBackground(Picture):
|
||||
bg = 'light'
|
||||
|
||||
def __init__(self):
|
||||
Picture.__init__(self, get_random_resource('backgrounds/light'))
|
||||
|
||||
|
||||
class DarkBackground(Picture):
|
||||
|
||||
def __init__(self):
|
||||
Picture.__init__(self, get_random_resource('backgrounds/dark'))
|
||||
|
||||
|
||||
class NoiseBackground(Layer):
|
||||
"""Add some noise as background. You can combine this with another
|
||||
background layer.
|
||||
"""
|
||||
bg = 'transparent'
|
||||
|
||||
def __init__(self, saturation=0.1, num_dots=None):
|
||||
self.saturation = saturation
|
||||
self.num_dots = random.randrange(300, 500)
|
||||
self.seed = random.random()
|
||||
|
||||
def render(self, image):
|
||||
r = random.Random(self.seed)
|
||||
for i in xrange(self.num_dots):
|
||||
dot_size = random.randrange(1, 5)
|
||||
bx = int(r.uniform(0, image.size[0] - dot_size))
|
||||
by = int(r.uniform(0, image.size[1] - dot_size))
|
||||
image.paste(random_color(self.saturation, 0.4),
|
||||
(bx, by, bx + dot_size - 1,
|
||||
by + dot_size - 1))
|
||||
return image
|
||||
|
||||
|
||||
class GridBackground(Layer):
|
||||
"""Add a grid as background. You can combine this with another
|
||||
background layer.
|
||||
"""
|
||||
bg = 'transparent'
|
||||
|
||||
def __init__(self, size=None, color=None):
|
||||
if size is None:
|
||||
size = random.randrange(10, 50)
|
||||
if color is None:
|
||||
color = random_color(0, 0.4)
|
||||
self.size = size
|
||||
self.color = color
|
||||
self.offset = (random.uniform(0, self.size),
|
||||
random.uniform(0, self.size))
|
||||
|
||||
def render(self, image):
|
||||
draw = ImageDraw.Draw(image)
|
||||
for i in xrange(image.size[0] / self.size + 1):
|
||||
draw.line((i * self.size + self.offset[0], 0,
|
||||
i * self.size + self.offset[0], image.size[1]),
|
||||
fill=self.color)
|
||||
for i in xrange(image.size[0] / self.size + 1):
|
||||
draw.line((0, i * self.size + self.offset[1],
|
||||
image.size[0], i * self.size+self.offset[1]),
|
||||
fill=self.color)
|
||||
return image
|
||||
|
||||
|
||||
class SolidColor(Layer):
|
||||
"""A solid color background. Very weak on its own, but good
|
||||
to combine with other backgrounds.
|
||||
"""
|
||||
|
||||
def __init__(self, color=None):
|
||||
if color is None:
|
||||
color = random_color(0.2, random.random() > 0.5 and 0.3 or 0.7)
|
||||
self.color = ImageColor.getrgb(color)
|
||||
if colorsys.rgb_to_hls(*[x / 255.0 for x in self.color])[1] > 0.5:
|
||||
self.bg = 'light'
|
||||
|
||||
def render(self, image):
|
||||
image.paste(self.color)
|
||||
return image
|
||||
|
||||
|
||||
class WigglyBlocks(Layer):
|
||||
"""Randomly select and shift blocks of the image"""
|
||||
bg = 'transparent'
|
||||
|
||||
def __init__(self, block_size=None, sigma=0.01, iterations=None):
|
||||
if block_size is None:
|
||||
block_size = random.randrange(15, 25)
|
||||
if iterations is None:
|
||||
iterations = random.randrange(250, 350)
|
||||
self.block_size = block_size
|
||||
self.sigma = sigma
|
||||
self.iterations = iterations
|
||||
self.seed = random.random()
|
||||
|
||||
def render(self, image):
|
||||
r = random.Random(self.seed)
|
||||
for i in xrange(self.iterations):
|
||||
# Select a block
|
||||
bx = int(r.uniform(0, image.size[0] - self.block_size))
|
||||
by = int(r.uniform(0, image.size[1] - self.block_size))
|
||||
block = image.crop((bx, by, bx + self.block_size - 1,
|
||||
by + self.block_size - 1))
|
||||
|
||||
# Figure out how much to move it.
|
||||
# The call to floor() is important so we always round toward
|
||||
# 0 rather than to -inf. Just int() would bias the block motion.
|
||||
mx = int(math.floor(r.normalvariate(0, self.sigma)))
|
||||
my = int(math.floor(r.normalvariate(0, self.sigma)))
|
||||
|
||||
# Now actually move the block
|
||||
image.paste(block, (bx+mx, by+my))
|
||||
return image
|
||||
|
||||
|
||||
class WarpBase(Layer):
|
||||
"""Abstract base class for image warping. Subclasses define a function
|
||||
that maps points in the output image to points in the input image. This
|
||||
warping engine runs a grid of points through this transform and uses PIL's
|
||||
mesh transform to warp the image.
|
||||
"""
|
||||
bg = 'transparent'
|
||||
filtering = Image.BILINEAR
|
||||
resolution = 10
|
||||
|
||||
def get_transform(self, image):
|
||||
"""Return a transformation function, subclasses should override this"""
|
||||
return lambda x, y: (x, y)
|
||||
|
||||
def render(self, image):
|
||||
r = self.resolution
|
||||
x_points = image.size[0] / r + 2
|
||||
y_points = image.size[1] / r + 2
|
||||
f = self.get_transform(image)
|
||||
|
||||
# Create a list of arrays with transformed points
|
||||
x_rows = []
|
||||
y_rows = []
|
||||
for j in xrange(y_points):
|
||||
x_row = []
|
||||
y_row = []
|
||||
for i in xrange(x_points):
|
||||
x, y = f(i * r, j * r)
|
||||
|
||||
# Clamp the edges so we don't get black undefined areas
|
||||
x = max(0, min(image.size[0] - 1, x))
|
||||
y = max(0, min(image.size[1] - 1, y))
|
||||
|
||||
x_row.append(x)
|
||||
y_row.append(y)
|
||||
x_rows.append(x_row)
|
||||
y_rows.append(y_row)
|
||||
|
||||
# Create the mesh list, with a transformation for
|
||||
# each square between points on the grid
|
||||
mesh = []
|
||||
for j in xrange(y_points - 1):
|
||||
for i in xrange(x_points-1):
|
||||
mesh.append((
|
||||
# Destination rectangle
|
||||
(i * r, j * r, (i + 1) * r, (j + 1) * r),
|
||||
# Source quadrilateral
|
||||
(x_rows[j][i], y_rows[j][i],
|
||||
x_rows[j + 1][i], y_rows[j+1][i],
|
||||
x_rows[j + 1][i + 1], y_rows[j + 1][i + 1],
|
||||
x_rows[j][i+1], y_rows[j][i + 1]),
|
||||
))
|
||||
return image.transform(image.size, Image.MESH, mesh, self.filtering)
|
||||
|
||||
|
||||
class SineWarp(WarpBase):
|
||||
"""Warp the image using a random composition of sine waves"""
|
||||
|
||||
def __init__(self, amplitude_range=(3, 6.5), period_range=(0.04, 0.1)):
|
||||
self.amplitude = random.uniform(*amplitude_range)
|
||||
self.period = random.uniform(*period_range)
|
||||
self.offset = (random.uniform(0, math.pi * 2 / self.period),
|
||||
random.uniform(0, math.pi * 2 / self.period))
|
||||
|
||||
def get_transform(self, image):
|
||||
return (lambda x, y, a=self.amplitude, p=self.period,
|
||||
o=self.offset: (math.sin((y + o[0]) * p) * a + x,
|
||||
math.sin((x + o[1]) * p) * a + y))
|
BIN
lodgeit/res/.DS_Store
vendored
Normal file
4
lodgeit/res/LICENSE
Normal file
@ -0,0 +1,4 @@
|
||||
Background Image Patterns
|
||||
|
||||
* 1-12 from PyCAPTCHA
|
||||
* 12-15 from orgdot.com/3dstuff
|
BIN
lodgeit/res/backgrounds/dark/1.jpeg
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
lodgeit/res/backgrounds/dark/10.jpeg
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
lodgeit/res/backgrounds/dark/11.jpeg
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
lodgeit/res/backgrounds/dark/12.jpeg
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
lodgeit/res/backgrounds/dark/13.jpeg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
lodgeit/res/backgrounds/dark/14.jpeg
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
lodgeit/res/backgrounds/dark/2.jpeg
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
lodgeit/res/backgrounds/dark/3.jpeg
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
lodgeit/res/backgrounds/dark/4.jpeg
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
lodgeit/res/backgrounds/dark/5.jpeg
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
lodgeit/res/backgrounds/dark/6.jpeg
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
lodgeit/res/backgrounds/dark/7.jpeg
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
lodgeit/res/backgrounds/dark/8.jpeg
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
lodgeit/res/backgrounds/dark/9.jpeg
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
lodgeit/res/backgrounds/light/1.jpeg
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
lodgeit/res/backgrounds/light/2.jpeg
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
lodgeit/res/backgrounds/light/3.jpeg
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
lodgeit/res/backgrounds/light/4.jpeg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
lodgeit/res/backgrounds/light/5.jpeg
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
lodgeit/res/backgrounds/light/6.jpeg
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
lodgeit/res/backgrounds/light/7.jpeg
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
lodgeit/res/fonts/GEMELLI_.TTF
Normal file
BIN
lodgeit/res/fonts/GEMERALD.TTF
Normal file
BIN
lodgeit/res/fonts/GOHAN___.TTF
Normal file
BIN
lodgeit/res/fonts/GOODFISH.TTF
Normal file
BIN
lodgeit/res/fonts/GUNPLAY.TTF
Normal file
BIN
lodgeit/res/fonts/Vera.ttf
Normal file
BIN
lodgeit/res/fonts/VeraBI.ttf
Normal file
BIN
lodgeit/res/fonts/VeraBd.ttf
Normal file
BIN
lodgeit/res/fonts/VeraMoBI.ttf
Normal file
BIN
lodgeit/res/fonts/VeraMoBd.ttf
Normal file
BIN
lodgeit/res/fonts/VeraMoIt.ttf
Normal file
BIN
lodgeit/res/fonts/VeraMono.ttf
Normal file
BIN
lodgeit/res/fonts/VeraSe.ttf
Normal file
BIN
lodgeit/res/fonts/VeraSeBd.ttf
Normal file
BIN
lodgeit/res/fonts/luximb.ttf
Normal file
BIN
lodgeit/res/fonts/luximbi.ttf
Normal file
BIN
lodgeit/res/fonts/luximr.ttf
Normal file
BIN
lodgeit/res/fonts/luximri.ttf
Normal file
BIN
lodgeit/res/fonts/luxirb.ttf
Normal file
BIN
lodgeit/res/fonts/luxirbi.ttf
Normal file
BIN
lodgeit/res/fonts/luxirr.ttf
Normal file
BIN
lodgeit/res/fonts/luxirri.ttf
Normal file
BIN
lodgeit/res/fonts/luxisb.ttf
Normal file
BIN
lodgeit/res/fonts/luxisbi.ttf
Normal file
BIN
lodgeit/res/fonts/luxisr.ttf
Normal file
BIN
lodgeit/res/fonts/luxisri.ttf
Normal file
BIN
lodgeit/res/fonts/zekton__.ttf
Normal file
BIN
lodgeit/res/fonts/zektonbi.ttf
Normal file
@ -149,6 +149,21 @@ div.related h3 a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div.notification div.captcha {
|
||||
background-color: #222;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
div.notification div.captcha p {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.notification div.captcha img {
|
||||
display: block;
|
||||
margin: 8px 0 8px 0;
|
||||
}
|
||||
|
||||
div.related h3 a:hover {
|
||||
background-color: #1d89a4;
|
||||
color: white;
|
||||
|
@ -20,6 +20,9 @@ urlmap = Map([
|
||||
Rule('/unidiff/<int:new_id>/<int:old_id>/', endpoint='pastes/unidiff_paste'),
|
||||
Rule('/tree/<int:paste_id>/', endpoint='pastes/show_tree'),
|
||||
|
||||
# captcha for new paste
|
||||
Rule('/_captcha.png', endpoint='pastes/show_captcha'),
|
||||
|
||||
# paste list
|
||||
Rule('/all/', endpoint='pastes/show_all'),
|
||||
Rule('/all/<int:page>/', endpoint='pastes/show_all'),
|
||||
|
@ -2,23 +2,31 @@
|
||||
{% set page_title = 'New Paste' %}
|
||||
{% set active_page = 'new' %}
|
||||
{% block body %}
|
||||
<form action="/" method="post" class="submitform">
|
||||
{%- if error %}
|
||||
<div class="notification">
|
||||
<h3>Error While Pasting</h3>
|
||||
<p>Could not submit your paste because {{ error|e }}.</p>
|
||||
{%- if show_captcha %}
|
||||
<div class="captcha">
|
||||
<p>Please fill out the CAPTCHA to proceed:</p>
|
||||
<img src="/_captcha.png" alt="a captcha you can't see. Sorry :(">
|
||||
<input type="text" name="captcha" size="20">
|
||||
</div>
|
||||
{%- else %}
|
||||
<p><a href="javascript:LodgeIt.hideNotification()">hide this message</a></p>
|
||||
{%- endif %}
|
||||
</div>
|
||||
{%- endif %}
|
||||
<form action="/" method="post" class="submitform">
|
||||
{% if parent %}
|
||||
<input type="hidden" name="parent" value="{{ parent.paste_id }}">
|
||||
{% endif %}
|
||||
<textarea name="code" rows="10" cols="80">{{ code|e }}</textarea>
|
||||
<select name="language">
|
||||
{% for key, caption in languages|dictsort(true, 'value') -%}
|
||||
{%- for key, caption in languages|dictsort(true, 'value') -%}
|
||||
<option value="{{ key }}"{% if language == key
|
||||
%} selected="selected"{% endif %}>{{ caption|e }}</option>
|
||||
{% endfor %}
|
||||
{%- endfor %}
|
||||
</select>
|
||||
<input type="text" value="webpage" id="webpage">
|
||||
<input type="submit" value="Paste!">
|
||||
|
@ -1,18 +1,22 @@
|
||||
from lodgeit.application import make_app
|
||||
from lodgeit.utils import ctx
|
||||
from lodgeit.database import session
|
||||
import os
|
||||
|
||||
from werkzeug import script
|
||||
from werkzeug.serving import run_simple
|
||||
from werkzeug.utils import create_environ, run_wsgi_app
|
||||
|
||||
from lodgeit.application import make_app
|
||||
from lodgeit.utils import ctx
|
||||
from lodgeit.database import session
|
||||
|
||||
dburi = 'sqlite:////tmp/lodgeit.db'
|
||||
secret_key = os.urandom(50)
|
||||
|
||||
def run_app(app, path='/'):
|
||||
env = create_environ(path)
|
||||
env = create_environ(path, secret_key)
|
||||
return run_wsgi_app(app, env)
|
||||
|
||||
action_runserver = script.make_runserver(
|
||||
lambda: make_app(dburi),
|
||||
lambda: make_app(dburi, secret_key),
|
||||
use_reloader=True)
|
||||
|
||||
action_shell = script.make_shell(
|
||||
|