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.
@ -61,6 +61,12 @@ class Post(models.Model):
|
|||||||
posted = models.IntegerField(default=0)
|
posted = models.IntegerField(default=0)
|
||||||
placeholder = models.BooleanField(default=False)
|
placeholder = models.BooleanField(default=False)
|
||||||
last_bumped = models.IntegerField(default=0)
|
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):
|
def get_all_replies(self):
|
||||||
if self.is_op():
|
if self.is_op():
|
||||||
|
@ -121,7 +121,8 @@ form {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.frontpage.posts {
|
.posts {
|
||||||
|
flex-direction: column;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
padding-top: 10%;
|
padding-top: 10%;
|
||||||
padding-left: 25%;
|
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\.]+)-(?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'^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'^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'^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'),
|
url(r'^$', views.FrontPageView.as_view(), name='index'),
|
||||||
]
|
]
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
import time
|
import time
|
||||||
|
import string
|
||||||
|
import random
|
||||||
|
import nntplib
|
||||||
|
import email.message
|
||||||
|
|
||||||
def hashid(msgid):
|
def hashid(msgid):
|
||||||
h = hashlib.sha1()
|
h = hashlib.sha1()
|
||||||
@ -24,3 +31,50 @@ def msgid_valid(msgid):
|
|||||||
def time_int(dtime):
|
def time_int(dtime):
|
||||||
return time.mktime(dtime.timetuple())
|
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.core.urlresolvers import reverse
|
||||||
from django.http import HttpResponse, Http404
|
from django.http import HttpResponse, Http404
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
@ -6,18 +7,40 @@ from django.views import generic
|
|||||||
|
|
||||||
from .models import Post, Newsgroup
|
from .models import Post, Newsgroup
|
||||||
|
|
||||||
|
from captcha.image import ImageCaptcha
|
||||||
|
from . import util
|
||||||
|
|
||||||
|
captcha = ImageCaptcha(fonts=settings.CAPTCHA_FONTS)
|
||||||
|
|
||||||
|
|
||||||
class Postable:
|
class Postable:
|
||||||
"""
|
"""
|
||||||
postable view
|
postable view
|
||||||
checks captcha etc
|
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):
|
def post(self, request, **kwargs):
|
||||||
ctx = {
|
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)
|
return render(request, 'frontend/postresult.html', ctx)
|
||||||
|
|
||||||
@ -27,6 +50,16 @@ class BoardView(generic.View, Postable):
|
|||||||
context_object_name = 'threads'
|
context_object_name = 'threads'
|
||||||
model = Post
|
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):
|
def get(self, request, name):
|
||||||
page = 0
|
page = 0
|
||||||
if 'p' in request.GET:
|
if 'p' in request.GET:
|
||||||
@ -45,21 +78,33 @@ class BoardView(generic.View, Postable):
|
|||||||
else:
|
else:
|
||||||
begin = page * group.posts_per_page
|
begin = page * group.posts_per_page
|
||||||
end = begin + group.posts_per_page - 1
|
end = begin + group.posts_per_page - 1
|
||||||
roots = self.model.objects.filter(newsgroup=group, reference='').order_by('-last_bumped')[begin:end]
|
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:
|
if page < group.max_pages:
|
||||||
ctx['nextpage'] = reverse('board', args=[name]) + '?p={}'.format(page + 1)
|
ctx['nextpage'] = reverse('board', args=[name]) + '?p={}'.format(page + 1)
|
||||||
if page > 0:
|
if page > 0:
|
||||||
ctx['prevpage'] = reverse('board', args=[name]) + '?p={}'.format(page - 1)
|
ctx['prevpage'] = reverse('board', args=[name]) + '?p={}'.format(page - 1)
|
||||||
return render(request, self.template_name, ctx)
|
return render(request, self.template_name, ctx)
|
||||||
|
|
||||||
class ThreadView(generic.ListView, Postable):
|
class ThreadView(generic.View, Postable):
|
||||||
template_name = 'frontend/thread.html'
|
template_name = 'frontend/thread.html'
|
||||||
model = Post
|
model = Post
|
||||||
context_object_name = 'op'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def handle_post(self, request, op):
|
||||||
return get_object_or_404(self.model, posthash=self.kwargs['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):
|
class FrontPageView(generic.View):
|
||||||
template_name = 'frontend/frontpage.html'
|
template_name = 'frontend/frontpage.html'
|
||||||
@ -77,3 +122,12 @@ def modlog(request, page):
|
|||||||
if page is None:
|
if page is None:
|
||||||
page = 0
|
page = 0
|
||||||
return HttpResponse('mod log page {}'.format(page))
|
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
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/1.10/ref/settings/
|
https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||||
"""
|
"""
|
||||||
|
import glob
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
@ -133,3 +133,23 @@ MEDIA_URL = '/media/'
|
|||||||
# for thumbnailing
|
# for thumbnailing
|
||||||
CONVERT_PATH = '/usr/bin/convert'
|
CONVERT_PATH = '/usr/bin/convert'
|
||||||
FFMPEG_PATH = '/usr/bin/ffmpeg'
|
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 'postform.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'banners.js' %}"></script>
|
<script type="text/javascript" src="{% static 'banners.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'settings.js' %}"></script>
|
<script type="text/javascript" src="{% static 'settings.js' %}"></script>
|
||||||
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{% load captcha %}
|
{% load captcha %}
|
||||||
<div class="postform">
|
<div class="postform">
|
||||||
<form id="postform" method="post" action="">
|
<form id="postform" method="post" action="" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table class="postform">
|
<table class="postform">
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -33,15 +33,7 @@
|
|||||||
<th>
|
<th>
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<input class="postform_attachment" id="postform_attachments" type="file" name="attachment_uploaded" multiple />
|
<input class="postform_attachment" id="postform_attachments" type="file" name="attachment" multiple />
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
<input type="checkbox" name="dubs" />
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
{% extends "frontend/base.html" %}
|
{% extends "frontend/base.html" %}
|
||||||
|
{% block head %}
|
||||||
|
<meta http-equiv="refresh" content="2; {{refresh_url}}"></meta>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% if error %}
|
{% if error %}
|
||||||
<pre class="error"> failed to post {{error}} </pre>
|
<pre class="error"> failed to post: {{error}} </pre>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if msgid %}
|
{% if msgid %}
|
||||||
<pre class="posted"> posted as {{msgid}} </pre>
|
<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>
|
<span class="navbar_link"><a href="{{op.newsgroup.get_absolute_url}}">back to {{op.newsgroup.name}}</a></span>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{% include "frontend/postform.html" %}
|
||||||
<div id="{{op.posthash}}" class="thread">
|
<div id="{{op.posthash}}" class="thread">
|
||||||
<div class="post op">
|
<div class="post op">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
@ -105,6 +105,8 @@ def webhook(request):
|
|||||||
post.save()
|
post.save()
|
||||||
|
|
||||||
for att in atts:
|
for att in atts:
|
||||||
|
if post.has_attachment(att.filehash):
|
||||||
|
continue
|
||||||
post.attachments.add(att)
|
post.attachments.add(att)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
captcha
|
captcha
|
||||||
django
|
django
|
||||||
|
psycopg2
|
||||||
|
Reference in New Issue
Block a user