Archived
1
0

it now works

This commit is contained in:
Jeff Becker 2016-11-06 17:47:23 -05:00
parent 14c68abf9d
commit f25f2cd956
No known key found for this signature in database
GPG Key ID: AB950234D6EA286B
13 changed files with 159 additions and 23 deletions

View File

@ -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():

View File

@ -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%;

View File

@ -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'),
] ]

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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)

View File

@ -1,2 +1,3 @@
captcha captcha
django django
psycopg2