Finished the merge, Disabling comments, Community bans.

This commit is contained in:
some weird guy
2023-08-12 14:55:51 -07:00
parent 46844a7cfb
commit 74ef0db34c
67 changed files with 870 additions and 759 deletions
+12 -2
View File
@@ -33,6 +33,12 @@ def Hide_content(modeladmin, request, queryset):
@admin.action(description='Show selected items')
def Show_content(modeladmin, request, queryset):
queryset.update(is_rm = False)
@admin.action(description='Disable comments')
def Disable_comments(modeladmin, request, queryset):
queryset.update(lock_comments = 2)
@admin.action(description='Enable comments')
def Enable_comments(modeladmin, request, queryset):
queryset.update(lock_comments = 0)
@admin.action(description='Feature selected communities')
def Feature_community(modeladmin, request, queryset):
queryset.update(is_feature = True)
@@ -48,12 +54,16 @@ def unforce_login(modeladmin, request, queryset):
@admin.action(description='Disable user')
def Disable_user(modeladmin, request, queryset):
queryset.update(active = False)
@admin.action(description='Enable user')
def Enable_user(modeladmin, request, queryset):
queryset.update(active = True)
class UserAdmin(admin.ModelAdmin):
search_fields = ('id', 'unique_id', 'username', 'nickname', 'email', )
list_display = ('id', 'username', 'nickname', 'warned', 'level', 'staff', 'active', )
exclude = ('addr', 'signup_addr', 'password', )
actions = [Disable_user]
actions = [Disable_user, Enable_user]
#exclude = ('staff', )
# Not yet
#form = UserForm
@@ -73,7 +83,7 @@ class PostAdmin(admin.ModelAdmin):
raw_id_fields = ('creator', 'poll', )
search_fields = ('id', 'unique_id', 'body', 'creator__username', )
list_display = ('id', 'creator', 'body', 'is_rm', )
actions = [Hide_content, Show_content]
actions = [Hide_content, Show_content, Disable_comments, Enable_comments]
def get_queryset(self, request):
return models.Post.real.get_queryset()
-2
View File
@@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.apps import AppConfig
+12
View File
@@ -0,0 +1,12 @@
try:
from closedverse.settings import brand_name
except ImportError:
# use default app name by default as brand name
from closedverse_main import apps
brand_name = apps.ClosedverseMainConfig.verbose_name
# the name of the function is merely what's imported into settings.py
def brand_name_universal(request):
# this returns what's actually newly available to the template
# so the name of the key actually dictates what you put in the tmpl
return {"brand_name": brand_name}
+8
View File
@@ -31,12 +31,17 @@ class ClosedMiddleware(object):
return redirect('https://{0}{1}'.format(request.get_host(), request.get_full_path()))
"""
if request.user.is_authenticated:
"""
if not request.user.is_active():
if request.user.warned_reason:
ban_msg = request.user.warned_reason
else:
ban_msg = 'You are banned.'
return HttpResponseForbidden(ban_msg)
"""
# can just forbid post requests for the time being (but leav our funny logout message :3)
if not request.user.is_active() and request.method != 'GET' and request.get_full_path() != '/logout/':
return HttpResponseForbidden()
# If there isn't a request.session
if not request.session.get('passwd'):
request.session['passwd'] = request.user.password
@@ -44,5 +49,8 @@ class ClosedMiddleware(object):
if request.session['passwd'] != request.user.password:
logout(request)
response = self.get_response(request)
if request.user.is_authenticated:
# for reverse proxy
response['X-Username'] = request.user.username
return response
+89 -40
View File
@@ -10,6 +10,7 @@ from django.core.exceptions import ValidationError
from datetime import timedelta, datetime, date, time
from passlib.hash import bcrypt_sha256
from closedverse import settings
from closedverse_main.context_processors import brand_name
from . import util
from random import getrandbits
import uuid, json, base64
@@ -98,6 +99,8 @@ class UserManager(BaseUserManager):
return (user, 2)
else:
if not passwd:
#if user.can_manage():
# return None
return (user, False)
return (user, True)
@@ -117,7 +120,6 @@ class ColorField(models.CharField):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 18
super(ColorField, self).__init__(*args, **kwargs)
#mii_domain = 'https://mii-secure.cdn.nintendo.net'
# as of writing, mii-secure is unstable, nintendo please do not f*ck me for this
mii_domain = 'https://s3.us-east-1.amazonaws.com/mii-images.account.nintendo.net/'
@@ -137,7 +139,7 @@ class User(models.Model):
# LEVEL: 0-1 is default, everything else is just levels
level = models.SmallIntegerField(default=0)
# ROLE: This doesn't have anything
role = models.SmallIntegerField(default=0, choices=((0, 'normal'), (1, 'Bot'), (2, 'Administrator'), (3, 'Moderator'), (4, 'NO'), (5, 'Donator'), (6, 'Tester'), (7, 'Cools'), (8, 'Developer'), (9, 'SMF9-Django'), (10, 'Staff'), (11, 'GAY DOGWATER' ), ( 12, 'DUMB SNAIL' ), (13, 'Russian ADRIAN'), (14, 'Contest'), (15, 'Gamecon'), (16, 'Cedar'), ))
role = models.SmallIntegerField(default=0, choices=((0, 'normal'), (1, 'bot'), (2, 'administrator'), (3, 'moderator'), (4, 'openverse'), (5, 'donator'), (6, 'cool'), (7, 'urapp'), (8, 'owner'), (9, 'badgedes'), (10, 'jack'), (11, 'verified'),))
addr = models.CharField(max_length=64, null=True, blank=True)
signup_addr = models.CharField(max_length=64, null=True, blank=True)
user_agent = models.TextField(null=True, blank=True)
@@ -150,7 +152,6 @@ class User(models.Model):
color = ColorField(default='', null=True, blank=True)
staff = models.BooleanField(default=False)
#active = models.SmallIntegerField(default=1, choices=((0, 'Redirect'), (1, 'Good'), (2, 'Disabled')))
active = models.BooleanField(default=True)
can_invite = models.BooleanField(default=True)
warned = models.BooleanField(default=False)
@@ -238,40 +239,30 @@ class User(models.Model):
def get_class(self):
first = {
1: 'tester',
1: 'cool',
2: 'administrator',
3: 'moderator',
4: 'openverse',
5: 'donator',
6: 'tester',
6: 'cool',
7: 'urapp',
8: 'developer',
9: 'pipinstalldjango',
10: 'staff',
11: 'kanna',
12: 'verified',
13: 'artcon',
14: 'contest',
15: 'gamecom',
16: 'mp',
9: 'badgedes',
10: 'jack',
11: 'verifiedd',
}.get(self.role, '')
second = {
1: "Bot",
2: "Administrator",
3: "Moderator",
4: "No",
4: "O-PHP-enverse Man",
5: "Donator",
6: "Tester",
7: "Cool Dude",
8: "Wii U still best console.",
9: "Rixy Installed Django!",
10: "Staff",
11: "GAY DOGWATER ETC",
12: "THE STUPIDEST ROLE IN THE WORLD",
13: "A D R I A N",
14: "Contest Winner",
15: "Game Contest Winner",
16: "Cedar Inc.",
6: "Cool Person",
7: "cave story is okay",
8: "owner guy",
9: "Badge Designer",
10: "stupid man",
11: "Verified",
}.get(self.role, '')
if first:
first = 'official ' + first
@@ -555,15 +546,14 @@ class User(models.Model):
def password_reset_email(self, request):
htmlmsg = render_to_string('closedverse_main/help/email.html', {
'menulogo': request.build_absolute_uri(settings.STATIC_URL + 'img/menu-logo.png'),
'contact': 'something',
'contact': request.build_absolute_uri(reverse('main:help-contact')),
'link': request.build_absolute_uri(reverse('main:forgot-passwd')) + "?token=" + base64.urlsafe_b64encode(bytes(self.password, 'utf-8')).decode(),
})
subj = 'Closedverse password reset for "{0}"'.format(self.username)
subj = '{1} password reset for "{0}"'.format(self.username, brand_name)
return send_mail(
subject='Reset password',
subject=subj,
html_message=htmlmsg,
message=htmlmsg,
from_email="noreply@" + settings.YOUR_DOMAIN,
from_email="{1} <{0}>".format(settings.DEFAULT_FROM_EMAIL, brand_name),
recipient_list=[self.email],
fail_silently=False)
def find_related(self):
@@ -711,7 +701,19 @@ class Community(models.Model):
#print(str(post) + ' to ' + str(self.creator) + ':' + str(post.user_is_blocked))
post.recent_comment = post.recent_comment()
return posts
def Community_block(self, request):
# This goes both ways.
if request.user.is_authenticated and not request.user.can_manage():
if UserBlock.find_block(self.creator, request.user):
return True
return False
def post_perm(self, request):
if not request.user.active:
return False
if self.Community_block(request):
return False
if request.user.level >= self.rank_needed_to_post:
return True
elif request.user.staff == True:
@@ -766,7 +768,7 @@ class Community(models.Model):
URLValidator()(value=request.POST['url'])
except ValidationError:
return 5
if not request.user.has_freedom() and (request.POST.get('url') or request.FILES.get('screen')):
if not request.user.has_freedom() and (request.POST.get('url') or request.FILES.get('screen') or request.FILES.get('video')):
return 6
if not request.user.is_active():
return 6
@@ -774,14 +776,19 @@ class Community(models.Model):
return 1
upload = None
drawing = None
video = None
body = request.POST.get('body')
for c in body:
if unicodedata.combining(c):
return 11
return 12
if request.FILES.get('screen'):
upload = util.image_upload(request.FILES['screen'], True)
if upload == 1:
return 2
if request.FILES.get('video'):
video = util.video_upload(request.FILES['video'])
if video == 1:
return 11
if request.POST.get('_post_type') == 'painting':
if not request.POST.get('painting'):
return 2
@@ -796,7 +803,7 @@ class Community(models.Model):
return 9
if body.isspace() and not drawing:
return 10
new_post = self.post_set.create(body=body, creator=request.user, community=self, feeling=int(request.POST.get('feeling_id', 0)), spoils=bool(request.POST.get('is_spoiler')), screenshot=upload, drawing=drawing, url=request.POST.get('url'))
new_post = self.post_set.create(body=body, creator=request.user, community=self, feeling=int(request.POST.get('feeling_id', 0)), spoils=bool(request.POST.get('is_spoiler')), screenshot=upload, drawing=drawing, url=request.POST.get('url'), video=video)
new_post.is_mine = True
return new_post
@@ -826,9 +833,11 @@ class Post(models.Model):
body = models.TextField(null=True)
drawing = models.CharField(max_length=200, null=True, blank=True)
screenshot = models.CharField(max_length=1200, null=True, blank=True, default='')
video = models.CharField(max_length=256, null=True, blank=True, default='')
url = models.URLField(max_length=1200, null=True, blank=True, default='')
spoils = models.BooleanField(default=False)
disable_yeah = models.BooleanField(default=False)
lock_comments = models.SmallIntegerField(default=0, choices=((0, 'Not locked'), (1, 'Locked by user'), (2, 'Locked by mod')))
created = models.DateTimeField(auto_now_add=True)
edited = models.DateTimeField(auto_now=True)
befores = models.TextField(null=True, blank=True)
@@ -892,10 +901,12 @@ class Post(models.Model):
else:
return False
def can_yeah(self, request):
if not request.user.is_authenticated or not request.user.is_active():
if not request.user.is_authenticated:
return False
if not request.user.is_active():
return False
if self.community.Community_block(request):
return False
# why did cedar-django do this? god knows
#return True
if self.is_mine(request.user) or UserBlock.find_block(self.creator, request.user):
return False
return True
@@ -923,12 +934,44 @@ class Post(models.Model):
def get_yeahs(self, request):
return Yeah.objects.filter(type=0, post=self).order_by('-created')[0:30]
def can_comment(self, request):
# TODO: Make this so that if a post's comments exceeds 100, make the user able to close the comments section
if self.number_comments() > 500:
return False
if not request.user.active:
return False
# yeah this is fucking nuts. It's basically a ban from an entire community.
if self.community.Community_block(request):
return False
if self.lock_comments != 0:
return False
if UserBlock.find_block(self.creator, request.user):
return False
return True
def can_lock_comments(self, request):
if self.lock_comments != 0:
return False
# If you are a mod, you can bypass the timer
# The timer is a personal choice of mine, I don't want users to pussy out of a fight too early or whatever.
# Always annoys me when someone has a dumb ass take only for them to turn off the comments immediately.
if self.created < timezone.now() - timedelta(hours=24) or request.user.can_manage():
if self.creator == request.user:
return True
if not self.creator.has_authority(request.user) and request.user.can_manage():
return True
return False
def lock_the_comments_up(self, request):
if request and self.can_lock_comments(request):
if self.is_mine(request.user):
self.lock_comments = 1
else:
self.lock_comments = 2
AuditLog.objects.create(type=3, post=self, user=self.creator, by=request.user)
self.save()
return True
else:
return False
def get_comments(self, request=None, limit=0, offset=0):
if request.user.is_authenticated:
blocked_me = request.user.block_target.filter().values('source')
@@ -967,7 +1010,7 @@ class Post(models.Model):
return 6
for c in request.POST['body']:
if unicodedata.combining(c):
return 11
return 12
if not request.user.is_active():
return 6
if len(request.POST['body']) > 2200 or (len(request.POST['body']) < 1 and not request.POST.get('_post_type') == 'painting'):
@@ -1269,7 +1312,7 @@ class Profile(models.Model):
if self.let_friendrequest == 2:
return False
#if user.is_authenticated and UserBlock.find_block(self.user, user):
# return False
# return False
elif self.let_friendrequest == 1:
if not user.is_following(self.user):
return False
@@ -1709,7 +1752,7 @@ class UserBlock(models.Model):
class AuditLog(models.Model):
id = models.AutoField(primary_key=True)
created = models.DateTimeField(auto_now_add=True)
type = models.SmallIntegerField(choices=((0, "Post delete"), (1, "Comment delete"), (2, "User edit"), (3, "Generate passwd reset"), (4, "User delete"), (5, "Image delete"), (6, "Purge 1"), (7, "Purge 2"), (8, "Purge 3"), (9, "Purge 4"), (10, "Purge 5"), (11, "Un-purge 1"), (12, 'Changed server settings'), ))
type = models.SmallIntegerField(choices=((0, "Post delete"), (1, "Comment delete"), (2, "User edit"), (3, "Disable comments"), (4, "User delete"), (5, "Image delete"), (6, "Purge 1"), (7, "Purge 2"), (8, "Purge 3"), (9, "Purge 4"), (10, "Purge 5"), (11, "Un-purge 1"), (12, 'Changed server settings'), ))
post = models.ForeignKey(Post, related_name='audit_post', null=True, on_delete=models.CASCADE)
comment = models.ForeignKey(Comment, related_name='audit_comment', null=True, on_delete=models.CASCADE)
user = models.ForeignKey(User, related_name='audit_user', null=True, on_delete=models.CASCADE)
@@ -1762,6 +1805,9 @@ class Ads(models.Model):
adsavailable = False
return adsavailable
class Meta:
verbose_name_plural = "ads"
def __str__(self):
return "Ad with id " + str(self.id) + ", created at " + str(self.created) + ", with url " + str(self.url) + ", and imageurl " + str(self.imageurl)
@@ -1786,6 +1832,9 @@ class ProfileHistory(models.Model):
def __str__(self):
return str(self.user) + ' changed profile details'
class Meta:
verbose_name_plural = "profile histories"
# blah blah blah
# this method will be executed when...
@@ -28,7 +28,7 @@
<div id="js-main">
<div class="activity-feed content-loading-window">
<div>
{% discordapp_spinner %}
{% loading_spinner %}
<p class="tleft"><span>Loading activity feed...</span></p>
</div>
</div>
@@ -1,5 +1,5 @@
{% extends "closedverse_main/layout.html" %}
{% load closedverse_tags %}{% load closedverse_community %}{% block main-body %}
{% load closedverse_tags %}{% load closedverse_community %}{% load closedverse_user %}{% block main-body %}
<div class="community-main">
<div id="community-eyecatch"></div>
</div>
@@ -7,8 +7,13 @@
<form action="{% url "main:community-search" %}" class="search">
<input maxlength="32" name="query" placeholder="Search all communities" type="text"><input title="Search" type="submit" value="q">
</form>
<!--<div class="close-announce-container">
<div class="close-announce-link">
<a class="title" id="close-text-ann" href="/posts/1186"><b>Closedverse is still in indefinite maintenance.</b></a>
</div>
</div>-->
{% if user.is_warned %}
{% if user.is_warned and user.is_active %}
<div class="notice" style="background-color: #ffc783;border: 1px solid #ffb358;">
<p><b>WARNING</b>: You have been issued a warning by an administrator.
<div>
@@ -18,23 +23,35 @@
</div>
</div>
{% endif %}
{% if not user.is_active and user.is_authenticated %}
<div class="notice" style="background-color: #ff9797;border: 1px solid #ff5252;">
<p><b>Oops</b>: You've been smacked by an admin. Better luck next time.
<div>
{% if user.get_warned_reason %}
<p>Reason: "{{ user.get_warned_reason }}"</p>
{% endif %}
</div>
</div>
{% endif %}
{% if announcements and request.user.is_authenticated %}
<div class="Announcements">
{% for announcements in announcements %}
<div class='Announcement-content'>
{% user_icon_container announcements.creator announcements.feeling %}
<a class='user-name' href={% url "main:user-view" announcements.creator.username %} {% if announcements.creator.color %}style=color:{{ announcements.creator.color }}{% endif %}>{{ announcements.creator }}</a><a class='timestamp"'> - {{ announcements.created }}</a>
<p>{{ announcements.body|linebreaksbr|urlize }}</p>
{% if announcements.screenshot %}<image class='Announcement-image' src={{ announcements.screenshot }}></image>{% endif %}
</div>
{% endfor %}
</div>
<div class="tleft">
<div class="post-list-outline">
<h2 class="label">Latest Announcements</h2>
<div class="post-body">
<div class="list multi-timeline-post-list">
{% for announcement in announcements %}
{% profile_post announcement True %}
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
{% if WelcomeMSG and not request.user.is_authenticated %}
<div class="post-list-outline index-memo">
<h2 class="label">Welcome to Cedar!</h2>
<h2 class="label">Welcome to {{ brand_name }}!</h2>
{% for WelcomeMSG in WelcomeMSG %}
<h2>{{ WelcomeMSG.Title }}</h2>
<p>{{ WelcomeMSG.message|linebreaksbr|urlize }}</p>
@@ -51,7 +68,7 @@
</div>
{% endif %}
</div>
<div class="community-main">
<div class="community-main" id="communities">
{% if favorites %}
<h3 class="community-title symbol community-favorite-title">Favorite communities</h3>
<div class="card" id="community-favorite">
@@ -74,12 +91,12 @@
{% if feature %}
{% community_page_element feature "Featured Communities" True %}
{% endif %}
{% if general %}{% community_page_element general "General Communities" %}{% endif %}
{% if game %}{% community_page_element game "Game Communities" %}{% endif %}
{% if special %}{% community_page_element special "Special Communities" %}{% endif %}
{% if user_communities %}{% community_page_element user_communities "Popular User-Created Communities" %}{% endif %}
{% if my_communities %}{% community_page_element my_communities "My Communities" %}{% endif %}
<a href="{% url "main:community-viewall" "gen" %}" class="big-button">Show more</a>
{% community_page_element general "General Communities" False "general" %}
{% community_page_element game "Game Communities" False "game" %}
{% community_page_element special "Special Communities" False "special" %}
{% community_page_element user_communities "Popular User-Created Communities" False "usr" %}
{% if user_communities %}<a href="{% url "main:community-viewall" "usr" %}" class="big-button">Show more</a>{% endif %}
{% community_page_element my_communities "My Communities" %}
</div>
<div id="community-guide-footer">
<div id="guide-menu">
@@ -1,18 +1,8 @@
{% extends "closedverse_main/layout.html" %}{% block main-body %}{% load closedverse_community %}
{% community_sidebar community request %}
<div class="main-column">
{% if user.is_warned %}
<div class="notice" style="background-color: #ffc783;border: 1px solid #ffb358;">
<p><b>WARNING</b>: You have been issued a warning by an administrator.
<div>
{% if user.get_warned_reason %}
<p>Reason: "{{ user.get_warned_reason }}"</p>
{% endif %}
</div>
</div>
{% endif %}
<div class="post-list-outline">
<h2 class="label">{{ community.name }}:</h2>
<h2 class="label">{{ community.name }}</h2>
{% if request.user.is_authenticated and community.post_perm %}
{% post_form request.user community %}
{% endif %}
@@ -1,5 +1,4 @@
{% load closedverse_community %}
{% if user.is_active %}
<form id="reply-form" class="for-identified-user" method="post" action="{% url "main:post-comments" post.id %}">
{% csrf_token %}
{% if not user.limit_remaining is False %}
@@ -31,6 +30,8 @@
</div>
{% if user.has_freedom %}
{% file_button %}
<!-- hack to temporarily hide video input, for now... -->
<style>div.file-button-container{display:none;}</style>
{% endif %}
<div class="post-form-footer-options">
@@ -47,8 +48,3 @@
<input type="submit" class="black-button reply-button disabled" value="Send" data-community-id="{{ post.community.unique_id }}" data-url-id="{{ post.id }}" data-post-content-type="text" data-post-with-screenshot="nodata" disabled="">
</div>
</form>
{% endif %}
{% if not user.is_active %}
<p class="center">Your account has been disabled.</p>
{% endif %}
@@ -1,5 +1,6 @@
{% load closedverse_tags %}{% if title %}<h3 class="community-title">{{ title }}</h3>
{% endif %}{% if communities %}
{% load closedverse_tags %}{% if communities %}
{% if title %}<h3 class="community-title">{{ title }}</h3>
{% endif %}
<ul class="list community-list{% if title %} community-card-list {% if feature %}test-community-list-item{% else %}device-new-community-list{% endif %}{% endif %}">
{% for community in communities %}
<li class="trigger" data-href="{% url "main:community-view" community.id %}"{% if not title %} class="test-community-list-item"{% endif %}>
@@ -21,12 +22,15 @@
</li>
{% endfor %}
</ul>
{% if url_name and communities.count >= 12 %}
<a href="{% url "main:community-viewall" url_name %}" class="big-button">Show more</a>
{% endif %}
{% else %}
{% if title %}<div class="post-list-outline">{% endif %}
<!--{% if title %}<div class="post-list-outline">{% endif %}-->
{% if title %}
{% nocontent "No communities of this type have been created yet." %}
<!--{% nocontent "No communities of this type have been created yet." %}-->
{% else %}
{% nocontent "You haven't favorited any communities yet. Use the favorite button in a community to add it here." %}
{% endif %}
{% if title %}</div>{% endif %}
{% endif %}
<!--{% if title %}</div>{% endif %}-->
{% endif %}
@@ -44,7 +44,8 @@
{% endif %}
{% endif %}
{% if post.screenshot %}<a class="screenshot-container still-image" href="{% if post.is_reply %}{% url "main:comment-view" post.id %}{% else %}{% url "main:post-view" post.id %}{% endif %}"><img src="{{ post.screenshot }}"></a>{% endif %}
{% if post.spoils and post.creator.is_active %}
{% if post.video %}<div class="screenshot-container still-image"><video class="vidya" controls src="{{ post.video }}" style="max-width:100%;max-height: 450px;"></video></div>{% endif %}
{% if post.spoils and not post.is_mine and post.creator.is_active %}
<div class="hidden-content"><p>This post contains spoilers.</p>
<button type="button" class="hidden-content-button">View Post</button>
</div>
@@ -1,4 +1,9 @@
<div id="sidebar">
{% if Community_block %}
<div class="notice" style="background-color: #ff9797;border: 1px solid #ff5252;">
<p><b>Oops</b>: You're either blocked by, or blocking {{ community.creator }}, who owns this community. You cannot Yeah, Reply, or Post here.
</div>
{% endif %}
<section class="sidebar-container" id="sidebar-community">
{% if community.banner %}
<span id="sidebar-cover">
@@ -22,8 +27,6 @@
<span class="news-community-badge">Announcement Community</span>
{% elif community.tags == 'changelog' %}
<span class="news-community-badge">Changelog Community</span>
{% elif community.tags == 'general' %}
<span class="news-community-badge">General</span>
{% endif %}
<h1 class="community-name">
<a href="{% url "main:community-view" community.id %}">{{ community.name }}
@@ -1 +0,0 @@
<p>I'm loading it !</p>
@@ -7,10 +7,16 @@
<label class="file-button-container">
<span class="input-label">Image <span id="image-dimensions">PNG and JPEG are allowed.</span></span>
<span class="button file-upload-button">Upload</span>
<input accept="image/gif, image/png, image/jpeg, image/jpg, image/svg+xml, image/bmp" type="file" style="display: none;" id="upload-file">
<input type="hidden" id="upload-input" name="screen">
<input accept="image/gif, image/png, image/jpeg, image/jpg, image/svg+xml, image/bmp" type="file" style="display: none;" id="upload-file" name="screen">
<div id="upload-preview-container" class="screenshot-container still-image" style="display: none;">
<img id="upload-preview">
<!-- not functional currently
<video id="video-upload-preview"></video>
-->
</div>
</label>
<div class="file-button-container">
<span class="input-label">Video <span>MP4s and WEBMs are allowed. (50MB size limit)</span></span>
<input type="file" class="file-button" name="video" accept="video/mp4, video/webm">
</div>
@@ -0,0 +1 @@
<span class="open-spin"></span>
@@ -1,5 +1,4 @@
{% load closedverse_community %}
{% if user.is_active %}
<form id="post-form" method="post" action="{% url "main:messages-view" friend.username %}" class="folded">
{% csrf_token %}
{% feeling_selector %}
@@ -28,15 +27,8 @@
<div class="post-form-footer-options">
</div>
<div class="form-buttons">
<input type="submit" class="black-button post-button disabled" value="Send" data-post-content-type="text" disabled="">
</div>
</form>
{% endif %}
{% if not user.is_active %}
<p class="center">Your account has been disabled.</p>
{% endif %}
</form>
@@ -1,5 +1,4 @@
{% load closedverse_community %}
{% if user.is_active %}
<form id="post-form" method="post" action="{% url "main:post-create" community.id %}" class="folded for-identified-user" data-post-subtype="default" name="test-post-default-form">
{% csrf_token %}
<input type="hidden" name="community" value="{{ community.unique_id }}">
@@ -63,8 +62,3 @@
<input type="submit" class="black-button post-button disabled" value="Send" data-community-id="{{ community.unique_id }}" data-post-content-type="text" data-post-with-screenshot="nodata" disabled="">
</div>
</form>
{% endif %}
{% if not user.is_active %}
<p class="center">Your account has been disabled.</p>
{% endif %}
@@ -1,8 +1,8 @@
{% load closedverse_tags %}<div id="{{ post.unique_id }}" {% if post.spoils and not post.is_mine or not post.creator.is_active %}data-href-hidden{% else %}data-href{% endif %}="{% url "main:post-view" post.id %}" class="post trigger{% if post.screenshot %} with-image{% endif %}{% if post.spoils and not post.is_mine or not post.creator.is_active %} hidden test-hidden{% endif %}" tabindex="0">
{% load closedverse_tags %}<div {% if post.spoils and not post.is_mine or not post.creator.is_active %}data-href-hidden{% else %}data-href{% endif %}="{% url "main:post-view" post.id %}" class="post trigger{% if post.screenshot and not for_announcements %} with-image{% endif %}{% if post.spoils and not post.is_mine or not post.creator.is_active %} hidden test-hidden{% endif %}" tabindex="0">
<p class="community-container"><a {% if post.community.clickable %}href="{% url "main:community-view" post.community_id %}"{% endif %}><img src="{{ post.community.icon }}" class="community-icon">{{ post.community.name }}</a></p>
<div class="body">
<div {% if not for_announcements %}class="body"{% endif %}>
<div class="post-content">
{% user_icon_container post.creator post.feeling %}
@@ -26,7 +26,10 @@
<div class="screenshot-container video"><video src="{{ post.url }}" width="50" height="48"></video></div>
{% endif %}
{% if post.screenshot %}
<a href="{% url "main:post-view" post.id %}" class="screenshot-container still-image"><img src="{{ post.screenshot }}"></a>
<a href="{% url "main:post-view" post.id %}" class="{% if not for_announcements %}screenshot-container {% endif %}still-image"><img src="{{ post.screenshot }}"></a>
{% endif %}
{% if post.video %}
<div class="screenshot-container video"><video src="{{ post.video }}" width="50" height="48"></video></div>
{% endif %}
{% if post.drawing %}
<p class="post-content-memo"><img src="{{ post.drawing }}" class="post-memo"></p>
@@ -50,6 +53,7 @@
<button type="button" class="hidden-content-button">View Anyway</button>
</p></div>
{% endif %}
{% if not for_announcements %}
<div class="post-meta">
<button type="button"{% if not post.can_yeah %} disabled{% endif %} class="symbol submit yeah-button
{% if post.has_yeah %}empathy-added{% endif %}
@@ -60,6 +64,7 @@
<div class="empathy symbol"><span class="symbol-label">Yeahs</span><span class="empathy-count">{{ post.number_yeahs }}</span></div>
<div class="reply symbol"><span class="symbol-label">Comments</span><span class="reply-count">{{ post.number_comments }}</span></div>
</div>
{% endif %}
</div>
</div>
@@ -21,11 +21,16 @@
<li>My notifications: <span> {{ notifications }}
</ul>
<h3>Restrictions:</h3>
{% if request.user.active %}
<ul>
<li>Sending images and creating new accounts: <span> {% if user.has_freedom %}Good standing{% else %}Restricted{% endif %}
<li>Post limit: <span> {% if request.user.profile.limit_post == 0 %}Good standing{% else %}{{ user.profile.limit_post }}{% endif %}
<li>Editing your profile: <span> {% if not user.profile.cannot_edit %}Good standing{% else %}Restricted{% endif %}
<li>Creating invites: <span> {% if user.can_invite %}Good standing{% else %}Restricted{% endif %}
</ul>
{% else %}
<p class='center'>You've been fucking banned lmao</p>
{% endif %}
<h3>Collected data:</h3>
<div class="user-data">
<p class="label">Account login history:</p>
@@ -4,14 +4,14 @@
<head>
<meta charset="utf-8">
{% endif %}
<title>{% if title %}{{ title }} - Cedar{% else %}Cedar{% endif %}</title>
<title>{% if title %}{{ title }} - {{ brand_name }} {% else %}{{ brand_name }}{% endif %}</title>
{% if not request.META.HTTP_X_PJAX %}
<meta http-equiv="content-style-type" content="text/css">
<meta http-equiv="content-script-type" content="text/javascript">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-title" content="Cedar">
<meta name="description" content="It's Closedverse but Cedar!">
<meta name="apple-mobile-web-app-title" content="{{ brand_name }}">
<meta name="description" content="{{ brand_name }} is a social network: designed by PF2M, programmed by Arian Kordi. With Miiverse DNA, style and familiar assets such as Miiverse's interface and Miis, you're sure to have fun here!">
<meta property="og:locale" content="en_US">
{% if ogdata %}
<meta property="og:title" content="{{ ogdata.title }}">
@@ -19,7 +19,7 @@
<meta property="og:url" content="{{ request.build_absolute_uri }}">
<meta property="og:image" content="{{ ogdata.image }}">
<meta property="og:description" content="{{ ogdata.description|truncatechars:150 }}">
<meta property="og:site_name" content="Cedar">
<meta property="og:site_name" content="{{ brand_name }}">
<meta property="article:published_time" content="{{ ogdata.date }}">
{% endif %}
<link rel="shortcut icon" type="image/png" href="{% static "img/favicon.png" %}">
@@ -62,6 +62,7 @@
document.documentElement.style.setProperty("--theme-slightly-dark", slightlyDarkColor);
document.documentElement.style.setProperty("--theme-darker", darkerColor);
document.documentElement.style.setProperty("--theme-light", lightColor);
{% if not request.user.bg_url %}document.documentElement.style.setProperty("--background", "url('img/indigo-pattern.png')");{% endif %}
}
// hex -> rgb
@@ -80,18 +81,19 @@
}
function toDefault() {
document.documentElement.style.setProperty("--theme", "initial");
document.documentElement.style.setProperty("--theme", "");
document.documentElement.style.setProperty("--theme-dark", "initial");
document.documentElement.style.setProperty("--theme-slightly-dark", "initial");
document.documentElement.style.setProperty("--theme-darker", "initial");
document.documentElement.style.setProperty("--theme-light", "initial");
document.documentElement.style.setProperty("--background", "");
}
changeThemeColor();
</script>{% endif %}
<style>
:root {
{% if request.user.bg_url %}--background: url({{ request.user.bg_url }});{% elif not theme %}--background: url({% static 'img/Background.png' %}){% endif %}
{% if request.user.bg_url %}--background: url({{ request.user.bg_url }});{% endif %}
}
</style>
</head>
@@ -102,12 +104,12 @@
<div id="wrapper"{% if not request.user.is_authenticated %} class="guest"{% endif %}>
<div id="sub-body">
<menu id="global-menu">
<li id="global-menu-logo"><h1><a href="/"><img src="{% static "img/menu-logo.svg" %}" alt="Cedar"></a></h1></li>
<li id="global-menu-logo"><h1><a href="/"><img src="{% static "img/menu-logo.svg" %}" alt="{{ brand_name }}"></a></h1></li>
{% if request.user.unique_id %}
{% invite_only request as inv %}
<li id="global-menu-list">
<ul>
<li id="global-menu-mymenu"><a href="{% url "main:user-view" request.user.username %}"><span class="icon-container {% user_class request.user %}"><img src="{% avatar request.user %}" alt="My Profile"></span><span>My Profile</span></a></li>
<li id="global-menu-mymenu"><a href="{% url "main:user-view" request.user.username %}"><span class="icon-container {% user_class request.user %}"><img src="{% avatar request.user %}" alt="User Page"></span><span>User Page</span></a></li>
<li id="global-menu-feed"><a href="{% url "main:activity" %}" class="symbol"><span>Activity Feed</span></a></li>
<li id="global-menu-community"><a href="/" class="symbol"><span>Communities</span></a></li>
<li id="global-menu-message"><a href="{% url "main:messages" %}" class="symbol"><span>Messages</span></a></li>
@@ -117,9 +119,10 @@
<li><a href="{% url "main:profile-settings" %}" class="symbol my-menu-profile-setting"><span>Profile settings</span></a></li>
<li><a href="#" class="symbol my-menu-account-setting"><span>Account preferences</span></a></li>
{% if inv %}<li><a href="{% url "main:invites" %}" class="symbol my-menu-guide"><span>Invites</span></a></li>{% endif %}
<li><a href="{% url "main:special-community-tag" "announcements" %}" class="symbol my-menu-openman"><span>Cedar Announcements</span></a></li>
<li><a href="{% url "main:special-community-tag" "announcements" %}" class="symbol my-menu-openman"><span>{{ brand_name }} Announcements</span></a></li>
<li><a href="{% url "main:special-community-tag" "changelog" %}" class="symbol my-menu-openman"><span>{{ brand_name }} Changelog</span></a></li>
<li><a href="{% url "main:help-faq" %}" class="symbol my-menu-guide"><span>Frequently Asked Questions (FAQ)</span></a></li>
<li><a href="{% url "main:help-rules" %}" class="symbol my-menu-guide"><span>Cedar Rules</span></a></li>
<li><a href="{% url "main:help-rules" %}" class="symbol my-menu-guide"><span>{{ brand_name }} Rules</span></a></li>
<li><a href="#" class="symbol my-menu-white-power"><span>Feedback/bug report</span></a></li>
<li><a href="{% url "main:server-stat" %}" class="symbol my-menu-openman"><span>Server Statistics</span></a></li>
<li>
@@ -5,7 +5,7 @@
<form method="post">
<img src="{% static "img/menu-logo.svg" %}">
<h1 class="lh">Sign In</h1>
<h2 class="lh">Please sign in to access Cedar.</h2>
<h2 class="lh">Please sign in to access {{ brand_name }}.</h2>
<div class="login-box">
<h3 class="label"><input type="text" class="auth-input" name="username" maxlength="32" placeholder="Username"></h3>
<h3 class="label"><input type="password" class="auth-input" name="password" maxlength="32" placeholder="Password"></h3>
@@ -10,7 +10,6 @@
</h1>
</header>
{% if post.is_mine or post.can_rm %}
{% if user.is_active %}
<div class="edit-buttons-content">
<button type="button" class="symbol button edit-button rm-post-button" data-action="{% url "main:post-rm" post.id %}"><span class="symbol-label">Delete</span></button>
{% if post.is_mine and not post.has_edit %}
@@ -19,8 +18,10 @@
{% if post.is_mine and post.screenshot %}
<button type="button" class="symbol button edit-button profile-post-button{% if post.is_favorite %} done {% endif %}" data-action="{% if post.is_favorite %}{% url "main:post-unset-profile" post.id %}{% else %}{% url "main:post-set-profile" post.id %}{% endif %}"><span class="symbol-label">Set as profile post</span></button>
{% endif %}
{% if post.can_lock_comments %}
<button type="button" class="symbol button edit-button lock-comments-button" data-action="{% url "main:lock-the-comments" post.id %}"><span class="symbol-label">Lock comments</span></button>
{% endif %}
</div>
{% endif %}
{% endif %}
<div class="user-content">
@@ -83,12 +84,14 @@
{% elif post.screenshot %}<div class="screenshot-container still-image"><img src="{{ post.screenshot }}"></div>{% endif %}
{% if post.yt_vid %}
<div class="screenshot-container video"><iframe class="youtube-player" type="text/html" width="490" height="276" src="https://www.youtube.com/embed/{{ post.yt_vid }}?rel=0&modestbranding=1&iv_load_policy=3" frameborder="0"></iframe></div>
{% elif post.discord_vid %}
<div class="DiscordCDN-container video">
<video class="discord-player" src="{{ post.url }}" style='max-width:100%;' controls></video>
</div>
<a>Discord embedded video</a>
<div class="screenshot-container video"><iframe class="youtube-player" type="text/html" style="max-width:100%;max-height: 450px;" src="https://www.youtube.com/embed/{{ post.yt_vid }}?rel=0&modestbranding=1&iv_load_policy=3" frameborder="0"></iframe></div>
{% elif post.video %}
<div class="screenshot-container video"><video class="vidya" controls src="{{ post.video }}" style="max-width:100%;max-height: 450px;"></video></div>
{% elif post.discord_vid %}
<div class="DiscordCDN-container video">
<video class="discord-player" src="{{ post.url }}" style="max-width:100%;max-height: 450px;" controls></video>
</div>
<a>Discord embedded video</a>
{% elif post.url %}
<p class="url-link"><a class="link-confirm" href="{{ post.url }}" target="_blank">{{ post.url }}</a></p>
{% endif %}
@@ -126,13 +129,19 @@
<h2 class="reply-label">Add a Comment</h2>
{% if not request.user.is_authenticated %}
<div class="guest-message">
<p>You must sign in to post a comment.<br><br>Sign in using a Cedar account to make posts and comments, as well as give Yeahs and follow users.</p>
<p>You must sign in to post a comment.<br><br>Sign in using a {{ brand_name }} account to make posts and comments, as well as give Yeahs and follow users.</p>
<a href="{% url "main:signup" %}" class="arrow-button"><span>Create an account</span></a>
<a href="{% url "main:help-faq" %}" class="arrow-button"><span>FAQ/Frequently Asked Questions</span></a>
</div>
{% elif not post.can_comment %}
<div class="cannot-reply"><div>
{% if post.lock_comments == 1 %}
<p>{{ post.creator.nickname }} has locked the comments.</p>
{% elif post.lock_comments == 2 %}
<p>The comments have been locked up tight.</p>
{% else %}
<p>You cannot comment on this post.</p>
{% endif %}
</div></div>
{% else %}
{% comment_form post request.user %}
@@ -22,7 +22,8 @@
<li class="setting-profile-comment">
<p class="settings-label">Profile comment</p>
<textarea class="textarea" name="profile_comment" maxlength="2200" placeholder="Write about yourself here.">{{ profile.comment }}</textarea>
<p class="note">Anything you write here will appear on your profile. Remember to keep it brief. Please don't write anything that'll violate <a href="{% url "main:help-rules" %}">Cedar's rules</a>.</p>
<p class="note">Anything you write here will appear on your profile. Remember to keep it brief. Please don't write anything that'll violate <a href="{% url "main:help-rules" %}">{{ brand_name }}'s rules</a>.</p>
</li>
<!--
<li>
@@ -128,12 +129,12 @@
<div class="select-content">
<div class="select-button">
<select name="pronoun_dot_is" id="pronoun_dot_is">
<option value="0"{% if profile.pronoun_is == 0 %} selected{% endif %}>I don't know</option>
<option value="1"{% if profile.pronoun_is == 1 %} selected{% endif %}>He/him</option>
<option value="2"{% if profile.pronoun_is == 2 %} selected{% endif %}>She/her</option>
<option value="3"{% if profile.pronoun_is == 3 %} selected{% endif %}>He/she</option>
<option value="4"{% if profile.pronoun_is == 4 %} selected{% endif %}>It</option>
<option value="5"{% if profile.pronoun_is == 5 %} selected{% endif %}>They/Them</option>
<option value="0"{% if profile.pronoun_is == 0 %} selected{% endif %}>I don't know</option>
<option value="1"{% if profile.pronoun_is == 1 %} selected{% endif %}>He/him</option>
<option value="2"{% if profile.pronoun_is == 2 %} selected{% endif %}>She/her</option>
<option value="3"{% if profile.pronoun_is == 3 %} selected{% endif %}>He/she</option>
<option value="4"{% if profile.pronoun_is == 4 %} selected{% endif %}>It</option>
<option value="5"{% if profile.pronoun_is == 5 %} selected{% endif %}>They/Them</option>
</select>
</div>
</div>
@@ -5,11 +5,11 @@
<form method="post" onload="">
<img src="{% static "img/menu-logo.svg" %}">
<p class="lh">Sign Up</p>
<p>Create a Cedar account on this instance to access this instance.</p><br>
<p>Create a {{ brand_name }} account to make posts and comments to various communities, give Yeahs to other users' content, and interact with other members of the {{ brand_name }} community.</p><br>
<p>Please follow <a href="{% url "main:help-rules" %}">our rules</a>. If you don't, be careful with your behavior.<br><br>You <strong>must</strong> be {{ age }} years of age or older to join, no exceptions.<br>If you are suspected to be younger than {{ age }} years old, we will ban you until you're {{ age }}.</p>
{% if invite_only %}<h3 class="label"><label><span class="red">*</span> Invite code: <input type="text" class="auth-input" name="invite_code" maxlength="64" minlength="4" placeholder="Invite code"></label></h3>{% endif %}
<h3 class="label"><label><span class="red">*$</span> User ID: <input type="text" class="auth-input" name="username" maxlength="32" minlength="4" placeholder="ID"></label></h3>
<h3 class="label"><label>Nickname: <input type="text" class="auth-input" name="nickname" maxlength="32" placeholder="Nick/Mii name?"></label></h3>
<h3 class="label"><label><span class="red">*$</span> Username: <input type="text" class="auth-input" name="username" maxlength="32" minlength="4" placeholder="Username"></label></h3>
<h3 class="label"><label><span class="red">*</span>Nickname: <input type="text" class="auth-input" name="nickname" maxlength="32" placeholder="Nick/Mii name?"></label></h3>
<h3 class="label nnid"><label>Nintendo Network ID: <input type="text" class="auth-input" name="origin_id" maxlength="16" minlength="6" placeholder="NNID" data-mii-domain="{{ mii_domain }}" data-action="{{ mii_endpoint }}">
<img class="none">
</label></h3>
@@ -1,4 +1,4 @@
{% extends "closedverse_main/layout.html" %}
{% load closedverse_tags %}{% block main-body %}
{% nocontent "Sign Ups are disabled for now. Please contact the administrator using the contact page." %}
{% endblock %}
{% nocontent "Sign ups are disabled for now. Please contact the administrator using the contact page." %}
{% endblock %}
@@ -6,6 +6,7 @@ def community_sidebar(community, request):
return {
'community': community,
'can_edit': community.can_edit_community(request),
'Community_block': community.Community_block(request),
'request': request,
}
@register.inclusion_tag('closedverse_main/elements/community_post.html')
@@ -64,9 +65,10 @@ def file_button():
}
@register.inclusion_tag('closedverse_main/elements/community_page_elem.html')
def community_page_element(communities, text='General Communities', feature=False):
def community_page_element(communities, text='General Communities', feature=False, url_name=''):
return {
'communities': communities,
'title': text,
'feature': feature,
'url_name': url_name,
}
@@ -97,8 +97,8 @@ def print_names(names):
'nameallmn': len(names) - 4,
'names': names,
}
@register.inclusion_tag('closedverse_main/elements/discordapp-spinner.html')
def discordapp_spinner():
@register.inclusion_tag('closedverse_main/elements/loading-spinner.html')
def loading_spinner():
return {
}
@@ -69,9 +69,10 @@ def u_post_list(posts, next_offset=None, type=2, nf_text='', time=None):
'type': type,
}
@register.inclusion_tag('closedverse_main/elements/profile-post.html')
def profile_post(post):
def profile_post(post, for_announcements=False):
return {
'post': post,
'for_announcements': for_announcements,
}
@register.inclusion_tag('closedverse_main/elements/profile-user-list.html')
def profile_user_list(users, next_offset=None, request=None):
+3 -1
View File
@@ -61,7 +61,7 @@ urlpatterns = [
url(r'posts/'+ post +'/yeah$', views.post_add_yeah, name='post-add-yeah'),
url(r'posts/'+ post +'/yeahu$', views.post_delete_yeah, name='post-delete-yeah'),
url(r'posts/'+ post +'/comments$', views.post_comments, name='post-comments'),
url(r'posts/'+ post +'/comments$', views.post_comments, name='post-comments'),
url(r'posts/'+ post +'/lock-comments$', views.lock_the_comments, name='lock-the-comments'),
url(r'posts/'+ post +'/change$', views.post_change, name='post-change'),
url(r'posts/'+ post +'/profile$', views.post_setprofile, name='post-set-profile'),
url(r'posts/'+ post +'/profile_rm', views.post_unsetprofile, name='post-unset-profile'),
@@ -115,4 +115,6 @@ urlpatterns = [
#url(r'openverse.png', views.openverse_logo, name='openverse-logo'),
]
# + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# serve static and media i think???? mighTTT???????
urlpatterns += [re_path(r'^i/(?P<path>.*)$', serve, {'document_root': MEDIA_ROOT, }), ]
+61 -18
View File
@@ -1,6 +1,6 @@
# Todo: move all requests to using requests instead of urllib3
import urllib.request, urllib.error
import requests
# requests is only used for get_mii which is not being used currently
#import requests
from lxml import etree
from random import choice
import json
@@ -38,35 +38,41 @@ def HumanTime(date, full=False):
unit_name += 's'
return f'{number_of_units} {unit_name} ago'
# the current source as of now uses AJAX to get mii data
def get_mii(id):
# Using AccountWS
dmca = {
'X-Nintendo-Client-ID': 'a2efa818a34fa16b8afbc8a74eba3eda',
'X-Nintendo-Client-Secret': 'c91cdb5658bd4954ade78533a339cf9a',
}
# TODO: Make this, the gravatar request, and reCAPTCHA request escape (or plainly use) URL params
nnid = requests.get('https://accountws.nintendo.net/v1/api/admin/mapped_ids?input_type=user_id&output_type=pid&input=' + id, headers=dmca)
nnid_dec = etree.fromstring(nnid.content)
del(nnid)
# Perform the first request to get pid
url_pid = 'https://accountws.nintendo.net/v1/api/admin/mapped_ids?input_type=user_id&output_type=pid&input=' + id
request_pid = urllib.request.Request(url_pid, headers=dmca)
with urllib.request.urlopen(request_pid) as nnid_response:
nnid_content = nnid_response.read()
nnid_dec = etree.fromstring(nnid_content)
pid = nnid_dec[0][1].text
if not pid:
return False
del(nnid_dec)
mii = requests.get('https://accountws.nintendo.net/v1/api/miis?pids=' + pid, headers=dmca)
# Perform the second request to get mii information
url_mii = 'https://accountws.nintendo.net/v1/api/miis?pids=' + pid
request_mii = urllib.request.Request(url_mii, headers=dmca)
with urllib.request.urlopen(request_mii) as mii_response:
mii_content = mii_response.read()
try:
mii_dec = etree.fromstring(mii.content)
# Can't be fucked to put individual exceptions to catch here
mii_dec = etree.fromstring(mii_content)
except:
return False
del(mii)
try:
miihash = mii_dec[0][2][0][0].text.split('.net/')[1].split('_')[0]
except IndexError:
miihash = None
screenname = mii_dec[0][3].text
nnid = mii_dec[0][6].text
del(mii_dec)
# Also todo: Return the NNID based on what accountws returns, not the user's input!!!
return [miihash, screenname, nnid]
def recaptcha_verify(request, key):
@@ -78,6 +84,26 @@ def recaptcha_verify(request, key):
return False
return True
def video_upload(video):
hash = sha1()
for chunk in video.chunks():
hash.update(chunk)
imhash = hash.hexdigest()
# only either webm or mp4
extension = video.name[-4:]
if extension == '.mp4':
fname = imhash + '.mp4'
elif extension == 'webm':
fname = imhash + '.webm'
else:
return 1
# simply only write image if the hashed path doesn't already exist
if not os.path.exists(settings.MEDIA_ROOT + fname):
with open(settings.MEDIA_ROOT + fname, "wb+") as destination:
for chunk in video.chunks():
destination.write(chunk)
return settings.MEDIA_URL + fname
ImageFile.LOAD_TRUNCATED_IMAGES = True
def image_upload(img, stream=False, drawing=False, avatar=False):
if stream:
@@ -212,11 +238,28 @@ def getipintel(addr):
else:
return 0
"""
# not ideal, switch this to use a real cache when caching for everything else is implemented (never?)
iphub_cache = dict()
# Now using iphub
def iphub(addr):
def iphub(addr, want_asn=False):
# hack to exclude my private network at the time (security flaw?)
if settings.IPHUB_KEY and not '192.168' in addr:
get = requests.get('http://v2.api.iphub.info/ip/' + addr, headers={'X-Key': settings.IPHUB_KEY})
if get.json()['block'] == 1:
return True
if addr in iphub_cache:
get_r = iphub_cache[addr]
#print('getting ip ' + addr + ' from cache 😌')
else:
return False
#print('GETTING IP ' + addr + ' FROM IPHUB😤😤😤😤😤😤')
req = urllib.request.Request('http://v2.api.iphub.info/ip/' + addr, headers={'X-Key': settings.IPHUB_KEY})
response = urllib.request.urlopen(req)
data = response.read().decode()
get_r = json.loads(data)
iphub_cache[addr] = get_r
if want_asn:
return get_r.get('asn', '0')
if get_r.get('block', 0) == 1:
return True
# should just return falsey when returning nothing anyway?
#else:
# return False
+114 -112
View File
@@ -3,16 +3,15 @@ from django.template import loader
from django.shortcuts import render, redirect, get_object_or_404
from django.http import Http404
from django.views.decorators.csrf import csrf_exempt
from django.db.models import Q, QuerySet, Max, F, Count, Case, When, Exists, OuterRef
from django.contrib.auth import login, logout
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.core.validators import EmailValidator
from django.core.exceptions import ValidationError
from django.db.models import Q, Count, Exists, OuterRef
from django.db.models.functions import Now
from .models import *
from .util import *
from .serializers import CommunitySerializer
from closedverse import settings
import re
from django.urls import reverse
@@ -22,14 +21,19 @@ from json import dumps, loads
import sys, traceback
import base64
import subprocess
from datetime import datetime
from datetime import datetime, timedelta
from django.utils import timezone
import django.utils.dateformat
from binascii import hexlify
from os import urandom
#from silk.profiling.profiler import silk_profile
# client-side mii fetch GET endpoint (like pf2m.com/hash if it supported cors)
mii_endpoint = 'https://nnidlt.murilo.eu.org/api.php?output=hash_only&env=production&user_id='
#mii_endpoint = '/origin?a='
if hasattr(settings, 'mii_endpoint'):
mii_endpoint = settings.mii_endpoint
def json_response(msg='', code=0, httperr=400):
thing = {
@@ -46,9 +50,10 @@ def json_response(msg='', code=0, httperr=400):
'code': httperr,
}
return JsonResponse(thing, safe=False, status=httperr)
def community_list(request):
"""Lists communities / main page."""
popularity = Community.popularity
#popularity = Community.popularity
obj = Community.objects
if request.user.is_authenticated:
classes = ['guest-top']
@@ -64,7 +69,8 @@ def community_list(request):
ad = "no ads"
WelcomeMSG = welcomemsg.objects.filter(show=True).order_by('-order', '-id')
announcements = Post.objects.filter(community__tags='announcements').order_by('-id')[:6]
# announcements within the past week-ish
announcements = Post.objects.filter(community__tags='announcements', created__gte=Now()-timedelta(days=5)).order_by('-created')[:6]
if request.user.is_authenticated:
my_communities = obj.filter(creator=request.user).order_by('-created')[0:12]
else:
@@ -79,7 +85,7 @@ def community_list(request):
'general': obj.filter(type=0).order_by('-created')[0:12],
'game': obj.filter(type=1).order_by('-created')[0:12],
'special': obj.filter(type=2).order_by('-created')[0:12],
'user_communities': sorted(obj.filter(type=3), key=lambda x: x.popularity(), reverse=True)[0:12],
'user_communities': sorted(obj.filter(type=3), key=lambda x: x.popularity(), reverse=True)[0:12],
'my_communities': my_communities,
'feature': obj.filter(is_feature=True).order_by('-created'),
'favorites': favorites,
@@ -206,7 +212,7 @@ def login_page(request):
else:
# Todo: I might want to do some things relating to making models take care of object stuff like this instead of the view, it's totally messed up now.
successful = False if user[1] is False or not user[0].is_active() else True
LoginAttempt.objects.create(user=user[0], success=successful, user_agent=request.META.get('HTTP_USER_AGENT'), addr=request.META.get('HTTP_CF_CONNECTING_IP'))
LoginAttempt.objects.create(user=user[0], success=successful, user_agent=request.META.get('HTTP_USER_AGENT'), addr=request.META.get('REMOTE_ADDR'))
if user[1] == False:
return HttpResponse("Invalid password.", status=401)
elif user[1] == 2:
@@ -219,7 +225,7 @@ def login_page(request):
# Then, let's get the referrer and either return that or the root.
# Actually, let's not for now.
#if request.META['HTTP_REFERER'] and "login" not in request.META['HTTP_REFERER'] and request.META['HTTP_HOST'] in request.META['HTTP_REFERER']:
# location = request.META['HTTP_REFERER']
# location = request.META['HTTP_REFERER']
#else:
location = '/'
if request.GET.get('next'):
@@ -243,12 +249,12 @@ def signup_page(request):
if request.user.is_authenticated:
return redirect('/')
if request.method == 'POST':
if settings.recaptcha_pub:
if not recaptcha_verify(request, settings.recaptcha_priv):
if settings.RECAPTCHA_PUBLIC_KEY:
if not recaptcha_verify(request, settings.RECAPTCHA_PRIVATE_KEY):
return HttpResponse("The reCAPTCHA validation has failed.", status=402)
if not (request.POST.get('username') and request.POST.get('password') and request.POST.get('password_again')):
return HttpResponseBadRequest("You didn't fill in all of the required fields.")
invited = False
invite = None
if settings.invite_only:
@@ -262,16 +268,43 @@ def signup_page(request):
if not invite.is_valid():
return HttpResponseBadRequest("The provided invite code has been used or is void. Please ask for another code.")
invited = True
if not re.compile(r'^[A-Za-z0-9-._]{1,32}$').match(request.POST['username']) or not re.compile(r'[A-Za-z0-9]').match(request.POST['username']):
return HttpResponseBadRequest("Your username either contains invalid characters or is too long (only letters + numbers, dashes, dots and underscores are allowed")
for keyword in ['admin', 'admln', 'adrnin', 'admn', ]:
if keyword in request.POST['username'].lower():
return HttpResponseForbidden("You aren't funny. Please use a funny name.")
for keyword in ['nigger', 'nigga', 'faggot', 'kile', ]:
if keyword in request.POST['username'].lower():
return HttpResponseForbidden("Nigga test failed, invalid pass.")
# forbidden keywords
groups = [
[
'admin', 'admln', 'adrnin', 'admn', 'closedverse',
'arian', 'kordi', 'windowscj', 'gab', 'term',
'penis', 'nazi', 'hitler', 'hitlre', 'ihtler', 'heil', 'kkk'
'nigg', 'niger', 'fag',
'smf9', 'dakux', 'dacucks', 'adrian'
],
['adam', 'nintendotom'],
['funny'],
['doeggs', 'do_eggs', 'do-eggs', 'do.eggs'],
]
messages = [
"That username isn't funny. Please pick a funny username.",
"Adam, no.",
"I'm laughing so hard right now!! No seriously. Pick a better username.",
"the world may never know",
]
for id in range(len(groups)):
for keyword in groups[id]:
if keyword in request.POST['username'].lower() or keyword in request.POST['nickname'].lower():
# perhaps warn admins at a later time
return HttpResponseForbidden(messages[id])
# this is such a miniscule amount of NNIDs that either an admin or a person could
# claim all of them, in which case they could not be reused
#for keyword in ['windowscj', 'gabalt']:
# if keyword in request.POST['origin_id'].lower():
# return HttpResponseForbidden("You're very funny. Unfortunately your funniness blah blah blah fuck off.")
# end of forbidden keywords
conflicting_user = User.objects.filter(Q(username__iexact=request.POST['username']) | Q(username__iexact=request.POST['username'].replace(' ', '')))
if conflicting_user:
return HttpResponseBadRequest("A user with that username already exists.")
@@ -280,11 +313,11 @@ def signup_page(request):
# do the length check
if len(request.POST['password']) < settings.minimum_password_length:
return HttpResponseBadRequest('The password must be at least ' + str(settings.minimum_password_length) + ' characters long.')
if not (request.POST['nickname'] or request.POST['origin_id']):
return HttpResponseBadRequest("You didn't fill in an NNID, so you need a nickname.")
if not request.POST['nickname']:
return HttpResponseBadRequest("You need a nickname. What else are we gonna call you????? Ghosty?")
if request.POST['nickname'] and len(request.POST['nickname']) > 32:
return HttpResponseBadRequest("Your nickname is either too long or too short (1-32 characters)")
if request.POST['origin_id'] and (len(request.POST['origin_id']) > 16 or len(request.POST['origin_id']) < 6):
if request.POST.get('origin_id') and (len(request.POST['origin_id']) > 16 or len(request.POST['origin_id']) < 6):
return HttpResponseBadRequest("The NNID provided is either too short or too long.")
if request.POST.get('email'):
if User.email_in_use(request.POST['email']):
@@ -293,46 +326,50 @@ def signup_page(request):
EmailValidator()(value=request.POST['email'])
except ValidationError:
return HttpResponseBadRequest("Your e-mail address is invalid. Input an e-mail address, or input nothing.")
check_others = Profile.objects.filter(user__addr=request.META['HTTP_CF_CONNECTING_IP'], let_freedom=False).exists()
check_others = Profile.objects.filter(user__addr=request.META['REMOTE_ADDR'], let_freedom=False).exists()
if check_others:
return HttpResponseBadRequest("Unfortunately, you cannot make any accounts at this time. This restriction was set for a reason, please contact the administration. Please don't bypass this, as if you do, you are just being ignorant. If you have not made any accounts, contact the administration and this restriction will be removed for you.")
check_othersban = User.objects.filter(addr=request.META['HTTP_CF_CONNECTING_IP'], active=False).exists()
check_othersban = User.objects.filter(addr=request.META['REMOTE_ADDR'], active=False).exists()
if check_othersban:
return HttpResponseBadRequest("You cannot sign up while banned.")
check_signupban = User.objects.filter(signup_addr=request.META['HTTP_CF_CONNECTING_IP'], active=False).exists()
check_signupban = User.objects.filter(signup_addr=request.META['REMOTE_ADDR'], active=False).exists()
if check_signupban:
return HttpResponseBadRequest("Get on your hands and knees")
if iphub(request.META['HTTP_CF_CONNECTING_IP']):
if settings.disallow_proxy:
return HttpResponseBadRequest("You cannot sign up with a proxy.")
if request.POST['origin_id']:
if iphub(request.META['REMOTE_ADDR']):
if settings.DISALLOW_PROXY:
return HttpResponseBadRequest("please do not use a vpn ok thanks")
if request.POST.get('origin_id'):
if not request.POST.get('mh'):
return HttpResponseBadRequest("sorry didn't get the mii image attribute. you might need to wait or just refresh, sorry")
if User.nnid_in_use(request.POST['origin_id']):
return HttpResponseBadRequest("That Nintendo Network ID is already in use, that would cause confusion.")
mii = get_mii(request.POST['origin_id'])
if not mii:
return HttpResponseBadRequest("The NNID provided doesn't exist.")
nick = mii[1]
#mii = get_mii(request.POST['origin_id'])
#if not mii:
# return HttpResponseBadRequest("The NNID provided doesn't exist.")
#nick = mii[1]
nick = request.POST['nickname']
mii = [request.POST.get('mh'), 'if you see this then something is wrong', request.POST['origin_id']]
gravatar = False
else:
nick = request.POST['nickname']
mii = None
gravatar = True
make = User.objects.closed_create_user(username=request.POST['username'], password=request.POST['password'], email=request.POST.get('email'), addr=request.META['HTTP_CF_CONNECTING_IP'], user_agent=request.META['HTTP_USER_AGENT'], signup_addr=request.META['HTTP_CF_CONNECTING_IP'], nick=nick, nn=mii, gravatar=gravatar)
make = User.objects.closed_create_user(username=request.POST['username'], password=request.POST['password'], email=request.POST.get('email'), addr=request.META['REMOTE_ADDR'], user_agent=request.META['HTTP_USER_AGENT'], signup_addr=request.META['REMOTE_ADDR'], nick=nick, nn=mii, gravatar=gravatar)
if invited == True:
invite.used = True
invite.used_by = make
invite.save()
LoginAttempt.objects.create(user=make, success=True, user_agent=request.META.get('HTTP_USER_AGENT'), addr=request.META.get('HTTP_CF_CONNECTING_IP'))
LoginAttempt.objects.create(user=make, success=True, user_agent=request.META.get('HTTP_USER_AGENT'), addr=request.META.get('REMOTE_ADDR'))
login(request, make)
request.session['passwd'] = make.password
return HttpResponse("/")
else:
if not settings.recaptcha_pub:
settings.recaptcha_pub = None
if not settings.RECAPTCHA_PUBLIC_KEY:
settings.RECAPTCHA_PUBLIC_KEY = None
return render(request, 'closedverse_main/signup_page.html', {
'title': 'Sign up',
'recaptcha': settings.recaptcha_pub,
'recaptcha': settings.RECAPTCHA_PUBLIC_KEY,
'invite_only': settings.invite_only,
'age': settings.age_allowed,
'mii_domain': mii_domain,
@@ -374,10 +411,15 @@ def forgot_passwd(request):
def logout_page(request):
"""Password email page / post endpoint."""
if not request.user.is_active():
r = HttpResponseForbidden("You can't log out while you're inactive. According to me and God, you'll just have to sit here and suffer for now. Go contemplate your actions. You will be redirected to Wario Land 4 momentarily.", content_type='text/plain')
r['Refresh'] = '7; url=https://gba.js.org/player#warioland4'
return r
logout(request)
if request.GET.get('next'):
return redirect(request.GET['next'])
return redirect('/')
def user_view(request, username):
"""The user view page, has recent posts/yeahs."""
user = get_object_or_404(User, username__iexact=username)
@@ -423,11 +465,15 @@ def user_view(request, username):
if len(request.POST.get('bg_url')) > 300:
return json_response('Background URL is too long (length '+str(len(request.POST.get('bg_url')))+', max 300)')
if len(request.POST.get('whatareyou')) > 300:
return json_response('Big brother it\'s too big, I can\'t take it! (length '+str(len(request.POST.get('whatareyou')))+', max 300)')
return json_response('"What Are You" is too long (length '+str(len(request.POST.get('whatareyou')))+', max 300)')
if len(request.POST.get('external')) > 255:
return json_response('Big brother it\'s too big, I can\'t take it! (length '+str(len(request.POST.get('external')))+', max 300)')
return json_response('Discord Tag is too long (length '+str(len(request.POST.get('external')))+', max 300)')
if len(request.POST.get('email')) > 500:
return json_response('Big brother it\'s too big, I can\'t take it! (length '+str(len(request.POST.get('email')))+', max 500)')
return json_response('Email is too long (length '+str(len(request.POST.get('email')))+', max 500)')
# Kinda unneeded but gdsjkgdfsg
if request.POST.get('website') == 'Web URL' or request.POST.get('country') == 'Region' or request.POST.get('external') == 'Discord Tag':
return json_response("I'm laughing right now.")
if len(request.POST.get('avatar')) > 255:
return json_response('Avatar is too long (length '+str(len(request.POST.get('avatar')))+', max 255)')
if request.POST.get('email') and not request.POST.get('email') == 'None':
@@ -615,11 +661,11 @@ def user_posts(request, username):
next_offset = offset + 50
if request.META.get('HTTP_X_AUTOPAGERIZE'):
return (debug(request, username) if 'HTTP_DIS' in request.META else render(request, 'closedverse_main/elements/u-post-list.html', {
return render(request, 'closedverse_main/elements/u-post-list.html', {
'posts': posts,
'next': next_offset,
'time': offset_time.isoformat(),
}))
})
else:
return render(request, 'closedverse_main/user_posts.html', {
'user': user,
@@ -1079,7 +1125,7 @@ def community_create_action(request):
def post_create(request, community):
if request.method == 'POST':
# Wake
request.user.wake(request.META['HTTP_CF_CONNECTING_IP'])
request.user.wake(request.META['REMOTE_ADDR'])
# Required
if not (request.POST.get('community')):
return HttpResponseBadRequest()
@@ -1106,7 +1152,8 @@ def post_create(request, community):
7: "Please don't spam.",
9: "You're very funny. Unfortunately your funniness blah blah blah fuck off.",
10: "No mr white, you can't make a post entirely consistant of spaces",
11: "Please don't post Zalgo text.",
11: "The video you've uploaded is invalid.",
12: "Please don't post Zalgo text.",
}.get(new_post))
# Render correctly whether we're posting to Activity Feed
if community.is_activity():
@@ -1134,6 +1181,7 @@ def post_view(request, post):
post.can_rm = post.can_rm(request)
post.is_favorite = post.is_favorite(request.user)
post.can_comment = post.can_comment(request)
post.can_lock_comments = post.can_lock_comments(request)
if post.is_mine:
title = 'Your post'
else:
@@ -1183,6 +1231,12 @@ def post_change(request, post):
return HttpResponse()
@require_http_methods(['POST'])
@login_required
def lock_the_comments(request, post):
the_post = get_object_or_404(Post, id=post)
the_post.lock_the_comments_up(request)
return HttpResponse()
@require_http_methods(['POST'])
@login_required
def post_setprofile(request, post):
the_post = get_object_or_404(Post, id=post)
the_post.favorite(request.user)
@@ -1215,11 +1269,9 @@ def comment_rm(request, comment):
@login_required
def post_comments(request, post):
post = get_object_or_404(Post, id=post)
if not request.is_ajax():
raise Http404()
if request.method == 'POST':
# Wake
request.user.wake(request.META['HTTP_CF_CONNECTING_IP'])
request.user.wake(request.META['REMOTE_ADDR'])
# Method of Post
new_post = post.create_comment(request)
if not new_post:
@@ -1234,7 +1286,7 @@ def post_comments(request, post):
2: "The image you've uploaded is invalid.",
3: "You're making comments too fast, wait a few seconds and try again.",
6: "Not allowed.",
11: "Please don't post Zalgo text.",
12: "Please don't post Zalgo text.",
}.get(new_post))
# Give the notification!
if post.is_mine(request.user):
@@ -1327,17 +1379,15 @@ def user_follow(request, username):
if user.follow(request.user):
# Give the notification!
Notification.give_notification(request.user, 4, user)
followct = request.user.num_following()
followct = user.num_followers()
return JsonResponse({'following_count': followct})
@require_http_methods(['POST'])
@login_required
def user_unfollow(request, username):
user = get_object_or_404(User, username=username)
user.unfollow(request.user)
return HttpResponse()
followct = request.user.num_following()
followct = user.num_followers()
return JsonResponse({'following_count': followct})
@require_http_methods(['POST'])
@login_required
def user_friendrequest_create(request, username):
@@ -1383,7 +1433,7 @@ def user_addblock(request, username):
user = get_object_or_404(User, username=username)
user.make_block(request.user)
return HttpResponse()
# Notifications work differently since the Openverse rebranding. (that we changed back)
# They used to respond with a JSON for values for unread notifications and messages.
# NOW we send the unread notifications in bytes, and then the unread messages in bytes, 2 bytes. The JS is using charCodeAt()
@@ -1397,7 +1447,7 @@ def check_notifications(request):
all_count = request.user.get_frs_notif() + n_count
msg_count = request.user.msg_count()
# Let's update the user's online status
request.user.wake(request.META['HTTP_CF_CONNECTING_IP'])
request.user.wake(request.META['REMOTE_ADDR'])
# Let's just now return the JSON only for Accept: HTML
if 'html' in request.META.get('HTTP_ACCEPT'):
return JsonResponse({'success': True, 'n': all_count, 'msg': msg_count})
@@ -1414,14 +1464,6 @@ def check_notifications(request):
return HttpResponse(binary_notifications, content_type='application/octet-stream')
@require_http_methods(['POST'])
@login_required
def notification_setread(request):
if request.GET.get('fr'):
update = request.user.read_fr()
else:
update = request.user.notification_read()
return HttpResponse()
@require_http_methods(['POST'])
@login_required
def notification_delete(request, notification):
if not request.method == 'POST':
raise Http404()
@@ -1559,7 +1601,7 @@ def messages_view(request, username):
conversation = friendship.conversation()
if request.method == 'POST':
# Wake
request.user.wake(request.META['HTTP_CF_CONNECTING_IP'])
request.user.wake(request.META['REMOTE_ADDR'])
new_post = conversation.make_message(request)
if not new_post:
return HttpResponseBadRequest()
@@ -1636,46 +1678,6 @@ def prefs(request):
lights = not (request.session.get('lights', False))
arr = [profile.let_yeahnotifs, lights, request.user.hide_online]
return JsonResponse(arr, safe=False)
@login_required
def post_list(request):
if not request.user.is_staff():
return JsonResponse({"err": "Not authorized"})
if not request.GET.get('s') or not request.GET.get('e'):
return JsonResponse({"err": "Start time required with 's' query param and end required with 'e' param (epoch)"})
if not request.GET.get('l'):
return JsonResponse({"err": "Limit required via 'l' query param"})
else:
limit = int(request.GET['l'])
if not request.GET.get('o'):
return JsonResponse({"err": "Offset required via 'o' query param"})
else:
offset = int(request.GET['o'])
if limit > 250:
return JsonResponse({"err": "Limit cannot be higher than 250"})
dateone = datetime.fromtimestamp(int(request.GET['s']))
datetwo = datetime.fromtimestamp(int(request.GET['e']))
iable = Post.objects.filter(created__range=(dateone, datetwo)).order_by('-created')[offset:offset + limit]
resparr = []
for post in iable:
resparr.append({
'id': post.id,
'created': django.utils.dateformat.format(post.created, 'U'),
'user': post.creator.username,
'community': post.community_id,
'feeling': post.feeling,
'spoiler': post.spoils,
'content': (post.body or None),
'drawing': (post.drawing or None),
'screenshot': (post.screenshot or None),
'url': (post.url or None),
})
#return HttpResponse(msgpack.packb(resparr), content_type='application/x-msgpack')
return JsonResponse(resparr, safe=False)
def user_tools(request, username):
if not request.user.is_authenticated:
@@ -1840,15 +1842,15 @@ def create_invite(request):
else:
raise Http404()
@require_http_methods(['POST'])
#@require_http_methods(['POST'])
# Disabling login requirement since it's in signup now. Regret?
#@login_required
def origin_id(request):
if not request.headers.get('x-requested-with') == 'XMLHttpRequest':
return HttpResponse("<a href='https://github.com/ariankordi/closedverse/blob/master/closedverse_main/util.py#L44-L86'>Please do not use this as an API!</a>")
if not request.POST.get('a'):
if not request.GET.get('a'):
return HttpResponseBadRequest()
mii = get_mii(request.POST['a'])
mii = get_mii(request.GET['a'])
if not mii:
return HttpResponseBadRequest("The NNID provided doesn't exist.")
return HttpResponse(mii[0])
@@ -1954,17 +1956,17 @@ def whatads(request):
return render(request, 'closedverse_main/help/whatads.html', {'title': 'What are user-generated ads?'})
@login_required
def help_rules(request):
return render(request, 'closedverse_main/help/rules.html', {'title': 'Cedar Three Rules', 'age': settings.age_allowed})
return render(request, 'closedverse_main/help/rules.html', {'title': 'Rules', 'age': settings.age_allowed})
def help_faq(request):
return render(request, 'closedverse_main/help/faq.html', {'title': 'FAQ'})
def help_legal(request):
if not settings.PROD:
if not settings.CLOSEDVERSE_PROD:
return HttpResponseForbidden()
return render(request, 'closedverse_main/help/legal.html', {})
return render(request, 'closedverse_main/help/legal.html', {'title': "Legal Information"})
def help_contact(request):
return render(request, 'closedverse_main/help/contact.html', {'title': "Contact Info"})
return render(request, 'closedverse_main/help/contact.html', {'title': "Contact info"})
def help_why(request):
return render(request, 'closedverse_main/help/why.html', {'title': "Why even join Cedar Three?"})
return render(request, 'closedverse_main/help/why.html', {'title': "Why even join this site?"})
def help_login(request):
return render(request, 'closedverse_main/help/login-help.html', {'title': "Login help"})