it now works
This commit is contained in:
parent
14c68abf9d
commit
f25f2cd956
BIN
contrib/frontends/django/nntpchan/assets/fonts/FreeMono.ttf
Normal file
BIN
contrib/frontends/django/nntpchan/assets/fonts/FreeMono.ttf
Normal file
Binary file not shown.
@ -62,6 +62,12 @@ class Post(models.Model):
|
||||
placeholder = models.BooleanField(default=False)
|
||||
last_bumped = models.IntegerField(default=0)
|
||||
|
||||
def has_attachment(self, filehash):
|
||||
for att in self.attachments.all():
|
||||
if att.filehash == filehash:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_all_replies(self):
|
||||
if self.is_op():
|
||||
return Post.objects.filter(reference=self.msgid).order_by('posted')
|
||||
|
@ -121,7 +121,8 @@ form {
|
||||
}
|
||||
|
||||
|
||||
.frontpage.posts {
|
||||
.posts {
|
||||
flex-direction: column;
|
||||
width: 50%;
|
||||
padding-top: 10%;
|
||||
padding-left: 25%;
|
||||
|
@ -8,7 +8,8 @@ urlpatterns = [
|
||||
url(r'^overchan\.(?P<name>[a-zA-Z0-9\.]+)-(?P<page>[0-9]+)\.html$', views.BoardView.as_view(), name='old-board'),
|
||||
url(r'^overchan\.(?P<name>[a-zA-Z0-9\.]+)/', views.BoardView.as_view(), name='board-alt'),
|
||||
url(r'^thread-(?P<op>[a-fA-F0-9\.]{40})\.html$', views.ThreadView.as_view(), name='old-thread'),
|
||||
url(r'^b/(?P<name>[a-zA-Z0-9]+)/$', views.BoardView.as_view(), name='board'),
|
||||
url(r'^b/(?P<name>[a-zA-Z0-9\.]+[a-zA-Z0-9])/$', views.BoardView.as_view(), name='board'),
|
||||
url(r'^t/(?P<op>[a-fA-F0-9\.]{40})/$', views.ThreadView.as_view(), name='thread'),
|
||||
url(r'captcha.png', views.create_captcha, name='captcha'),
|
||||
url(r'^$', views.FrontPageView.as_view(), name='index'),
|
||||
]
|
||||
|
@ -1,8 +1,15 @@
|
||||
from django.conf import settings
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
from datetime import datetime
|
||||
import time
|
||||
import string
|
||||
import random
|
||||
import nntplib
|
||||
import email.message
|
||||
|
||||
def hashid(msgid):
|
||||
h = hashlib.sha1()
|
||||
@ -24,3 +31,50 @@ def msgid_valid(msgid):
|
||||
def time_int(dtime):
|
||||
return time.mktime(dtime.timetuple())
|
||||
|
||||
def randstr(l, base=string.digits):
|
||||
r = ''
|
||||
while l > 0:
|
||||
r += random.choice(base)
|
||||
l -= 1
|
||||
return r
|
||||
|
||||
|
||||
def createPost(newsgroup, ref, form, files):
|
||||
"""
|
||||
create a post and post it to a news server
|
||||
"""
|
||||
|
||||
msg = email.message.Message()
|
||||
msg['Content-Type'] = 'multipart/mixed'
|
||||
msg["Subject"] = form["subject"] or "None"
|
||||
msg['Date'] = email.utils.format_datetime(datetime.now())
|
||||
msg['Message-ID'] = email.utils.make_msgid(randstr(13), settings.FRONTEND_NAME)
|
||||
if not msgid_valid(ref):
|
||||
return None, "invalid reference: {}".format(ref)
|
||||
msg["References"] = ref
|
||||
msg["Newsgroups"] = newsgroup
|
||||
msg["From"] = '{} <anon@django.nntpchan.tld>'.format(form['name'] or 'Anonymous')
|
||||
if 'attachment' in files:
|
||||
f = files['attachment']
|
||||
part = email.message.Message()
|
||||
part['Content-Type'] = f.content_type
|
||||
part['Content-Disposition'] = 'form-data; filename="{}"; name="attachment"'.format(f.name)
|
||||
part['Content-Transfer-Encoding'] = 'base64'
|
||||
part.set_payload(base64.b64encode(f.read()))
|
||||
msg.attach(part)
|
||||
text = email.message.Message()
|
||||
m = '{}'.format(form['message'] or ' ')
|
||||
text.set_payload(m)
|
||||
text['Content-Type'] = 'text/plain'
|
||||
msg.attach(text)
|
||||
|
||||
server = settings.NNTP_SERVER
|
||||
server['readermode'] = True
|
||||
msgid = None
|
||||
try:
|
||||
with nntplib.NNTP(**server) as nntp:
|
||||
nntp.login(**settings.NNTP_LOGIN)
|
||||
msgid = nntp.post(msg.as_bytes())
|
||||
except Exception as e:
|
||||
return None, e
|
||||
return msgid, None
|
||||
|
@ -1,3 +1,4 @@
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
@ -6,18 +7,40 @@ from django.views import generic
|
||||
|
||||
from .models import Post, Newsgroup
|
||||
|
||||
from captcha.image import ImageCaptcha
|
||||
from . import util
|
||||
|
||||
captcha = ImageCaptcha(fonts=settings.CAPTCHA_FONTS)
|
||||
|
||||
|
||||
class Postable:
|
||||
"""
|
||||
postable view
|
||||
checks captcha etc
|
||||
"""
|
||||
|
||||
def context_for_get(self, request, defaults):
|
||||
defaults['captcha'] = reverse('captcha')
|
||||
defaults['refresh_url'] = request.path
|
||||
return defaults
|
||||
|
||||
def handle_post(self, request, **kwargs):
|
||||
"""
|
||||
handle post request, implement in subclass
|
||||
"""
|
||||
return None, 'handle_post() not implemented'
|
||||
|
||||
def post(self, request, **kwargs):
|
||||
ctx = {
|
||||
'error' : None
|
||||
'error' : 'invalid captcha'
|
||||
}
|
||||
|
||||
|
||||
solution = request.session['captcha']
|
||||
if solution is not None:
|
||||
if 'captcha' in request.POST:
|
||||
if request.POST['captcha'].lower() == solution.lower():
|
||||
ctx['msgid'], ctx['error'] = self.handle_post(request, **kwargs)
|
||||
request.session['captcha'] = ''
|
||||
request.session.save()
|
||||
|
||||
return render(request, 'frontend/postresult.html', ctx)
|
||||
|
||||
@ -27,6 +50,16 @@ class BoardView(generic.View, Postable):
|
||||
context_object_name = 'threads'
|
||||
model = Post
|
||||
|
||||
def handle_post(self, request, name):
|
||||
"""
|
||||
make a new thread
|
||||
"""
|
||||
name = 'overchan.{}'.format(name)
|
||||
if not util.newsgroup_valid(name):
|
||||
return None, "invalid newsgroup: {}".format(name)
|
||||
return util.createPost(name, '', request.POST, request.FILES)
|
||||
|
||||
|
||||
def get(self, request, name):
|
||||
page = 0
|
||||
if 'p' in request.GET:
|
||||
@ -46,20 +79,32 @@ class BoardView(generic.View, Postable):
|
||||
begin = page * group.posts_per_page
|
||||
end = begin + group.posts_per_page - 1
|
||||
roots = self.model.objects.filter(newsgroup=group, reference='').order_by('-last_bumped')[begin:end]
|
||||
ctx = {'threads': roots, 'page': page, 'name': newsgroup}
|
||||
ctx = self.context_for_get(request, {'threads': roots, 'page': page, 'name': newsgroup})
|
||||
if page < group.max_pages:
|
||||
ctx['nextpage'] = reverse('board', args=[name]) + '?p={}'.format(page + 1)
|
||||
if page > 0:
|
||||
ctx['prevpage'] = reverse('board', args=[name]) + '?p={}'.format(page - 1)
|
||||
return render(request, self.template_name, ctx)
|
||||
|
||||
class ThreadView(generic.ListView, Postable):
|
||||
class ThreadView(generic.View, Postable):
|
||||
template_name = 'frontend/thread.html'
|
||||
model = Post
|
||||
context_object_name = 'op'
|
||||
|
||||
def get_queryset(self):
|
||||
return get_object_or_404(self.model, posthash=self.kwargs['op'])
|
||||
def handle_post(self, request, op):
|
||||
"""
|
||||
make a new thread
|
||||
"""
|
||||
post = get_object_or_404(self.model, posthash=op)
|
||||
name = post.newsgroup.name
|
||||
if not util.newsgroup_valid(name):
|
||||
return None, "invalid newsgroup: {}".format(name)
|
||||
return util.createPost(name, post.msgid, request.POST, request.FILES)
|
||||
|
||||
|
||||
def get(self, request, op):
|
||||
posts = get_object_or_404(self.model, posthash=op)
|
||||
ctx = self.context_for_get(request, {'op': posts})
|
||||
return render(request, self.template_name, ctx)
|
||||
|
||||
class FrontPageView(generic.View):
|
||||
template_name = 'frontend/frontpage.html'
|
||||
@ -77,3 +122,12 @@ def modlog(request, page):
|
||||
if page is None:
|
||||
page = 0
|
||||
return HttpResponse('mod log page {}'.format(page))
|
||||
|
||||
def create_captcha(request):
|
||||
solution = util.randstr(7).lower()
|
||||
request.session['captcha'] = solution
|
||||
request.session.save()
|
||||
c = captcha.generate(solution)
|
||||
r =HttpResponse(c)
|
||||
r['Content-Type'] = 'image/png'
|
||||
return r
|
||||
|
@ -9,7 +9,7 @@ https://docs.djangoproject.com/en/1.10/topics/settings/
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||
"""
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
@ -133,3 +133,23 @@ MEDIA_URL = '/media/'
|
||||
# for thumbnailing
|
||||
CONVERT_PATH = '/usr/bin/convert'
|
||||
FFMPEG_PATH = '/usr/bin/ffmpeg'
|
||||
|
||||
|
||||
# for captcha
|
||||
CAPTCHA_FONT_DIR = os.path.join(ASSETS_ROOT, 'fonts')
|
||||
CAPTCHA_FONTS = glob.glob(os.path.join(CAPTCHA_FONT_DIR, '*.ttf'))
|
||||
|
||||
# for nntp server
|
||||
# see nntplib module for more info
|
||||
NNTP_SERVER = {
|
||||
'host': '127.0.0.1',
|
||||
'port': 1129
|
||||
}
|
||||
|
||||
# nntp server login credentials
|
||||
NNTP_LOGIN = {
|
||||
'user': None,
|
||||
'password': None
|
||||
}
|
||||
|
||||
FRONTEND_NAME = 'ebin.tld'
|
||||
|
@ -8,6 +8,7 @@
|
||||
<script type="text/javascript" src="{% static 'postform.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'banners.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'settings.js' %}"></script>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper">
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% load captcha %}
|
||||
<div class="postform">
|
||||
<form id="postform" method="post" action="">
|
||||
<form id="postform" method="post" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<table class="postform">
|
||||
<tbody>
|
||||
@ -33,15 +33,7 @@
|
||||
<th>
|
||||
</th>
|
||||
<td>
|
||||
<input class="postform_attachment" id="postform_attachments" type="file" name="attachment_uploaded" multiple />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
|
||||
</th>
|
||||
<td>
|
||||
<input type="checkbox" name="dubs" />
|
||||
<input class="postform_attachment" id="postform_attachments" type="file" name="attachment" multiple />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -1,9 +1,12 @@
|
||||
{% extends "frontend/base.html" %}
|
||||
{% block head %}
|
||||
<meta http-equiv="refresh" content="2; {{refresh_url}}"></meta>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if error %}
|
||||
<pre class="error"> failed to post {{error}} </pre>
|
||||
<pre class="error"> failed to post: {{error}} </pre>
|
||||
{% else %}
|
||||
{% if msgid %}
|
||||
<pre class="posted"> posted as {{msgid}} </pre>
|
||||
|
@ -5,6 +5,7 @@
|
||||
<span class="navbar_link"><a href="{{op.newsgroup.get_absolute_url}}">back to {{op.newsgroup.name}}</a></span>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% include "frontend/postform.html" %}
|
||||
<div id="{{op.posthash}}" class="thread">
|
||||
<div class="post op">
|
||||
<div class="header">
|
||||
|
@ -105,6 +105,8 @@ def webhook(request):
|
||||
post.save()
|
||||
|
||||
for att in atts:
|
||||
if post.has_attachment(att.filehash):
|
||||
continue
|
||||
post.attachments.add(att)
|
||||
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
captcha
|
||||
django
|
||||
psycopg2
|
||||
|
Reference in New Issue
Block a user