Merge remote-tracking branch 'upstream/frost' into birthgay-staging

master
Snakes 2022-05-19 17:59:35 -04:00
commit c789f6923e
166 changed files with 23418 additions and 22990 deletions

8
.gitattributes vendored
View File

@ -1,4 +1,4 @@
*.css linguist-detectable=false
*.js linguist-detectable=true
*.html linguist-detectable=false
*.py linguist-detectable=true
*.css linguist-detectable=false
*.js linguist-detectable=true
*.html linguist-detectable=false
*.py linguist-detectable=true

View File

@ -1,70 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '18 19 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -42,7 +42,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -53,7 +53,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -67,4 +67,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@ -22,6 +22,8 @@ jobs:
# OSSAR runs on windows-latest.
# ubuntu-latest and macos-latest support coming soon
runs-on: windows-latest
permissions:
security-events: write
steps:
- name: Checkout repository
@ -44,6 +46,6 @@ jobs:
# Upload results to the Security tab
- name: Upload OSSAR results
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: ${{ steps.ossar.outputs.sarifFile }}
sarif_file: ${{ steps.ossar.outputs.sarifFile }}

14
.github/workflows/test.yml vendored 100644
View File

@ -0,0 +1,14 @@
name: "run_tests.py"
on: [push, pull_request]
jobs:
analyze:
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: run_tests.py
run: |
./run_tests.py

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
image.*
video.mp4
video.webm
unsanitized.mp4
cache/
__pycache__/
.idea/

View File

@ -1,17 +1,17 @@
FROM ubuntu:20.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt -y upgrade && apt install -y supervisor python3-pip libenchant1c2a ffmpeg
COPY supervisord.conf /etc/supervisord.conf
COPY requirements.txt /etc/requirements.txt
RUN pip3 install -r /etc/requirements.txt
RUN mkdir /images && mkdir /songs
EXPOSE 80/tcp
CMD [ "/usr/bin/supervisord", "-c", "/etc/supervisord.conf" ]
FROM ubuntu:20.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt -y upgrade && apt install -y supervisor python3-pip libenchant1c2a ffmpeg
COPY supervisord.conf /etc/supervisord.conf
COPY requirements.txt /etc/requirements.txt
RUN pip3 install -r /etc/requirements.txt
RUN mkdir /images && mkdir /songs
EXPOSE 80/tcp
CMD [ "/usr/bin/supervisord", "-c", "/etc/supervisord.conf" ]

1322
LICENSE

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "daily"

View File

@ -2,6 +2,7 @@ version: '2.3'
services:
files:
container_name: "rDrama"
build:
context: .
volumes:

View File

@ -1,123 +1,127 @@
import gevent.monkey
gevent.monkey.patch_all()
from os import environ, path
import secrets
from flask import *
from flask_caching import Cache
from flask_limiter import Limiter
from flask_compress import Compress
from flask_mail import Mail
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import *
import gevent
import redis
import time
from sys import stdout, argv
import faulthandler
import json
app = Flask(__name__, template_folder='templates')
app.url_map.strict_slashes = False
app.jinja_env.cache = {}
app.jinja_env.auto_reload = True
faulthandler.enable()
app.config["SITE_NAME"]=environ.get("SITE_NAME").strip()
app.config["GUMROAD_LINK"]=environ.get("GUMROAD_LINK", "https://marsey1.gumroad.com/l/tfcvri").strip()
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['DATABASE_URL'] = environ.get("DATABASE_URL", "postgresql://postgres@localhost:5432")
app.config['SECRET_KEY'] = environ.get('MASTER_KEY')
app.config["SERVER_NAME"] = environ.get("DOMAIN").strip()
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3153600
app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower()
app.config["VERSION"] = "1.0.0"
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
app.config["SESSION_COOKIE_SECURE"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * 365
app.config["DEFAULT_COLOR"] = environ.get("DEFAULT_COLOR", "ff0000").strip()
app.config["DEFAULT_THEME"] = environ.get("DEFAULT_THEME", "midnight").strip()
app.config["FORCE_HTTPS"] = 1
app.config["UserAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
app.config["HCAPTCHA_SITEKEY"] = environ.get("HCAPTCHA_SITEKEY","").strip()
app.config["HCAPTCHA_SECRET"] = environ.get("HCAPTCHA_SECRET","").strip()
app.config["SPAM_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_SIMILARITY_THRESHOLD", 0.5))
app.config["SPAM_URL_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_URL_SIMILARITY_THRESHOLD", 0.1))
app.config["SPAM_SIMILAR_COUNT_THRESHOLD"] = int(environ.get("SPAM_SIMILAR_COUNT_THRESHOLD", 10))
app.config["COMMENT_SPAM_SIMILAR_THRESHOLD"] = float(environ.get("COMMENT_SPAM_SIMILAR_THRESHOLD", 0.5))
app.config["COMMENT_SPAM_COUNT_THRESHOLD"] = int(environ.get("COMMENT_SPAM_COUNT_THRESHOLD", 10))
app.config["CACHE_TYPE"] = "RedisCache"
app.config["CACHE_REDIS_URL"] = environ.get("REDIS_URL", "redis://localhost")
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = environ.get("MAIL_USERNAME", "").strip()
app.config['MAIL_PASSWORD'] = environ.get("MAIL_PASSWORD", "").strip()
app.config['DESCRIPTION'] = environ.get("DESCRIPTION", "rdrama.net caters to drama in all forms such as: Real life, videos, photos, gossip, rumors, news sites, Reddit, and Beyond™. There isn't drama we won't touch, and we want it all!").strip()
app.config['SETTINGS'] = {}
r=redis.Redis(host=environ.get("REDIS_URL", "redis://localhost"), decode_responses=True, ssl_cert_reqs=None)
def get_CF():
with app.app_context():
return request.headers.get('CF-Connecting-IP')
limiter = Limiter(
app,
key_func=get_CF,
default_limits=["3/second;30/minute;200/hour;1000/day"],
application_limits=["10/second;200/minute;5000/hour;10000/day"],
storage_uri=environ.get("REDIS_URL", "redis://localhost")
)
Base = declarative_base()
engine = create_engine(app.config['DATABASE_URL'])
db_session = scoped_session(sessionmaker(bind=engine, autoflush=False))
cache = Cache(app)
Compress(app)
mail = Mail(app)
@app.before_request
def before_request():
with open('site_settings.json', 'r') as f:
app.config['SETTINGS'] = json.load(f)
if request.host != app.config["SERVER_NAME"]: return {"error":"Unauthorized host provided."}, 401
if request.headers.get("CF-Worker"): return {"error":"Cloudflare workers are not allowed to access this website."}, 401
if not app.config['SETTINGS']['Bots'] and request.headers.get("Authorization"): abort(503)
g.db = db_session()
ua = request.headers.get("User-Agent","").lower()
if '; wv) ' in ua: g.webview = True
else: g.webview = False
if 'iphone' in ua or 'ipad' in ua or 'ipod' in ua or 'mac os' in ua or ' firefox/' in ua: g.inferior_browser = True
else: g.inferior_browser = False
g.timestamp = int(time.time())
@app.teardown_appcontext
def teardown_request(error):
if hasattr(g, 'db') and g.db:
g.db.close()
stdout.flush()
@app.after_request
def after_request(response):
response.headers.add("Strict-Transport-Security", "max-age=31536000")
response.headers.add("X-Frame-Options", "deny")
return response
if "load_chat" in argv:
from files.routes.chat import *
else:
import gevent.monkey
gevent.monkey.patch_all()
from os import environ, path
import secrets
from flask import *
from flask_caching import Cache
from flask_limiter import Limiter
from flask_compress import Compress
from flask_mail import Mail
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import *
import gevent
import redis
import time
from sys import stdout, argv
import faulthandler
import json
app = Flask(__name__, template_folder='templates')
app.url_map.strict_slashes = False
app.jinja_env.cache = {}
app.jinja_env.auto_reload = True
faulthandler.enable()
app.config["SITE_NAME"]=environ.get("SITE_NAME").strip()
app.config["GUMROAD_LINK"]=environ.get("GUMROAD_LINK", "https://marsey1.gumroad.com/l/tfcvri").strip()
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['DATABASE_URL'] = environ.get("DATABASE_URL", "postgresql://postgres@localhost:5432")
app.config['SECRET_KEY'] = environ.get('MASTER_KEY')
app.config["SERVER_NAME"] = environ.get("DOMAIN").strip()
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3153600
app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower()
app.config["VERSION"] = "1.0.0"
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
app.config["SESSION_COOKIE_SECURE"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * 365
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
app.config["DEFAULT_COLOR"] = environ.get("DEFAULT_COLOR", "ff0000").strip()
app.config["DEFAULT_THEME"] = environ.get("DEFAULT_THEME", "midnight").strip()
app.config["FORCE_HTTPS"] = 1
app.config["UserAgent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
app.config["HCAPTCHA_SITEKEY"] = environ.get("HCAPTCHA_SITEKEY","").strip()
app.config["HCAPTCHA_SECRET"] = environ.get("HCAPTCHA_SECRET","").strip()
app.config["SPAM_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_SIMILARITY_THRESHOLD", 0.5))
app.config["SPAM_URL_SIMILARITY_THRESHOLD"] = float(environ.get("SPAM_URL_SIMILARITY_THRESHOLD", 0.1))
app.config["SPAM_SIMILAR_COUNT_THRESHOLD"] = int(environ.get("SPAM_SIMILAR_COUNT_THRESHOLD", 10))
app.config["COMMENT_SPAM_SIMILAR_THRESHOLD"] = float(environ.get("COMMENT_SPAM_SIMILAR_THRESHOLD", 0.5))
app.config["COMMENT_SPAM_COUNT_THRESHOLD"] = int(environ.get("COMMENT_SPAM_COUNT_THRESHOLD", 10))
app.config["CACHE_TYPE"] = "RedisCache"
app.config["CACHE_REDIS_URL"] = environ.get("REDIS_URL", "redis://localhost")
app.config['MAIL_SERVER'] = 'smtp.gmail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = environ.get("MAIL_USERNAME", "").strip()
app.config['MAIL_PASSWORD'] = environ.get("MAIL_PASSWORD", "").strip()
app.config['DESCRIPTION'] = environ.get("DESCRIPTION", "rdrama.net caters to drama in all forms such as: Real life, videos, photos, gossip, rumors, news sites, Reddit, and Beyond™. There isn't drama we won't touch, and we want it all!").strip()
app.config['SETTINGS'] = {}
r=redis.Redis(host=environ.get("REDIS_URL", "redis://localhost"), decode_responses=True, ssl_cert_reqs=None)
def get_CF():
with app.app_context():
return request.headers.get('CF-Connecting-IP')
limiter = Limiter(
app,
key_func=get_CF,
default_limits=["3/second;30/minute;200/hour;1000/day"],
application_limits=["10/second;200/minute;5000/hour;10000/day"],
storage_uri=environ.get("REDIS_URL", "redis://localhost")
)
Base = declarative_base()
engine = create_engine(app.config['DATABASE_URL'])
db_session = scoped_session(sessionmaker(bind=engine, autoflush=False))
cache = Cache(app)
Compress(app)
mail = Mail(app)
@app.before_request
def before_request():
with open('site_settings.json', 'r') as f:
app.config['SETTINGS'] = json.load(f)
if request.host != app.config["SERVER_NAME"]: return {"error":"Unauthorized host provided."}, 401
if request.headers.get("CF-Worker"): return {"error":"Cloudflare workers are not allowed to access this website."}, 401
if not app.config['SETTINGS']['Bots'] and request.headers.get("Authorization"): abort(503)
g.db = db_session()
ua = request.headers.get("User-Agent","").lower()
if '; wv) ' in ua: g.webview = True
else: g.webview = False
if 'iphone' in ua or 'ipad' in ua or 'ipod' in ua or 'mac os' in ua or ' firefox/' in ua: g.inferior_browser = True
else: g.inferior_browser = False
g.timestamp = int(time.time())
@app.teardown_appcontext
def teardown_request(error):
if hasattr(g, 'db') and g.db:
g.db.close()
stdout.flush()
@app.after_request
def after_request(response):
response.headers.add("Strict-Transport-Security", "max-age=31536000")
response.headers.add("X-Frame-Options", "deny")
return response
if app.config["SERVER_NAME"] == 'localhost':
from files.routes import *
# from files.routes.chat import *
elif "load_chat" in argv:
from files.routes.chat import *
else:
from files.routes import *

View File

@ -1,21 +1,21 @@
from .alts import *
from .badges import *
from .clients import *
from .comment import *
from .domains import *
from .flags import *
from .user import *
from .userblock import *
from .submission import *
from .votes import *
from .domains import *
from .subscriptions import *
from files.__main__ import app
from .mod_logs import *
from .award import *
from .marsey import *
from .sub_block import *
from .saves import *
from .views import *
from .notifications import *
from .alts import *
from .badges import *
from .clients import *
from .comment import *
from .domains import *
from .flags import *
from .user import *
from .userblock import *
from .submission import *
from .votes import *
from .domains import *
from .subscriptions import *
from files.__main__ import app
from .mod_logs import *
from .award import *
from .marsey import *
from .sub_block import *
from .saves import *
from .views import *
from .notifications import *
from .follows import *

View File

@ -1,14 +1,14 @@
from sqlalchemy import *
from files.__main__ import Base
class Alt(Base):
__tablename__ = "alts"
user1 = Column(Integer, ForeignKey("users.id"), primary_key=True)
user2 = Column(Integer, ForeignKey("users.id"), primary_key=True)
is_manual = Column(Boolean, default=False)
def __repr__(self):
return f"<Alt(id={self.id})>"
from sqlalchemy import *
from files.__main__ import Base
class Alt(Base):
__tablename__ = "alts"
user1 = Column(Integer, ForeignKey("users.id"), primary_key=True)
user2 = Column(Integer, ForeignKey("users.id"), primary_key=True)
is_manual = Column(Boolean, default=False)
def __repr__(self):
return f"<Alt(id={self.id})>"

View File

@ -1,36 +1,36 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from os import environ
from files.helpers.lazy import lazy
from files.helpers.const import *
class AwardRelationship(Base):
__tablename__ = "award_relationships"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
submission_id = Column(Integer, ForeignKey("submissions.id"))
comment_id = Column(Integer, ForeignKey("comments.id"))
kind = Column(String)
user = relationship("User", primaryjoin="AwardRelationship.user_id==User.id", viewonly=True)
post = relationship("Submission", primaryjoin="AwardRelationship.submission_id==Submission.id", viewonly=True)
comment = relationship("Comment", primaryjoin="AwardRelationship.comment_id==Comment.id", viewonly=True)
@property
@lazy
def type(self):
return AWARDS[self.kind]
@property
@lazy
def title(self):
return self.type['title']
@property
@lazy
def class_list(self):
return self.type['icon']+' '+self.type['color']
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from os import environ
from files.helpers.lazy import lazy
from files.helpers.const import *
class AwardRelationship(Base):
__tablename__ = "award_relationships"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
submission_id = Column(Integer, ForeignKey("submissions.id"))
comment_id = Column(Integer, ForeignKey("comments.id"))
kind = Column(String)
user = relationship("User", primaryjoin="AwardRelationship.user_id==User.id", viewonly=True)
post = relationship("Submission", primaryjoin="AwardRelationship.submission_id==Submission.id", viewonly=True)
comment = relationship("Comment", primaryjoin="AwardRelationship.comment_id==Comment.id", viewonly=True)
@property
@lazy
def type(self):
return AWARDS[self.kind]
@property
@lazy
def title(self):
return self.type['title']
@property
@lazy
def class_list(self):
return self.type['icon']+' '+self.type['color']

View File

@ -1,73 +1,73 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base, app
from os import environ
from files.helpers.lazy import lazy
from files.helpers.const import *
from datetime import datetime
from json import loads
class BadgeDef(Base):
__tablename__ = "badge_defs"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
description = Column(String)
def __repr__(self):
return f"<BadgeDef(id={self.id})>"
class Badge(Base):
__tablename__ = "badges"
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
badge_id = Column(Integer, ForeignKey('badge_defs.id'), primary_key=True)
description = Column(String)
url = Column(String)
user = relationship("User", viewonly=True)
badge = relationship("BadgeDef", primaryjoin="foreign(Badge.badge_id) == remote(BadgeDef.id)", viewonly=True)
def __repr__(self):
return f"<Badge(user_id={self.user_id}, badge_id={self.badge_id})>"
@property
@lazy
def text(self):
if self.name == "Chud":
ti = self.user.agendaposter
if ti: text = self.badge.description + " until " + datetime.utcfromtimestamp(ti).strftime('%Y-%m-%d %H:%M:%S')
else: text = self.badge.description + " permanently"
elif self.badge_id in {94,95,96,97,98,109}:
if self.badge_id == 94: ti = self.user.progressivestack
elif self.badge_id == 95: ti = self.user.bird
elif self.badge_id == 96: ti = self.user.flairchanged
elif self.badge_id == 97: ti = self.user.longpost
elif self.badge_id == 98: ti = self.user.marseyawarded
elif self.badge_id == 109: ti = self.user.rehab
text = self.badge.description + " until " + datetime.utcfromtimestamp(ti).strftime('%Y-%m-%d %H:%M:%S')
elif self.description: text = self.description
elif self.badge.description: text = self.badge.description
else: return self.name
return f'{self.name} - {text}'
@property
@lazy
def name(self):
return self.badge.name
@property
@lazy
def path(self):
return f"/assets/images/badges/{self.badge_id}.webp"
@property
@lazy
def json(self):
return {'text': self.text,
'name': self.name,
'url': self.url,
'icon_url':self.path
}
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base, app
from os import environ
from files.helpers.lazy import lazy
from files.helpers.const import *
from datetime import datetime
from json import loads
class BadgeDef(Base):
__tablename__ = "badge_defs"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
description = Column(String)
def __repr__(self):
return f"<BadgeDef(id={self.id})>"
class Badge(Base):
__tablename__ = "badges"
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
badge_id = Column(Integer, ForeignKey('badge_defs.id'), primary_key=True)
description = Column(String)
url = Column(String)
user = relationship("User", viewonly=True)
badge = relationship("BadgeDef", primaryjoin="foreign(Badge.badge_id) == remote(BadgeDef.id)", viewonly=True)
def __repr__(self):
return f"<Badge(user_id={self.user_id}, badge_id={self.badge_id})>"
@property
@lazy
def text(self):
if self.name == "Chud":
ti = self.user.agendaposter
if ti: text = self.badge.description + " until " + datetime.utcfromtimestamp(ti).strftime('%Y-%m-%d %H:%M:%S')
else: text = self.badge.description + " permanently"
elif self.badge_id in {94,95,96,97,98,109}:
if self.badge_id == 94: ti = self.user.progressivestack
elif self.badge_id == 95: ti = self.user.bird
elif self.badge_id == 96: ti = self.user.flairchanged
elif self.badge_id == 97: ti = self.user.longpost
elif self.badge_id == 98: ti = self.user.marseyawarded
elif self.badge_id == 109: ti = self.user.rehab
text = self.badge.description + " until " + datetime.utcfromtimestamp(ti).strftime('%Y-%m-%d %H:%M:%S')
elif self.description: text = self.description
elif self.badge.description: text = self.badge.description
else: return self.name
return f'{self.name} - {text}'
@property
@lazy
def name(self):
return self.badge.name
@property
@lazy
def path(self):
return f"/assets/images/badges/{self.badge_id}.webp"
@property
@lazy
def json(self):
return {'text': self.text,
'name': self.name,
'url': self.url,
'icon_url':self.path
}

View File

@ -1,84 +1,84 @@
from flask import *
from sqlalchemy import *
from sqlalchemy.orm import relationship
from .submission import Submission
from .comment import Comment
from files.__main__ import Base
from files.helpers.lazy import lazy
from files.helpers.const import *
import time
class OauthApp(Base):
__tablename__ = "oauth_apps"
id = Column(Integer, primary_key=True)
client_id = Column(String)
app_name = Column(String)
redirect_uri = Column(String)
description = Column(String)
author_id = Column(Integer, ForeignKey("users.id"))
author = relationship("User", viewonly=True)
def __repr__(self): return f"<OauthApp(id={self.id})>"
@property
@lazy
def created_date(self):
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
@property
@lazy
def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
@property
@lazy
def permalink(self): return f"/admin/app/{self.id}"
@lazy
def idlist(self, page=1):
posts = g.db.query(Submission.id).filter_by(app_id=self.id)
posts=posts.order_by(Submission.created_utc.desc())
posts=posts.offset(100*(page-1)).limit(101)
return [x[0] for x in posts.all()]
@lazy
def comments_idlist(self, page=1):
posts = g.db.query(Comment.id).filter_by(app_id=self.id)
posts=posts.order_by(Comment.created_utc.desc())
posts=posts.offset(100*(page-1)).limit(101)
return [x[0] for x in posts.all()]
class ClientAuth(Base):
__tablename__ = "client_auths"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
oauth_client = Column(Integer, ForeignKey("oauth_apps.id"), primary_key=True)
access_token = Column(String)
user = relationship("User", viewonly=True)
application = relationship("OauthApp", viewonly=True)
@property
@lazy
def created_date(self):
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
@property
@lazy
def created_datetime(self):
from flask import *
from sqlalchemy import *
from sqlalchemy.orm import relationship
from .submission import Submission
from .comment import Comment
from files.__main__ import Base
from files.helpers.lazy import lazy
from files.helpers.const import *
import time
class OauthApp(Base):
__tablename__ = "oauth_apps"
id = Column(Integer, primary_key=True)
client_id = Column(String)
app_name = Column(String)
redirect_uri = Column(String)
description = Column(String)
author_id = Column(Integer, ForeignKey("users.id"))
author = relationship("User", viewonly=True)
def __repr__(self): return f"<OauthApp(id={self.id})>"
@property
@lazy
def created_date(self):
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
@property
@lazy
def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
@property
@lazy
def permalink(self): return f"/admin/app/{self.id}"
@lazy
def idlist(self, page=1):
posts = g.db.query(Submission.id).filter_by(app_id=self.id)
posts=posts.order_by(Submission.created_utc.desc())
posts=posts.offset(100*(page-1)).limit(101)
return [x[0] for x in posts.all()]
@lazy
def comments_idlist(self, page=1):
posts = g.db.query(Comment.id).filter_by(app_id=self.id)
posts=posts.order_by(Comment.created_utc.desc())
posts=posts.offset(100*(page-1)).limit(101)
return [x[0] for x in posts.all()]
class ClientAuth(Base):
__tablename__ = "client_auths"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
oauth_client = Column(Integer, ForeignKey("oauth_apps.id"), primary_key=True)
access_token = Column(String)
user = relationship("User", viewonly=True)
application = relationship("OauthApp", viewonly=True)
@property
@lazy
def created_date(self):
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
@property
@lazy
def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))

View File

@ -392,10 +392,11 @@ class Comment(Base):
if not self.total_poll_voted(v): body += ' d-none'
body += f'"> - <a href="/votes?link=t3_{c.id}"><span id="poll-{c.id}">{c.upvotes}</span> votes</a></span></label></div>'
curr = self.total_choice_voted(v)
if curr: curr = " value=" + str(curr[0].comment_id)
else: curr = ''
body += f'<input class="d-none" id="current-{self.id}"{curr}>'
if self.choices:
curr = self.total_choice_voted(v)
if curr: curr = " value=" + str(curr[0].comment_id)
else: curr = ''
body += f'<input class="d-none" id="current-{self.id}"{curr}>'
for c in self.choices:
body += f'''<div class="custom-control"><input name="choice-{self.id}" autocomplete="off" class="custom-control-input" type="radio" id="{c.id}" onchange="choice_vote('{c.id}','{self.id}')"'''
@ -478,7 +479,7 @@ class Comment(Base):
wager = int(split_result[4])
try: kind = split_result[5]
except: kind = "coins"
currency_kind = "Coins" if kind == "coins" else "Marseybucks"
currency_kind = "Coins" if kind == "coins" else "Marseybux"
try: is_insured = split_result[6]
except: is_insured = "0"

View File

@ -1,8 +1,8 @@
from sqlalchemy import *
from files.__main__ import Base
class BannedDomain(Base):
__tablename__ = "banneddomains"
domain = Column(String, primary_key=True)
from sqlalchemy import *
from files.__main__ import Base
class BannedDomain(Base):
__tablename__ = "banneddomains"
domain = Column(String, primary_key=True)
reason = Column(String)

View File

@ -1,71 +1,71 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
from files.helpers.const import *
import time
class Flag(Base):
__tablename__ = "flags"
post_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
reason = Column(String)
created_utc = Column(Integer)
user = relationship("User", primaryjoin = "Flag.user_id == User.id", uselist = False, viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Flag(id={self.id})>"
@property
@lazy
def created_date(self):
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
@property
@lazy
def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
@lazy
def realreason(self, v):
return censor_slurs(self.reason, v)
class CommentFlag(Base):
__tablename__ = "commentflags"
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
reason = Column(String)
created_utc = Column(Integer)
user = relationship("User", primaryjoin = "CommentFlag.user_id == User.id", uselist = False, viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<CommentFlag(id={self.id})>"
@property
@lazy
def created_date(self):
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
@property
@lazy
def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
@lazy
def realreason(self, v):
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
from files.helpers.const import *
import time
class Flag(Base):
__tablename__ = "flags"
post_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
reason = Column(String)
created_utc = Column(Integer)
user = relationship("User", primaryjoin = "Flag.user_id == User.id", uselist = False, viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Flag(id={self.id})>"
@property
@lazy
def created_date(self):
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
@property
@lazy
def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
@lazy
def realreason(self, v):
return censor_slurs(self.reason, v)
class CommentFlag(Base):
__tablename__ = "commentflags"
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
reason = Column(String)
created_utc = Column(Integer)
user = relationship("User", primaryjoin = "CommentFlag.user_id == User.id", uselist = False, viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<CommentFlag(id={self.id})>"
@property
@lazy
def created_date(self):
return time.strftime("%d %B %Y", time.gmtime(self.created_utc))
@property
@lazy
def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc)))
@lazy
def realreason(self, v):
return censor_slurs(self.reason, v)

View File

@ -1,424 +1,428 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time
from files.helpers.lazy import lazy
from os import environ
from copy import deepcopy
from files.helpers.const import *
class ModAction(Base):
__tablename__ = "modactions"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
kind = Column(String)
target_user_id = Column(Integer, ForeignKey("users.id"))
target_submission_id = Column(Integer, ForeignKey("submissions.id"))
target_comment_id = Column(Integer, ForeignKey("comments.id"))
_note=Column(String)
created_utc = Column(Integer)
user = relationship("User", primaryjoin="User.id==ModAction.user_id", viewonly=True)
target_user = relationship("User", primaryjoin="User.id==ModAction.target_user_id", viewonly=True)
target_post = relationship("Submission", viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<ModAction(id={self.id})>"
@property
@lazy
def age_string(self):
age = int(time.time()) - self.created_utc
if age < 60:
return "just now"
elif age < 3600:
minutes = int(age / 60)
return f"{minutes}m ago"
elif age < 86400:
hours = int(age / 3600)
return f"{hours}hr ago"
elif age < 2678400:
days = int(age / 86400)
return f"{days}d ago"
now = time.gmtime()
ctd = time.gmtime(self.created_utc)
months = now.tm_mon - ctd.tm_mon + 12 * (now.tm_year - ctd.tm_year)
if now.tm_mday < ctd.tm_mday:
months -= 1
if months < 12:
return f"{months}mo ago"
else:
years = int(months / 12)
return f"{years}yr ago"
@property
def note(self):
if self.kind=="ban_user":
if self.target_post: return f'for <a href="{self.target_post.permalink}">post</a>'
elif self.target_comment_id: return f'for <a href="/comment/{self.target_comment_id}">comment</a>'
else: return self._note
else:
return self._note or ""
@note.setter
def note(self, x):
self._note=x
@property
@lazy
def string(self):
output = ACTIONTYPES[self.kind]["str"].format(self=self, cc=CC_TITLE)
if self.note: output += f" <i>({self.note})</i>"
return output
@property
@lazy
def target_link(self):
if self.target_user: return f'<a href="{self.target_user.url}">{self.target_user.username}</a>'
elif self.target_post:
if self.target_post.club: return f'<a href="{self.target_post.permalink}">{CC} ONLY</a>'
return f'<a href="{self.target_post.permalink}">{self.target_post.title_html}</a>'
elif self.target_comment_id: return f'<a href="/comment/{self.target_comment_id}?context=8#context">comment</a>'
@property
@lazy
def icon(self):
return ACTIONTYPES[self.kind]['icon']
@property
@lazy
def color(self):
return ACTIONTYPES[self.kind]['color']
@property
@lazy
def permalink(self):
return f"/log/{self.id}"
ACTIONTYPES = {
'agendaposter': {
"str": 'set chud theme on {self.target_link}',
"icon": 'fa-snooze',
"color": 'bg-danger'
},
'approve_app': {
"str": 'approved an application by {self.target_link}',
"icon": 'fa-robot',
"color": 'bg-success'
},
'badge_grant': {
"str": 'granted badge to {self.target_link}',
"icon": 'fa-badge',
"color": 'bg-success'
},
'badge_remove': {
"str": 'removed badge from {self.target_link}',
"icon": 'fa-badge',
"color": 'bg-danger'
},
'ban_comment': {
"str": 'removed {self.target_link}',
"icon": 'fa-comment',
"color": 'bg-danger'
},
'ban_domain': {
"str": 'banned a domain',
"icon": 'fa-globe',
"color": 'bg-danger'
},
'ban_post': {
"str": 'removed post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-danger'
},
'ban_user': {
"str": 'banned user {self.target_link}',
"icon": 'fa-user-slash',
"color": 'bg-danger'
},
'change_sidebar': {
"str": 'changed the sidebar',
"icon": 'fa-columns',
"color": 'bg-primary'
},
'check': {
"str": 'gave {self.target_link} a checkmark',
"icon": 'fa-badge-check',
"color": 'bg-success'
},
'club_allow': {
"str": 'allowed user {self.target_link} into the {cc}',
"icon": 'fa-golf-club',
"color": 'bg-success'
},
'club_ban': {
"str": 'disallowed user {self.target_link} from the {cc}',
"icon": 'fa-golf-club',
"color": 'bg-danger'
},
'delete_report': {
"str": 'deleted report on {self.target_link}',
"icon": 'fa-flag',
"color": 'bg-danger'
},
'disable_Bots': {
"str": 'disabled Bots',
"icon": 'fa-robot',
"color": 'bg-danger'
},
'disable_Fart mode': {
"str": 'disabled fart mode',
"icon": 'fa-gas-pump-slash',
"color": 'bg-danger'
},
'disable_Read-only mode': {
"str": 'disabled readonly mode',
"icon": 'fa-book',
"color": 'bg-danger'
},
'disable_Signups': {
"str": 'disabled Signups',
"icon": 'fa-users',
"color": 'bg-danger'
},
'disable_under_attack': {
"str": 'disabled under attack mode',
"icon": 'fa-shield',
"color": 'bg-muted'
},
'distinguish_comment': {
"str": 'distinguished {self.target_link}',
"icon": 'fa-crown',
"color": 'bg-success'
},
'distinguish_post': {
"str": 'distinguished {self.target_link}',
"icon": 'fa-crown',
"color": 'bg-success'
},
'distribute': {
"str": 'distributed bet winnings to voters on {self.target_link}',
"icon": 'fa-dollar-sign',
"color": 'bg-success'
},
'dump_cache': {
"str": 'dumped cache',
"icon": 'fa-trash-alt',
"color": 'bg-muted'
},
'edit_post': {
"str": 'edited {self.target_link}',
"icon": 'fa-edit',
"color": 'bg-primary'
},
'enable_Bots': {
"str": 'enabled Bots',
"icon": 'fa-robot',
"color": 'bg-success'
},
'enable_Fart mode': {
"str": 'enabled fart mode',
"icon": 'fa-gas-pump',
"color": 'bg-success'
},
'enable_Read-only mode': {
"str": 'enabled readonly mode',
"icon": 'fa-book',
"color": 'bg-success'
},
'enable_Signups': {
"str": 'enabled Signups',
"icon": 'fa-users',
"color": 'bg-success'
},
'enable_under_attack': {
"str": 'enabled under attack mode',
"icon": 'fa-shield',
"color": 'bg-success'
},
'flair_post': {
"str": 'set a flair on {self.target_link}',
"icon": 'fa-tag',
"color": 'bg-primary'
},
'grant_awards': {
"str": 'granted awards to {self.target_link}',
"icon": 'fa-gift',
"color": 'bg-primary'
},
'link_accounts': {
"str": 'linked {self.target_link}',
"icon": 'fa-link',
"color": 'bg-success'
},
'make_admin': {
"str": 'made {self.target_link} admin',
"icon": 'fa-user-crown',
"color": 'bg-success'
},
'make_meme_admin': {
"str": 'made {self.target_link} meme admin',
"icon": 'fa-user-crown',
"color": 'bg-success'
},
'monthly': {
"str": 'distributed monthly marseybux',
"icon": 'fa-sack-dollar',
"color": 'bg-success'
},
'move_hole': {
"str": 'moved {self.target_link} to <a href="/h/{self.target_post.sub}">/h/{self.target_post.sub}</a>',
"icon": 'fa-manhole',
"color": 'bg-primary'
},
'nuke_user': {
"str": 'removed all content of {self.target_link}',
"icon": 'fa-radiation-alt',
"color": 'bg-danger'
},
'pin_comment': {
"str": 'pinned a {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-success'
},
'pin_post': {
"str": 'pinned post {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-success'
},
'purge_cache': {
"str": 'purged cache',
"icon": 'fa-memory',
"color": 'bg-muted'
},
'reject_app': {
"str": 'rejected an application request by {self.target_link}',
"icon": 'fa-robot',
"color": 'bg-muted'
},
'remove_admin': {
"str": 'removed {self.target_link} as admin',
"icon": 'fa-user-crown',
"color": 'bg-danger'
},
'remove_meme_admin': {
"str": 'removed {self.target_link} as meme admin',
"icon": 'fa-user-crown',
"color": 'bg-danger'
},
'revert': {
"str": 'reverted {self.target_link} mod actions',
"icon": 'fa-history',
"color": 'bg-danger'
},
'revoke_app': {
"str": 'revoked an application by {self.target_link}',
"icon": 'fa-robot',
"color": 'bg-muted'
},
'set_flair_locked': {
"str": "set {self.target_link}'s flair (locked)",
"icon": 'fa-award',
"color": 'bg-primary'
},
'set_flair_notlocked': {
"str": "set {self.target_link}'s flair (not locked)",
"icon": 'fa-award',
"color": 'bg-primary'
},
'set_nsfw': {
"str": 'set nsfw on post {self.target_link}',
"icon": 'fa-eye-evil',
"color": 'bg-danger'
},
'shadowban': {
"str": 'shadowbanned {self.target_link}',
"icon": 'fa-eye-slash',
"color": 'bg-danger'
},
'unagendaposter': {
"str": 'removed chud theme from {self.target_link}',
"icon": 'fa-snooze',
"color": 'bg-success'
},
'unban_comment': {
"str": 'reinstated {self.target_link}',
"icon": 'fa-comment',
"color": 'bg-success'
},
'unban_domain': {
"str": 'unbanned a domain',
"icon": 'fa-globe',
"color": 'bg-success'
},
'unban_post': {
"str": 'reinstated post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-success'
},
'unban_user': {
"str": 'unbanned user {self.target_link}',
"icon": 'fa-user',
"color": 'bg-success'
},
'uncheck': {
"str": 'removed checkmark from {self.target_link}',
"icon": 'fa-badge-check',
"color": 'bg-muted'
},
'undistinguish_comment': {
"str": 'un-distinguished {self.target_link}',
"icon": 'fa-crown',
"color": 'bg-muted'
},
'undistinguish_post': {
"str": 'un-distinguished {self.target_link}',
"icon": 'fa-crown',
"color": 'bg-muted'
},
'unnuke_user': {
"str": 'approved all content of {self.target_link}',
"icon": 'fa-radiation-alt',
"color": 'bg-success'
},
'unpin_comment': {
"str": 'un-pinned a {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-muted'
},
'unpin_post': {
"str": 'un-pinned post {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-muted'
},
'unset_nsfw': {
"str": 'un-set nsfw on post {self.target_link}',
"icon": 'fa-eye-evil',
"color": 'bg-success'
},
'unshadowban': {
"str": 'unshadowbanned {self.target_link}',
"icon": 'fa-eye',
"color": 'bg-success'
}
}
ACTIONTYPES2 = deepcopy(ACTIONTYPES)
ACTIONTYPES2.pop("shadowban")
ACTIONTYPES2.pop("unshadowban")
ACTIONTYPES2.pop("flair_post")
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time
from files.helpers.lazy import lazy
from os import environ
from copy import deepcopy
from files.helpers.const import *
class ModAction(Base):
__tablename__ = "modactions"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
kind = Column(String)
target_user_id = Column(Integer, ForeignKey("users.id"))
target_submission_id = Column(Integer, ForeignKey("submissions.id"))
target_comment_id = Column(Integer, ForeignKey("comments.id"))
_note=Column(String)
created_utc = Column(Integer)
user = relationship("User", primaryjoin="User.id==ModAction.user_id", viewonly=True)
target_user = relationship("User", primaryjoin="User.id==ModAction.target_user_id", viewonly=True)
target_post = relationship("Submission", viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<ModAction(id={self.id})>"
@property
@lazy
def age_string(self):
age = int(time.time()) - self.created_utc
if age < 60:
return "just now"
elif age < 3600:
minutes = int(age / 60)
return f"{minutes}m ago"
elif age < 86400:
hours = int(age / 3600)
return f"{hours}hr ago"
elif age < 2678400:
days = int(age / 86400)
return f"{days}d ago"
now = time.gmtime()
ctd = time.gmtime(self.created_utc)
months = now.tm_mon - ctd.tm_mon + 12 * (now.tm_year - ctd.tm_year)
if now.tm_mday < ctd.tm_mday:
months -= 1
if months < 12:
return f"{months}mo ago"
else:
years = int(months / 12)
return f"{years}yr ago"
@property
@lazy
def created_string(self):
return time.strftime('%d %b %Y %H:%M:%S UTC', time.gmtime(self.created_utc))
@property
def note(self):
if self.kind=="ban_user":
if self.target_post: return f'for <a href="{self.target_post.permalink}">post</a>'
elif self.target_comment_id: return f'for <a href="/comment/{self.target_comment_id}">comment</a>'
else: return self._note
else:
return self._note or ""
@note.setter
def note(self, x):
self._note=x
@property
@lazy
def string(self):
output = ACTIONTYPES[self.kind]["str"].format(self=self, cc=CC_TITLE)
if self.note: output += f" <i>({self.note})</i>"
return output
@property
@lazy
def target_link(self):
if self.target_user: return f'<a href="{self.target_user.url}">{self.target_user.username}</a>'
elif self.target_post:
if self.target_post.club: return f'<a href="{self.target_post.permalink}">{CC} ONLY</a>'
return f'<a href="{self.target_post.permalink}">{self.target_post.title_html}</a>'
elif self.target_comment_id: return f'<a href="/comment/{self.target_comment_id}?context=8#context">comment</a>'
@property
@lazy
def icon(self):
return ACTIONTYPES[self.kind]['icon']
@property
@lazy
def color(self):
return ACTIONTYPES[self.kind]['color']
@property
@lazy
def permalink(self):
return f"/log/{self.id}"
ACTIONTYPES = {
'agendaposter': {
"str": 'set chud theme on {self.target_link}',
"icon": 'fa-snooze',
"color": 'bg-danger'
},
'approve_app': {
"str": 'approved an application by {self.target_link}',
"icon": 'fa-robot',
"color": 'bg-success'
},
'badge_grant': {
"str": 'granted badge to {self.target_link}',
"icon": 'fa-badge',
"color": 'bg-success'
},
'badge_remove': {
"str": 'removed badge from {self.target_link}',
"icon": 'fa-badge',
"color": 'bg-danger'
},
'ban_comment': {
"str": 'removed {self.target_link}',
"icon": 'fa-comment',
"color": 'bg-danger'
},
'ban_domain': {
"str": 'banned a domain',
"icon": 'fa-globe',
"color": 'bg-danger'
},
'ban_post': {
"str": 'removed post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-danger'
},
'ban_user': {
"str": 'banned user {self.target_link}',
"icon": 'fa-user-slash',
"color": 'bg-danger'
},
'change_sidebar': {
"str": 'changed the sidebar',
"icon": 'fa-columns',
"color": 'bg-primary'
},
'check': {
"str": 'gave {self.target_link} a checkmark',
"icon": 'fa-badge-check',
"color": 'bg-success'
},
'club_allow': {
"str": 'allowed user {self.target_link} into the {cc}',
"icon": 'fa-golf-club',
"color": 'bg-success'
},
'club_ban': {
"str": 'disallowed user {self.target_link} from the {cc}',
"icon": 'fa-golf-club',
"color": 'bg-danger'
},
'delete_report': {
"str": 'deleted report on {self.target_link}',
"icon": 'fa-flag',
"color": 'bg-danger'
},
'disable_Bots': {
"str": 'disabled Bots',
"icon": 'fa-robot',
"color": 'bg-danger'
},
'disable_Fart mode': {
"str": 'disabled fart mode',
"icon": 'fa-gas-pump-slash',
"color": 'bg-danger'
},
'disable_Read-only mode': {
"str": 'disabled readonly mode',
"icon": 'fa-book',
"color": 'bg-danger'
},
'disable_Signups': {
"str": 'disabled Signups',
"icon": 'fa-users',
"color": 'bg-danger'
},
'disable_under_attack': {
"str": 'disabled under attack mode',
"icon": 'fa-shield',
"color": 'bg-muted'
},
'distinguish_comment': {
"str": 'distinguished {self.target_link}',
"icon": 'fa-crown',
"color": 'bg-success'
},
'distinguish_post': {
"str": 'distinguished {self.target_link}',
"icon": 'fa-crown',
"color": 'bg-success'
},
'distribute': {
"str": 'distributed bet winnings to voters on {self.target_link}',
"icon": 'fa-dollar-sign',
"color": 'bg-success'
},
'dump_cache': {
"str": 'dumped cache',
"icon": 'fa-trash-alt',
"color": 'bg-muted'
},
'edit_post': {
"str": 'edited {self.target_link}',
"icon": 'fa-edit',
"color": 'bg-primary'
},
'enable_Bots': {
"str": 'enabled Bots',
"icon": 'fa-robot',
"color": 'bg-success'
},
'enable_Fart mode': {
"str": 'enabled fart mode',
"icon": 'fa-gas-pump',
"color": 'bg-success'
},
'enable_Read-only mode': {
"str": 'enabled readonly mode',
"icon": 'fa-book',
"color": 'bg-success'
},
'enable_Signups': {
"str": 'enabled Signups',
"icon": 'fa-users',
"color": 'bg-success'
},
'enable_under_attack': {
"str": 'enabled under attack mode',
"icon": 'fa-shield',
"color": 'bg-success'
},
'flair_post': {
"str": 'set a flair on {self.target_link}',
"icon": 'fa-tag',
"color": 'bg-primary'
},
'grant_awards': {
"str": 'granted awards to {self.target_link}',
"icon": 'fa-gift',
"color": 'bg-primary'
},
'link_accounts': {
"str": 'linked {self.target_link}',
"icon": 'fa-link',
"color": 'bg-success'
},
'make_admin': {
"str": 'made {self.target_link} admin',
"icon": 'fa-user-crown',
"color": 'bg-success'
},
'make_meme_admin': {
"str": 'made {self.target_link} meme admin',
"icon": 'fa-user-crown',
"color": 'bg-success'
},
'monthly': {
"str": 'distributed monthly marseybux',
"icon": 'fa-sack-dollar',
"color": 'bg-success'
},
'move_hole': {
"str": 'moved {self.target_link} to <a href="/h/{self.target_post.sub}">/h/{self.target_post.sub}</a>',
"icon": 'fa-manhole',
"color": 'bg-primary'
},
'nuke_user': {
"str": 'removed all content of {self.target_link}',
"icon": 'fa-radiation-alt',
"color": 'bg-danger'
},
'pin_comment': {
"str": 'pinned a {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-success'
},
'pin_post': {
"str": 'pinned post {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-success'
},
'purge_cache': {
"str": 'purged cache',
"icon": 'fa-memory',
"color": 'bg-muted'
},
'reject_app': {
"str": 'rejected an application request by {self.target_link}',
"icon": 'fa-robot',
"color": 'bg-muted'
},
'remove_admin': {
"str": 'removed {self.target_link} as admin',
"icon": 'fa-user-crown',
"color": 'bg-danger'
},
'remove_meme_admin': {
"str": 'removed {self.target_link} as meme admin',
"icon": 'fa-user-crown',
"color": 'bg-danger'
},
'revert': {
"str": 'reverted {self.target_link} mod actions',
"icon": 'fa-history',
"color": 'bg-danger'
},
'revoke_app': {
"str": 'revoked an application by {self.target_link}',
"icon": 'fa-robot',
"color": 'bg-muted'
},
'set_flair_locked': {
"str": "set {self.target_link}'s flair (locked)",
"icon": 'fa-award',
"color": 'bg-primary'
},
'set_flair_notlocked': {
"str": "set {self.target_link}'s flair (not locked)",
"icon": 'fa-award',
"color": 'bg-primary'
},
'set_nsfw': {
"str": 'set nsfw on post {self.target_link}',
"icon": 'fa-eye-evil',
"color": 'bg-danger'
},
'shadowban': {
"str": 'shadowbanned {self.target_link}',
"icon": 'fa-eye-slash',
"color": 'bg-danger'
},
'unagendaposter': {
"str": 'removed chud theme from {self.target_link}',
"icon": 'fa-snooze',
"color": 'bg-success'
},
'unban_comment': {
"str": 'reinstated {self.target_link}',
"icon": 'fa-comment',
"color": 'bg-success'
},
'unban_domain': {
"str": 'unbanned a domain',
"icon": 'fa-globe',
"color": 'bg-success'
},
'unban_post': {
"str": 'reinstated post {self.target_link}',
"icon": 'fa-feather-alt',
"color": 'bg-success'
},
'unban_user': {
"str": 'unbanned user {self.target_link}',
"icon": 'fa-user',
"color": 'bg-success'
},
'uncheck': {
"str": 'removed checkmark from {self.target_link}',
"icon": 'fa-badge-check',
"color": 'bg-muted'
},
'undistinguish_comment': {
"str": 'un-distinguished {self.target_link}',
"icon": 'fa-crown',
"color": 'bg-muted'
},
'undistinguish_post': {
"str": 'un-distinguished {self.target_link}',
"icon": 'fa-crown',
"color": 'bg-muted'
},
'unnuke_user': {
"str": 'approved all content of {self.target_link}',
"icon": 'fa-radiation-alt',
"color": 'bg-success'
},
'unpin_comment': {
"str": 'un-pinned a {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-muted'
},
'unpin_post': {
"str": 'un-pinned post {self.target_link}',
"icon": 'fa-thumbtack fa-rotate--45',
"color": 'bg-muted'
},
'unset_nsfw': {
"str": 'un-set nsfw on post {self.target_link}',
"icon": 'fa-eye-evil',
"color": 'bg-success'
},
'unshadowban': {
"str": 'unshadowbanned {self.target_link}',
"icon": 'fa-eye',
"color": 'bg-success'
}
}
ACTIONTYPES2 = deepcopy(ACTIONTYPES)
ACTIONTYPES2.pop("shadowban")
ACTIONTYPES2.pop("unshadowban")
ACTIONTYPES2.pop("flair_post")
ACTIONTYPES2.pop("edit_post")

View File

@ -165,8 +165,6 @@ class Submission(Base):
@lazy
def edited_string(self):
if not self.edited_utc: return "never"
age = int(time.time()) - self.edited_utc
if age < 60:
@ -184,11 +182,13 @@ class Submission(Base):
now = time.gmtime()
ctd = time.gmtime(self.edited_utc)
months = now.tm_mon - ctd.tm_mon + 12 * (now.tm_year - ctd.tm_year)
if now.tm_mday < ctd.tm_mday:
months -= 1
if months < 12:
return f"{months}mo ago"
else:
years = now.tm_year - ctd.tm_year
years = int(months / 12)
return f"{years}yr ago"
@ -452,7 +452,7 @@ class Submission(Base):
def realtitle(self, v):
if self.club and not (v and (v.paid_dues or v.id == self.author_id)):
if v: return random.choice(TROLLTITLES).format(username=v.username)
elif SITE == 'cringetopia.org': return f'Please make an account to see this post'
elif dues == -2: return f'Please make an account to see this post'
else: return f'{CC} MEMBERS ONLY'
elif self.title_html: title = self.title_html
else: title = self.title

View File

@ -1,16 +1,16 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
class Subscription(Base):
__tablename__ = "subscriptions"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user = relationship("User", uselist=False, viewonly=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __repr__(self):
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
class Subscription(Base):
__tablename__ = "subscriptions"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user = relationship("User", uselist=False, viewonly=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Subscription(id={self.id})>"

View File

@ -26,6 +26,9 @@ defaulttheme = environ.get("DEFAULT_THEME", "midnight").strip()
defaulttimefilter = environ.get("DEFAULT_TIME_FILTER", "all").strip()
cardview = bool(int(environ.get("CARD_VIEW", 1)))
if SITE_NAME in ('Cringetopia', 'WPD'): patron_default = 7
else: patron_default = 0
class User(Base):
__tablename__ = "users"
@ -48,7 +51,7 @@ class User(Base):
profileurl = Column(String)
bannerurl = Column(String)
house = Column(String)
patron = Column(Integer, default=0)
patron = Column(Integer, default=patron_default)
patron_utc = Column(Integer, default=0)
verified = Column(String)
verifiedcolor = Column(String)
@ -181,6 +184,21 @@ class User(Base):
return time.strftime("%d %b %Y", time.gmtime(self.created_utc))
@property
@lazy
def is_cakeday(self):
if time.time() - self.created_utc > 363 * 86400:
date = time.strftime("%d %b", time.gmtime(self.created_utc))
now = time.strftime("%d %b", time.gmtime())
if date == now:
if not self.has_badge(134):
new_badge = Badge(badge_id=134, user_id=self.id)
g.db.add(new_badge)
g.db.commit()
return True
return False
@property
@lazy
def discount(self):
@ -190,6 +208,7 @@ class User(Base):
elif self.patron == 4: discount = 0.75
elif self.patron == 5: discount = 0.70
elif self.patron == 6: discount = 0.65
elif self.patron == 7: discount = 0.60
else: discount = 1
for badge in discounts:
@ -306,7 +325,7 @@ class User(Base):
@property
@lazy
def follow_count(self):
return g.db.query(Follow.target_id).filter_by(user_id=self.id).count()
return g.db.query(Follow).filter_by(user_id=self.id).count()
@property
@lazy
@ -406,7 +425,7 @@ class User(Base):
@lazy
def modaction_num(self):
if self.admin_level < 2: return 0
return g.db.query(ModAction.id).filter_by(user_id=self.id).count()
return g.db.query(ModAction).filter_by(user_id=self.id).count()
@property
@lazy
@ -421,12 +440,12 @@ class User(Base):
@property
@lazy
def post_notifications_count(self):
return g.db.query(Notification.user_id).join(Comment).filter(Notification.user_id == self.id, Notification.read == False, Comment.author_id == AUTOJANNY_ID).count()
return g.db.query(Notification).join(Comment).filter(Notification.user_id == self.id, Notification.read == False, Comment.author_id == AUTOJANNY_ID).count()
@property
@lazy
def reddit_notifications_count(self):
return g.db.query(Notification.user_id).join(Comment).filter(Notification.user_id == self.id, Notification.read == False, Comment.is_banned == False, Comment.deleted_utc == 0, Comment.body_html.like('%<p>New site mention: <a href="https://old.reddit.com/r/%'), Comment.parent_submission == None, Comment.author_id == NOTIFICATIONS_ID).count()
return g.db.query(Notification).join(Comment).filter(Notification.user_id == self.id, Notification.read == False, Comment.is_banned == False, Comment.deleted_utc == 0, Comment.body_html.like('%<p>New site mention: <a href="https://old.reddit.com/r/%'), Comment.parent_submission == None, Comment.author_id == NOTIFICATIONS_ID).count()
@property
@lazy
@ -636,4 +655,4 @@ class User(Base):
def filter_words(self):
l = [i.strip() for i in self.custom_filter_list.split('\n')] if self.custom_filter_list else []
l = [i for i in l if i]
return l
return l

View File

@ -1,15 +1,15 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
class UserBlock(Base):
__tablename__ = "userblocks"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
target_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
user = relationship("User", primaryjoin="User.id==UserBlock.user_id", viewonly=True)
target = relationship("User", primaryjoin="User.id==UserBlock.target_id", viewonly=True)
def __repr__(self):
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
class UserBlock(Base):
__tablename__ = "userblocks"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
target_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
user = relationship("User", primaryjoin="User.id==UserBlock.user_id", viewonly=True)
target = relationship("User", primaryjoin="User.id==UserBlock.target_id", viewonly=True)
def __repr__(self):
return f"<UserBlock(user={self.user_id}, target={self.target_id})>"

View File

@ -1,87 +1,87 @@
from flask import *
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
import time
class Vote(Base):
__tablename__ = "votes"
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = Column(Integer)
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
real = Column(Boolean, default=True)
created_utc = Column(Integer)
user = relationship("User", lazy="subquery", viewonly=True)
post = relationship("Submission", lazy="subquery", viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Vote(id={self.id})>"
@property
@lazy
def json_core(self):
data={
"user_id": self.user_id,
"submission_id":self.submission_id,
"vote_type":self.vote_type
}
return data
@property
@lazy
def json(self):
data=self.json_core
data["user"]=self.user.json_core
data["post"]=self.post.json_core
return data
class CommentVote(Base):
__tablename__ = "commentvotes"
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = Column(Integer)
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
real = Column(Boolean, default=True)
created_utc = Column(Integer)
user = relationship("User", lazy="subquery")
comment = relationship("Comment", lazy="subquery", viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<CommentVote(id={self.id})>"
@property
@lazy
def json_core(self):
data={
"user_id": self.user_id,
"comment_id":self.comment_id,
"vote_type":self.vote_type
}
return data
@property
@lazy
def json(self):
data=self.json_core
data["user"]=self.user.json_core
data["comment"]=self.comment.json_core
from flask import *
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
import time
class Vote(Base):
__tablename__ = "votes"
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = Column(Integer)
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
real = Column(Boolean, default=True)
created_utc = Column(Integer)
user = relationship("User", lazy="subquery", viewonly=True)
post = relationship("Submission", lazy="subquery", viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Vote(id={self.id})>"
@property
@lazy
def json_core(self):
data={
"user_id": self.user_id,
"submission_id":self.submission_id,
"vote_type":self.vote_type
}
return data
@property
@lazy
def json(self):
data=self.json_core
data["user"]=self.user.json_core
data["post"]=self.post.json_core
return data
class CommentVote(Base):
__tablename__ = "commentvotes"
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = Column(Integer)
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
real = Column(Boolean, default=True)
created_utc = Column(Integer)
user = relationship("User", lazy="subquery")
comment = relationship("Comment", lazy="subquery", viewonly=True)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<CommentVote(id={self.id})>"
@property
@lazy
def json_core(self):
data={
"user_id": self.user_id,
"comment_id":self.comment_id,
"vote_type":self.vote_type
}
return data
@property
@lazy
def json(self):
data=self.json_core
data["user"]=self.user.json_core
data["comment"]=self.comment.json_core
return data

View File

@ -1,100 +1,112 @@
from files.classes import *
from flask import g
from .sanitize import *
from .const import *
def create_comment(text_html, autojanny=False):
if autojanny: author_id = AUTOJANNY_ID
else: author_id = NOTIFICATIONS_ID
new_comment = Comment(author_id=author_id,
parent_submission=None,
body_html=text_html,
distinguish_level=6)
g.db.add(new_comment)
g.db.flush()
new_comment.top_comment_id = new_comment.id
return new_comment.id
def send_repeatable_notification(uid, text, autojanny=False):
if autojanny: author_id = AUTOJANNY_ID
else: author_id = NOTIFICATIONS_ID
text_html = sanitize(text)
existing_comment = g.db.query(Comment.id).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).first()
if existing_comment:
cid = existing_comment[0]
existing_notif = g.db.query(Notification.user_id).filter_by(user_id=uid, comment_id=cid).one_or_none()
if existing_notif: cid = create_comment(text_html, autojanny)
else: cid = create_comment(text_html, autojanny)
notif = Notification(comment_id=cid, user_id=uid)
g.db.add(notif)
def send_notification(uid, text, autojanny=False):
cid = notif_comment(text, autojanny)
add_notif(cid, uid)
def notif_comment(text, autojanny=False):
if autojanny:
author_id = AUTOJANNY_ID
alert = True
else:
author_id = NOTIFICATIONS_ID
alert = False
text_html = sanitize(text, alert=alert)
existing = g.db.query(Comment.id).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).one_or_none()
if existing: return existing[0]
else: return create_comment(text_html, autojanny)
def notif_comment2(p):
search_html = f'%</a> has mentioned you: <a href="/post/{p.id}">%'
existing = g.db.query(Comment.id).filter(Comment.author_id == NOTIFICATIONS_ID, Comment.parent_submission == None, Comment.body_html.like(search_html)).first()
if existing: return existing[0]
else:
text = f"@{p.author.username} has mentioned you: [{p.title}](/post/{p.id})"
if p.sub: text += f" in <a href='/h/{p.sub}'>/h/{p.sub}"
text_html = sanitize(text, alert=True)
return create_comment(text_html)
def add_notif(cid, uid):
existing = g.db.query(Notification.user_id).filter_by(comment_id=cid, user_id=uid).one_or_none()
if not existing:
notif = Notification(comment_id=cid, user_id=uid)
g.db.add(notif)
def NOTIFY_USERS(text, v):
notify_users = set()
for word, id in NOTIFIED_USERS.items():
if id == 0 or v.id == id: continue
if word in text.lower() and id not in notify_users: notify_users.add(id)
captured = []
for i in mention_regex.finditer(text):
if v.username.lower() == i.group(2).lower(): continue
if i.group(0) in captured: continue
captured.append(i.group(0))
user = get_user(i.group(2), graceful=True)
if user and v.id != user.id and not v.any_block_exists(user): notify_users.add(user.id)
from files.classes import *
from flask import g
from .sanitize import *
from .const import *
def create_comment(text_html, autojanny=False):
if autojanny: author_id = AUTOJANNY_ID
else: author_id = NOTIFICATIONS_ID
new_comment = Comment(author_id=author_id,
parent_submission=None,
body_html=text_html,
distinguish_level=6)
g.db.add(new_comment)
g.db.flush()
new_comment.top_comment_id = new_comment.id
return new_comment.id
def send_repeatable_notification(uid, text, autojanny=False):
if autojanny: author_id = AUTOJANNY_ID
else: author_id = NOTIFICATIONS_ID
text_html = sanitize(text)
existing_comment = g.db.query(Comment.id).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).first()
if existing_comment:
cid = existing_comment[0]
existing_notif = g.db.query(Notification.user_id).filter_by(user_id=uid, comment_id=cid).one_or_none()
if existing_notif: cid = create_comment(text_html, autojanny)
else: cid = create_comment(text_html, autojanny)
notif = Notification(comment_id=cid, user_id=uid)
g.db.add(notif)
def send_notification(uid, text, autojanny=False):
cid = notif_comment(text, autojanny)
add_notif(cid, uid)
def notif_comment(text, autojanny=False):
if autojanny:
author_id = AUTOJANNY_ID
alert = True
else:
author_id = NOTIFICATIONS_ID
alert = False
text_html = sanitize(text, alert=alert)
try: existing = g.db.query(Comment.id).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).one_or_none()
except:
existing = g.db.query(Comment).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).all()
notifs = g.db.query(Notification).filter(Notification.comment_id.in_([x.id for x in existing])).all()
for c in notifs: g.db.delete(c)
g.db.flush()
for c in existing: g.db.delete(c)
g.db.flush()
existing = g.db.query(Comment.id).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).one_or_none()
if existing: return existing[0]
else: return create_comment(text_html, autojanny)
def notif_comment2(p):
search_html = f'%</a> has mentioned you: <a href="/post/{p.id}">%'
existing = g.db.query(Comment.id).filter(Comment.author_id == NOTIFICATIONS_ID, Comment.parent_submission == None, Comment.body_html.like(search_html)).first()
if existing: return existing[0]
else:
text = f"@{p.author.username} has mentioned you: [{p.title}](/post/{p.id})"
if p.sub: text += f" in <a href='/h/{p.sub}'>/h/{p.sub}"
text_html = sanitize(text, alert=True)
return create_comment(text_html)
def add_notif(cid, uid):
existing = g.db.query(Notification.user_id).filter_by(comment_id=cid, user_id=uid).one_or_none()
if not existing:
notif = Notification(comment_id=cid, user_id=uid)
g.db.add(notif)
def NOTIFY_USERS(text, v):
notify_users = set()
for word, id in NOTIFIED_USERS.items():
if id == 0 or v.id == id: continue
if word in text.lower() and id not in notify_users: notify_users.add(id)
captured = []
for i in mention_regex.finditer(text):
if v.username.lower() == i.group(2).lower(): continue
if i.group(0) in captured: continue
captured.append(i.group(0))
user = get_user(i.group(2), graceful=True)
if user and v.id != user.id and not v.any_block_exists(user): notify_users.add(user.id)
return notify_users

View File

@ -6,7 +6,7 @@ deck_count = 4
ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A")
suits = ("♠️", "♥️", "♣️", "♦️")
coins_command_word = "!blackjack"
marseybucks_command_word = "!blackjackmb"
marseybux_command_word = "!blackjackmb"
minimum_bet = 100
maximum_bet = INFINITY
@ -51,7 +51,7 @@ def format_all(player_hand, dealer_hand, deck, status, wager, kind, is_insured=0
def check_for_blackjack_commands(in_text, from_user, from_comment):
for command_word in (coins_command_word, marseybucks_command_word):
for command_word in (coins_command_word, marseybux_command_word):
currency_prop = "coins" if command_word == coins_command_word else "procoins"
currency_value = getattr(from_user, currency_prop, 0)
@ -107,7 +107,8 @@ def player_stayed(from_comment):
deck = deck.split("/")
if dealer_value == 21 and is_insured == "1":
from_comment.author.coins += int(wager)
currency_value = getattr(from_comment.author, kind, 0)
setattr(from_comment.author, kind, currency_value + int(wager))
else:
while dealer_value < 17 and dealer_value != -1:
next = deck.pop(0)
@ -126,12 +127,13 @@ def player_doubled_down(from_comment):
# When doubling down, the player receives one additional card (a "hit") and their initial bet is doubled.
player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_")
wager_value = int(wager)
currency_value = getattr(from_comment.author, kind, 0)
# Gotsta have enough coins
if (from_comment.author.coins < wager_value): return
if (currency_value < wager_value): return
# Double the initial wager
from_comment.author.coins -= wager_value
setattr(from_comment.author, kind, currency_value - wager_value)
wager_value *= 2
# Apply the changes to the stored hand.
@ -148,12 +150,13 @@ def player_bought_insurance(from_comment):
player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_")
wager_value = int(wager)
insurance_cost = wager_value / 2
currency_value = getattr(from_comment.author, kind, 0)
# Gotsta have enough coins
if (from_comment.author.coins < insurance_cost): return
if (currency_value < insurance_cost): return
# Charge for (and grant) insurance
from_comment.author.coins -= insurance_cost
setattr(from_comment.author, kind, currency_value - insurance_cost)
is_insured = 1
# Apply the changes to the stored hand.

View File

@ -26,29 +26,23 @@ AJ_REPLACEMENTS = {
' YOUR ': " YOU'RE ",
' TO ': " TOO ",
'anybody': 'anypony',
'everybody': 'everypony',
'Anybody': 'Anypony',
'Everybody': 'Everypony',
'ANYBODY': 'ANYPONY',
'EVERYBODY': 'EVERYPONY',
}
if SITE_NAME == 'Cringetopia':
SLURS = {
"retarded": "neurodivergent",
"retard": "neurodivergent",
"faggotry": "cute twinkry",
"faggot": "cute twink",
"n1gger": "🏀",
"nlgger": "🏀",
"nigger": "🏀",
"uss liberty incident": "tragic accident aboard the USS Liberty",
"lavon affair": "Lavon Misunderstanding",
"i hate marsey": "i love marsey",
"autistic": "neurodivergent",
"holohoax": "i tried to claim the Holocaust didn't happen because I am a pencil-dicked imbecile and the word filter caught me lol",
"i hate carp": "i love Carp",
"heil hitler": "hello kitty",
" fag ": " cute twink ",
}
else:
if SITE_NAME == 'rDrama':
SLURS = {
"california": "commiefornia",
"hollywood": "hollyweird",
"tiananmen square": "tiananmen square didn't happen (but it should have)",
"dasha": "beautiful angelic perfect Dasha/future Mrs. Carp",
"retarded": "r-slurred",
"retard": "r-slur",
"gayfag": "gaystrag",
@ -56,8 +50,8 @@ else:
"richfag": "richstrag",
"newfag": "newstrag",
"oldfag": "oldstrag",
"faggotry": "cute twinkry",
"faggot": "cute twink",
"fag": "cute twink",
"pedophile": "libertarian",
"kill yourself": "keep yourself safe",
"n1gger": "BIPOC",
@ -104,15 +98,32 @@ else:
"elon musk": "rocket daddy",
"fake and gay": "fake and straight",
" rapist ": " male feminist ",
" pedo ": " libertarian ",
" rapist": " male feminist",
" kys ": " keep yourself safe ",
" fag ": " cute twink ",
" pedo ": " libertarian ",
" pedos ": " libertarians ",
}
else:
SLURS = {
"retarded": "neurodivergent",
"retard": "neurodivergent",
"faggot": "cute twink",
"fag": "cute twink",
"n1gger": "🏀",
"nlgger": "🏀",
"nigger": "🏀",
"uss liberty incident": "tragic accident aboard the USS Liberty",
"lavon affair": "Lavon Misunderstanding",
"i hate marsey": "i love marsey",
"autistic": "neurodivergent",
"holohoax": "i tried to claim the Holocaust didn't happen because I am a pencil-dicked imbecile and the word filter caught me lol",
"i hate carp": "i love Carp",
"heil hitler": "hello kitty",
}
single_words = "|".join([slur.lower() for slur in SLURS.keys()])
LONGPOST_REPLIES = ('Wow, you must be a JP fan.', 'This is one of the worst posts I have EVER seen. Delete it.', "No, don't reply like this, please do another wall of unhinged rant please.", '# 😴😴😴', "Ma'am we've been over this before. You need to stop.", "I've known more coherent downies.", "Your pulitzer's in the mail", "That's great and all, but I asked for my burger without cheese.", 'That degree finally paying off', "That's nice sweaty. Why don't you have a seat in the time out corner with Pizzashill until you calm down, then you can have your Capri Sun.", "All them words won't bring your pa back.", "You had a chance to not be completely worthless, but it looks like you threw it away. At least you're consistent.", 'Some people are able to display their intelligence by going on at length on a subject and never actually saying anything. This ability is most common in trades such as politics, public relations, and law. You have impressed me by being able to best them all, while still coming off as an absolute idiot.', "You can type 10,000 characters and you decided that these were the one's that you wanted.", 'Have you owned the libs yet?', "I don't know what you said, because I've seen another human naked.", 'Impressive. Normally people with such severe developmental disabilities struggle to write much more than a sentence or two. He really has exceded our expectations for the writing portion. Sadly the coherency of his writing, along with his abilities in the social skills and reading portions, are far behind his peers with similar disabilities.', "This is a really long way of saying you don't fuck.", "Sorry ma'am, looks like his delusions have gotten worse. We'll have to admit him.", ':#marseywoah:', 'If only you could put that energy into your relationships', 'Posts like this is why I do Heroine.', 'still unemployed then?', 'K', 'look im gunna have 2 ask u 2 keep ur giant dumps in the toilet not in my replys 😷😷😷', "Mommy is soooo proud of you, sweaty. Let's put this sperg out up on the fridge with all your other failures.", "Good job bobby, here's a star", "That was a mistake. You're about to find out the hard way why.", f'You sat down and wrote all this shit. You could have done so many other things with your life. What happened to your life that made you decide writing novels of bullshit on {SITE} was the best option?', "I don't have enough spoons to read this shit", "All those words won't bring daddy back.", 'OUT!', "Damn, you're really mad over this, but thanks for the effort you put into typing that all out! Sadly I won't read it all.", "Jesse what the fuck are you talking about??", "▼you're fucking bananas if you think I'm reading all that, take my downvote and shut up idiot", "Are you feeling okay bud?")
AGENDAPOSTER_PHRASE = 'trans lives matter'
@ -130,6 +141,7 @@ if SITE in {'rdrama.net','devrama.xyz'}:
AUTOCHOICE_ID = 9167
BASEDBOT_ID = 0
SCHIZO_ID = 8494
A_ID = 1230
KIPPY_ID = 7150
GIFT_NOTIF_ID = 995
@ -173,6 +185,7 @@ elif SITE == "pcmemes.net":
AUTOCHOICE_ID = 2072
BASEDBOT_ID = 800
SCHIZO_ID = 0
A_ID = 0
KIPPY_ID = 1592
PIZZASHILL_ID = 0
@ -205,6 +218,7 @@ elif SITE == 'cringetopia.org':
AUTOCHOICE_ID = 8
BASEDBOT_ID = 0
SCHIZO_ID = 0
A_ID = 0
KIPPY_ID = 0
GIFT_NOTIF_ID = 43
@ -248,6 +262,7 @@ else:
AUTOCHOICE_ID = 8
BASEDBOT_ID = 0
SCHIZO_ID = 0
A_ID = 0
KIPPY_ID = 0
GIFT_NOTIF_ID = 9
@ -613,6 +628,14 @@ AWARDS = {
"color": "text-gold",
"price": 50000
},
"checkmark": {
"kind": "checkmark",
"title": "Checkmark",
"description": "Gives the recipient a checkmark.",
"icon": "fas fa-badge-check",
"color": "checkmark",
"price": 100000
},
"firework": {
"kind": "firework",
"title": "Fireworks",
@ -671,7 +694,7 @@ if SITE_NAME == 'PCM':
AWARDS2 = deepcopy(AWARDS)
for k, val in AWARDS.items():
if val['description'] == '' and not (k == 'ghost' and SITE_NAME == 'PCM'): AWARDS2.pop(k)
if SITE == 'pcmemes.net' and k in ('ban','pizzashill','marsey','bird','grass','chud'): AWARDS2.pop(k)
if SITE == 'pcmemes.net' and k in ('ban','pizzashill','marsey','bird','grass','chud','unblockable'): AWARDS2.pop(k)
AWARDS3 = {}
@ -704,10 +727,14 @@ NOTIFIED_USERS = {
'kippy': KIPPY_ID,
'the_homocracy': HOMO_ID,
'soren': SOREN_ID,
'schizocel': SCHIZO_ID,
'scitzocel': SCHIZO_ID
}
FORTUNE_REPLIES = ('<b style="color:#6023f8">Your fortune: Allah Wills It</b>','<b style="color:#d302a7">Your fortune: Inshallah, Only Good Things Shall Come To Pass</b>','<b style="color:#e7890c">Your fortune: Allah Smiles At You This Day</b>','<b style="color:#7fec11">Your fortune: Your Bussy Is In For A Blasting</b>','<b style="color:#43fd3b">Your fortune: You Will Be Propositioned By A High-Tier Twink</b>','<b style="color:#9d05da">Your fortune: Repent, You Have Displeased Allah And His Vengeance Is Nigh</b>','<b style="color:#f51c6a">Your fortune: Reply Hazy, Try Again</b>','<b style="color:#00cbb0">Your fortune: lmao you just lost 100 coins</b>','<b style="color:#2a56fb">Your fortune: Yikes 😬</b>','<b style="color:#0893e1">Your fortune: You Will Be Blessed With Many Black Bulls</b>','<b style="color:#16f174">Your fortune: NEETmax, The Day Is Lost If You Venture Outside</b>','<b style="color:#fd4d32">Your fortune: A Taste Of Jannah Awaits You Today</b>','<b style="color:#bac200">Your fortune: Watch Your Back</b>','<b style="color:#6023f8">Your fortune: Outlook good</b>','<b style="color:#d302a7">Your fortune: Godly Luck</b>','<b style="color:#e7890c">Your fortune: Good Luck</b>','<b style="color:#7fec11">Your fortune: Bad Luck</b>','<b style="color:#43fd3b">Your fortune: Good news will come to you by mail</b>','<b style="color:#9d05da">Your fortune: Very Bad Luck</b>','<b style="color:#00cbb0">Your fortune: キタ━━━━━━(゚∀゚)━━━━━━ !!!!</b>','<b style="color:#2a56fb">Your fortune: Better not tell you now</b>','<b style="color:#0893e1">Your fortune: You will meet a dark handsome stranger</b>','<b style="color:#16f174">Your fortune:  ´_ゝ`)フーン</b>','<b style="color:#fd4d32">Your fortune: Excellent Luck</b>','<b style="color:#bac200">Your fortune: Average Luck</b>')
FACTCHECK_REPLIES = ('<b style="color:#6023f8">Factcheck: This claim has been confirmed as correct by experts. </b>','<b style="color:#d302a7">Factcheck: This claim has been classified as misogynistic.</b>','<b style="color:#e7890c">Factcheck: This claim is currently being debunked.</b>','<b style="color:#7fec11">Factcheck: This claim is 100% true.</b>','<b style="color:#9d05da">Factcheck: This claim hurts trans lives.</b>','<b style="color:#f51c6a">Factcheck: [REDACTED].</b>','<b style="color:#00cbb0">Factcheck: This claim is both true and false.</b>','<b style="color:#2a56fb">Factcheck: You really believe that shit? Lmao dumbass nigga 🤣</b>','<b style="color:#0893e1">Factcheck: None of this is real.</b>','<b style="color:#16f174">Factcheck: Yes.</b>','<b style="color:#fd4d32">Factcheck: This claim has not been approved by experts.</b>','<b style="color:#bac200">Factcheck: This claim is a gross exageration of reality.</b>','<b style="color:#ff2200">Factcheck: WARNING! THIS CLAIM HAS BEEN CLASSIFIED AS DANGEROUS. PLEASE REMAIN STILL, AN AGENT WILL COME TO MEET YOU SHORTLY.</b>')
if SITE_NAME == 'rDrama': patron = 'Paypig'
else: patron = 'Patron'
@ -808,6 +835,7 @@ slur_regex = re.compile(f"({single_words})(?![^<]*>)", flags=re.I|re.A)
slur_regex_upper = re.compile(f"({single_words.upper()})(?![^<]*>)", flags=re.A)
torture_regex = re.compile('(^|\s)(i|me) ', flags=re.I|re.A)
torture_regex2 = re.compile("(^|\s)i'm ", flags=re.I|re.A)
torture_regex_exclude = re.compile('^\s*>', flags=re.A)
def sub_matcher(match):
return SLURS[match.group(0).lower()]
@ -822,15 +850,21 @@ def censor_slurs(body, logged_user):
return body
def torture_ap(body, username):
for k, l in AJ_REPLACEMENTS.items():
body = body.replace(k, l)
body = torture_regex.sub(rf'\1@{username} ', body)
body = torture_regex2.sub(rf'\1@{username} is ', body)
return body
lines = body.splitlines(keepends=True)
for i in range(len(lines)):
if torture_regex_exclude.match(lines[i]):
continue
for k, l in AJ_REPLACEMENTS.items():
lines[i] = lines[i].replace(k, l)
lines[i] = torture_regex.sub(rf'\1@{username} ', lines[i])
lines[i] = torture_regex2.sub(rf'\1@{username} is ', lines[i])
return ''.join(lines)
YOUTUBE_KEY = environ.get("YOUTUBE_KEY", "").strip()
ADMIGGERS = (37696,37697,37749,37833,37838)
ADMIGGERS = (37696,37697,37749,37833,37838,39413)
proxies = {"http":"http://127.0.0.1:18080","https":"http://127.0.0.1:18080"}
@ -840,9 +874,9 @@ approved_embed_hosts = [
'rdrama.net',
'pcmemes.net',
'cringetopia.org',
'watchpeopledie.co',
'devrama.xyz',
'imgur.com',
'ibb.co',
'lain.la',
'pngfind.com',
'kym-cdn.com',
@ -887,7 +921,13 @@ approved_embed_hosts = [
'githubusercontent.com',
'unilad.co.uk',
'grrrgraphics.com',
'redditmedia.com'
'redditmedia.com',
'deviantart.com',
'deviantart.net',
'googleapis.com',
'bing.com',
'typekit.net',
'postimg.cc'
]
hosts = "|".join(approved_embed_hosts).replace('.','\.')
@ -906,8 +946,13 @@ yt_id_regex = re.compile('[a-z0-9-_]{5,20}', flags=re.I|re.A)
image_regex = re.compile("(^|\s)(https:\/\/[\w\-.#&/=\?@%;+]{5,250}(\.png|\.jpg|\.jpeg|\.gif|\.webp|maxwidth=9999|fidelity=high))($|\s)", flags=re.I|re.A)
link_fix_regex = re.compile("(?!.*(http|\/))(.*\[[^\]]+\]\()([^)]+\))", flags=re.A)
css_regex = re.compile('''url\(['"]?(.*?)['"]?\)''', flags=re.I|re.A)
css_regex2 = re.compile('''['"](http.*?)['"]''', flags=re.I|re.A)
procoins_li = (0,2500,5000,10000,25000,50000,125000,250000)
linefeeds_regex = re.compile("([^\n])\n([^\n])", flags=re.A)
def make_name(*args, **kwargs): return request.base_url
def make_name(*args, **kwargs): return request.base_url

View File

@ -1,66 +1,60 @@
from os import environ
import requests
import threading
from .const import *
SERVER_ID = environ.get("DISCORD_SERVER_ID",'').strip()
CLIENT_ID = environ.get("DISCORD_CLIENT_ID",'').strip()
CLIENT_SECRET = environ.get("DISCORD_CLIENT_SECRET",'').strip()
BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN",'').strip()
AUTH = environ.get("DISCORD_AUTH",'').strip()
def discord_wrap(f):
def wrapper(*args, **kwargs):
user=args[0]
if not user.discord_id:
return
thread=threading.Thread(target=f, args=args, kwargs=kwargs)
thread.start()
wrapper.__name__=f.__name__
return wrapper
@discord_wrap
def add_role(user, role_name):
role_id = ROLES[role_name]
url = f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}/roles/{role_id}"
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
requests.put(url, headers=headers, timeout=5)
@discord_wrap
def remove_role(user, role_name):
role_id = ROLES[role_name]
url = f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}/roles/{role_id}"
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
requests.delete(url, headers=headers, timeout=5)
@discord_wrap
def remove_user(user):
url=f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}"
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
requests.delete(url, headers=headers, timeout=5)
@discord_wrap
def set_nick(user, nick):
url=f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}"
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
data={"nick": nick}
requests.patch(url, headers=headers, json=data, timeout=5)
def send_discord_message(message):
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
data={"content": message}
requests.post("https://discordapp.com/api/channels/924485611715452940/messages", headers=headers, data=data, timeout=5)
requests.post("https://discordapp.com/api/channels/924486091795484732/messages", headers=headers, data=data, timeout=5)
def send_cringetopia_message(message):
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
data={"content": message}
requests.post("https://discordapp.com/api/channels/965264044531527740/messages", headers=headers, data=data, timeout=5)
from os import environ
import requests
import threading
from .const import *
SERVER_ID = environ.get("DISCORD_SERVER_ID",'').strip()
CLIENT_ID = environ.get("DISCORD_CLIENT_ID",'').strip()
CLIENT_SECRET = environ.get("DISCORD_CLIENT_SECRET",'').strip()
BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN",'').strip()
AUTH = environ.get("DISCORD_AUTH",'').strip()
def discord_wrap(f):
def wrapper(*args, **kwargs):
user=args[0]
if not user.discord_id:
return
thread=threading.Thread(target=f, args=args, kwargs=kwargs)
thread.start()
wrapper.__name__=f.__name__
return wrapper
@discord_wrap
def add_role(user, role_name):
role_id = ROLES[role_name]
url = f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}/roles/{role_id}"
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
requests.put(url, headers=headers, timeout=5)
@discord_wrap
def remove_role(user, role_name):
role_id = ROLES[role_name]
url = f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}/roles/{role_id}"
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
requests.delete(url, headers=headers, timeout=5)
@discord_wrap
def remove_user(user):
url=f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}"
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
requests.delete(url, headers=headers, timeout=5)
@discord_wrap
def set_nick(user, nick):
url=f"https://discordapp.com/api/guilds/{SERVER_ID}/members/{user.discord_id}"
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
data={"nick": nick}
requests.patch(url, headers=headers, json=data, timeout=5)
def send_discord_message(message):
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
data={"content": message}
requests.post("https://discordapp.com/api/channels/924485611715452940/messages", headers=headers, data=data, timeout=5)
requests.post("https://discordapp.com/api/channels/924486091795484732/messages", headers=headers, data=data, timeout=5)

View File

@ -1,289 +1,289 @@
from files.classes import *
from flask import g
def get_id(username, v=None, graceful=False):
username = username.replace('\\', '').replace('_', '\_').replace('%', '').strip()
user = g.db.query(
User.id
).filter(
or_(
User.username.ilike(username),
User.original_username.ilike(username)
)
).one_or_none()
if not user:
if not graceful:
abort(404)
else:
return None
return user[0]
def get_user(username, v=None, graceful=False):
if not username:
if not graceful: abort(404)
else: return None
username = username.replace('\\', '').replace('_', '\_').replace('%', '').strip()
user = g.db.query(
User
).filter(
or_(
User.username.ilike(username),
User.original_username.ilike(username)
)
).one_or_none()
if not user:
if not graceful: abort(404)
else: return None
if v:
block = g.db.query(UserBlock).filter(
or_(
and_(
UserBlock.user_id == v.id,
UserBlock.target_id == user.id
),
and_(UserBlock.user_id == user.id,
UserBlock.target_id == v.id
)
)
).first()
user.is_blocking = block and block.user_id == v.id
user.is_blocked = block and block.target_id == v.id
return user
def get_account(id, v=None):
try: id = int(id)
except: abort(404)
user = g.db.query(User).filter_by(id = id).one_or_none()
if not user: abort(404)
if v:
block = g.db.query(UserBlock).filter(
or_(
and_(
UserBlock.user_id == v.id,
UserBlock.target_id == user.id
),
and_(UserBlock.user_id == user.id,
UserBlock.target_id == v.id
)
)
).first()
user.is_blocking = block and block.user_id == v.id
user.is_blocked = block and block.target_id == v.id
return user
def get_post(i, v=None, graceful=False):
if v:
vt = g.db.query(Vote).filter_by(
user_id=v.id, submission_id=i).subquery()
blocking = v.blocking.subquery()
items = g.db.query(
Submission,
vt.c.vote_type,
blocking.c.target_id,
)
items=items.filter(Submission.id == i
).join(
vt,
vt.c.submission_id == Submission.id,
isouter=True
).join(
blocking,
blocking.c.target_id == Submission.author_id,
isouter=True
)
items=items.one_or_none()
if not items:
if graceful: return None
else: abort(404)
x = items[0]
x.voted = items[1] or 0
x.is_blocking = items[2] or 0
else:
items = g.db.query(
Submission
).filter(Submission.id == i).one_or_none()
if not items:
if graceful: return None
else: abort(404)
x=items
return x
def get_posts(pids, v=None):
if not pids:
return []
if v:
vt = g.db.query(Vote).filter(
Vote.submission_id.in_(pids),
Vote.user_id==v.id
).subquery()
blocking = v.blocking.subquery()
blocked = v.blocked.subquery()
query = g.db.query(
Submission,
vt.c.vote_type,
blocking.c.target_id,
blocked.c.target_id,
).filter(
Submission.id.in_(pids)
).join(
vt, vt.c.submission_id==Submission.id, isouter=True
).join(
blocking,
blocking.c.target_id == Submission.author_id,
isouter=True
).join(
blocked,
blocked.c.user_id == Submission.author_id,
isouter=True
).all()
output = [p[0] for p in query]
for i in range(len(output)):
output[i].voted = query[i][1] or 0
output[i].is_blocking = query[i][2] or 0
output[i].is_blocked = query[i][3] or 0
else:
output = g.db.query(Submission,).filter(Submission.id.in_(pids)).all()
return sorted(output, key=lambda x: pids.index(x.id))
def get_comment(i, v=None, graceful=False):
if v:
comment=g.db.query(Comment).filter(Comment.id == i).one_or_none()
if not comment and not graceful: abort(404)
block = g.db.query(UserBlock).filter(
or_(
and_(
UserBlock.user_id == v.id,
UserBlock.target_id == comment.author_id
),
and_(
UserBlock.user_id == comment.author_id,
UserBlock.target_id == v.id
)
)
).first()
vts = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id)
vt = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
comment.is_blocking = block and block.user_id == v.id
comment.is_blocked = block and block.target_id == v.id
comment.voted = vt.vote_type if vt else 0
else:
comment = g.db.query(Comment).filter(Comment.id == i).one_or_none()
if not comment and not graceful:abort(404)
return comment
def get_comments(cids, v=None, load_parent=False):
if not cids: return []
if v:
votes = g.db.query(CommentVote).filter_by(user_id=v.id).subquery()
blocking = v.blocking.subquery()
blocked = v.blocked.subquery()
comments = g.db.query(
Comment,
votes.c.vote_type,
blocking.c.target_id,
blocked.c.target_id,
).filter(Comment.id.in_(cids))
if not (v and (v.shadowbanned or v.admin_level > 2)):
comments = comments.join(User, User.id == Comment.author_id).filter(User.shadowbanned == None)
comments = comments.join(
votes,
votes.c.comment_id == Comment.id,
isouter=True
).join(
blocking,
blocking.c.target_id == Comment.author_id,
isouter=True
).join(
blocked,
blocked.c.user_id == Comment.author_id,
isouter=True
).all()
output = []
for c in comments:
comment = c[0]
comment.voted = c[1] or 0
comment.is_blocking = c[2] or 0
comment.is_blocked = c[3] or 0
output.append(comment)
else:
output = g.db.query(Comment).join(User, User.id == Comment.author_id).filter(User.shadowbanned == None, Comment.id.in_(cids)).all()
if load_parent:
parents = [x.parent_comment_id for x in output if x.parent_comment_id]
parents = get_comments(parents, v=v)
parents = {x.id: x for x in parents}
for c in output: c.sex = parents.get(c.parent_comment_id)
return sorted(output, key=lambda x: cids.index(x.id))
def get_domain(s):
parts = s.split(".")
domain_list = set()
for i in range(len(parts)):
new_domain = parts[i]
for j in range(i + 1, len(parts)):
new_domain += "." + parts[j]
domain_list.add(new_domain)
doms = [x for x in g.db.query(BannedDomain).filter(BannedDomain.domain.in_(domain_list)).all()]
if not doms:
return None
doms = sorted(doms, key=lambda x: len(x.domain), reverse=True)
from files.classes import *
from flask import g
def get_id(username, v=None, graceful=False):
username = username.replace('\\', '').replace('_', '\_').replace('%', '').strip()
user = g.db.query(
User.id
).filter(
or_(
User.username.ilike(username),
User.original_username.ilike(username)
)
).one_or_none()
if not user:
if not graceful:
abort(404)
else:
return None
return user[0]
def get_user(username, v=None, graceful=False):
if not username:
if not graceful: abort(404)
else: return None
username = username.replace('\\', '').replace('_', '\_').replace('%', '').strip()
user = g.db.query(
User
).filter(
or_(
User.username.ilike(username),
User.original_username.ilike(username)
)
).one_or_none()
if not user:
if not graceful: abort(404)
else: return None
if v:
block = g.db.query(UserBlock).filter(
or_(
and_(
UserBlock.user_id == v.id,
UserBlock.target_id == user.id
),
and_(UserBlock.user_id == user.id,
UserBlock.target_id == v.id
)
)
).first()
user.is_blocking = block and block.user_id == v.id
user.is_blocked = block and block.target_id == v.id
return user
def get_account(id, v=None):
try: id = int(id)
except: abort(404)
user = g.db.query(User).filter_by(id = id).one_or_none()
if not user: abort(404)
if v:
block = g.db.query(UserBlock).filter(
or_(
and_(
UserBlock.user_id == v.id,
UserBlock.target_id == user.id
),
and_(UserBlock.user_id == user.id,
UserBlock.target_id == v.id
)
)
).first()
user.is_blocking = block and block.user_id == v.id
user.is_blocked = block and block.target_id == v.id
return user
def get_post(i, v=None, graceful=False):
if v:
vt = g.db.query(Vote).filter_by(
user_id=v.id, submission_id=i).subquery()
blocking = v.blocking.subquery()
items = g.db.query(
Submission,
vt.c.vote_type,
blocking.c.target_id,
)
items=items.filter(Submission.id == i
).join(
vt,
vt.c.submission_id == Submission.id,
isouter=True
).join(
blocking,
blocking.c.target_id == Submission.author_id,
isouter=True
)
items=items.one_or_none()
if not items:
if graceful: return None
else: abort(404)
x = items[0]
x.voted = items[1] or 0
x.is_blocking = items[2] or 0
else:
items = g.db.query(
Submission
).filter(Submission.id == i).one_or_none()
if not items:
if graceful: return None
else: abort(404)
x=items
return x
def get_posts(pids, v=None):
if not pids:
return []
if v:
vt = g.db.query(Vote).filter(
Vote.submission_id.in_(pids),
Vote.user_id==v.id
).subquery()
blocking = v.blocking.subquery()
blocked = v.blocked.subquery()
query = g.db.query(
Submission,
vt.c.vote_type,
blocking.c.target_id,
blocked.c.target_id,
).filter(
Submission.id.in_(pids)
).join(
vt, vt.c.submission_id==Submission.id, isouter=True
).join(
blocking,
blocking.c.target_id == Submission.author_id,
isouter=True
).join(
blocked,
blocked.c.user_id == Submission.author_id,
isouter=True
).all()
output = [p[0] for p in query]
for i in range(len(output)):
output[i].voted = query[i][1] or 0
output[i].is_blocking = query[i][2] or 0
output[i].is_blocked = query[i][3] or 0
else:
output = g.db.query(Submission,).filter(Submission.id.in_(pids)).all()
return sorted(output, key=lambda x: pids.index(x.id))
def get_comment(i, v=None, graceful=False):
if v:
comment=g.db.query(Comment).filter(Comment.id == i).one_or_none()
if not comment and not graceful: abort(404)
block = g.db.query(UserBlock).filter(
or_(
and_(
UserBlock.user_id == v.id,
UserBlock.target_id == comment.author_id
),
and_(
UserBlock.user_id == comment.author_id,
UserBlock.target_id == v.id
)
)
).first()
vts = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id)
vt = g.db.query(CommentVote).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
comment.is_blocking = block and block.user_id == v.id
comment.is_blocked = block and block.target_id == v.id
comment.voted = vt.vote_type if vt else 0
else:
comment = g.db.query(Comment).filter(Comment.id == i).one_or_none()
if not comment and not graceful:abort(404)
return comment
def get_comments(cids, v=None, load_parent=False):
if not cids: return []
if v:
votes = g.db.query(CommentVote).filter_by(user_id=v.id).subquery()
blocking = v.blocking.subquery()
blocked = v.blocked.subquery()
comments = g.db.query(
Comment,
votes.c.vote_type,
blocking.c.target_id,
blocked.c.target_id,
).filter(Comment.id.in_(cids))
if not (v and (v.shadowbanned or v.admin_level > 2)):
comments = comments.join(User, User.id == Comment.author_id).filter(User.shadowbanned == None)
comments = comments.join(
votes,
votes.c.comment_id == Comment.id,
isouter=True
).join(
blocking,
blocking.c.target_id == Comment.author_id,
isouter=True
).join(
blocked,
blocked.c.user_id == Comment.author_id,
isouter=True
).all()
output = []
for c in comments:
comment = c[0]
comment.voted = c[1] or 0
comment.is_blocking = c[2] or 0
comment.is_blocked = c[3] or 0
output.append(comment)
else:
output = g.db.query(Comment).join(User, User.id == Comment.author_id).filter(User.shadowbanned == None, Comment.id.in_(cids)).all()
if load_parent:
parents = [x.parent_comment_id for x in output if x.parent_comment_id]
parents = get_comments(parents, v=v)
parents = {x.id: x for x in parents}
for c in output: c.sex = parents.get(c.parent_comment_id)
return sorted(output, key=lambda x: cids.index(x.id))
def get_domain(s):
parts = s.split(".")
domain_list = set()
for i in range(len(parts)):
new_domain = parts[i]
for j in range(i + 1, len(parts)):
new_domain += "." + parts[j]
domain_list.add(new_domain)
doms = [x for x in g.db.query(BannedDomain).filter(BannedDomain.domain.in_(domain_list)).all()]
if not doms:
return None
doms = sorted(doms, key=lambda x: len(x.domain), reverse=True)
return doms[0]

View File

@ -1,28 +1,35 @@
from PIL import Image, ImageOps
from PIL.ImageSequence import Iterator
from webptools import gifwebp
import subprocess
def process_image(filename=None, resize=0):
i = Image.open(filename)
if resize and i.width > resize:
try: subprocess.call(["convert", filename, "-coalesce", "-resize", f"{resize}>", filename])
except: pass
elif i.format.lower() != "webp":
exif = i.getexif()
for k in exif.keys():
if k != 0x0112:
exif[k] = None
del exif[k]
i.info["exif"] = exif.tobytes()
if i.format.lower() == "gif":
gifwebp(input_image=filename, output_image=filename, option="-mixed -metadata none -f 100 -mt -m 6")
else:
i = ImageOps.exif_transpose(i)
i.save(filename, format="WEBP", method=6)
from PIL import Image, ImageOps
from PIL.ImageSequence import Iterator
from webptools import gifwebp
import subprocess
import os
from flask import abort
def process_image(patron, filename=None, resize=0):
size = os.stat(filename).st_size
if size > 16 * 1024 * 1024 or not patron and size > 8 * 1024 * 1024:
os.remove(filename)
abort(413)
i = Image.open(filename)
if resize and i.width > resize:
try: subprocess.call(["convert", filename, "-coalesce", "-resize", f"{resize}>", filename])
except: pass
elif i.format.lower() != "webp":
exif = i.getexif()
for k in exif.keys():
if k != 0x0112:
exif[k] = None
del exif[k]
i.info["exif"] = exif.tobytes()
if i.format.lower() == "gif":
gifwebp(input_image=filename, output_image=filename, option="-mixed -metadata none -f 100 -mt -m 6")
else:
i = ImageOps.exif_transpose(i)
i.save(filename, format="WEBP", method=6)
return filename

View File

@ -3,6 +3,7 @@ from .get import *
from os import listdir, environ
from .const import *
import time
from datetime import datetime
@app.template_filter("post_embed")
def post_embed(id, v):
@ -49,4 +50,4 @@ def timestamp(timestamp):
@app.context_processor
def inject_constants():
return {"environ":environ, "SITE":SITE, "SITE_NAME":SITE_NAME, "SITE_FULL":SITE_FULL, "AUTOJANNY_ID":AUTOJANNY_ID, "NOTIFICATIONS_ID":NOTIFICATIONS_ID, "PUSHER_ID":PUSHER_ID, "CC":CC, "CC_TITLE":CC_TITLE, "listdir":listdir, "MOOSE_ID":MOOSE_ID, "AEVANN_ID":AEVANN_ID, "PIZZASHILL_ID":PIZZASHILL_ID, "config":app.config.get, "DEFAULT_COLOR":DEFAULT_COLOR, "COLORS":COLORS, "ADMIGGERS":ADMIGGERS}
return {"environ":environ, "SITE":SITE, "SITE_NAME":SITE_NAME, "SITE_FULL":SITE_FULL, "AUTOJANNY_ID":AUTOJANNY_ID, "NOTIFICATIONS_ID":NOTIFICATIONS_ID, "PUSHER_ID":PUSHER_ID, "CC":CC, "CC_TITLE":CC_TITLE, "listdir":listdir, "MOOSE_ID":MOOSE_ID, "AEVANN_ID":AEVANN_ID, "PIZZASHILL_ID":PIZZASHILL_ID, "config":app.config.get, "DEFAULT_COLOR":DEFAULT_COLOR, "COLORS":COLORS, "ADMIGGERS":ADMIGGERS, "datetime":datetime}

View File

@ -1,18 +1,18 @@
# Prevents certain properties from having to be recomputed each time they
# are referenced
def lazy(f):
def wrapper(*args, **kwargs):
o = args[0]
if "_lazy" not in o.__dict__: o.__dict__["_lazy"] = {}
if f.__name__ not in o.__dict__["_lazy"]: o.__dict__["_lazy"][f.__name__] = f(*args, **kwargs)
return o.__dict__["_lazy"][f.__name__]
wrapper.__name__ = f.__name__
return wrapper
# Prevents certain properties from having to be recomputed each time they
# are referenced
def lazy(f):
def wrapper(*args, **kwargs):
o = args[0]
if "_lazy" not in o.__dict__: o.__dict__["_lazy"] = {}
if f.__name__ not in o.__dict__["_lazy"]: o.__dict__["_lazy"][f.__name__] = f(*args, **kwargs)
return o.__dict__["_lazy"][f.__name__]
wrapper.__name__ = f.__name__
return wrapper

View File

@ -1,328 +1,357 @@
import bleach
from bs4 import BeautifulSoup
from bleach.linkifier import LinkifyFilter, build_url_re
from functools import partial
from .get import *
from os import path, environ
import re
from mistletoe import markdown
from json import loads, dump
from random import random, choice
import signal
import time
import requests
TLDS = ('ac','ad','ae','aero','af','ag','ai','al','am','an','ao','aq','ar','arpa','as','asia','at','au','aw','ax','az','ba','bb','bd','be','bf','bg','bh','bi','biz','bj','bm','bn','bo','br','bs','bt','bv','bw','by','bz','ca','cafe','cat','cc','cd','cf','cg','ch','ci','ck','cl','club','cm','cn','co','com','coop','cr','cu','cv','cx','cy','cz','de','dj','dk','dm','do','dz','ec','edu','ee','eg','er','es','et','eu','fi','fj','fk','fm','fo','fr','ga','gb','gd','ge','gf','gg','gh','gi','gl','gm','gn','gov','gp','gq','gr','gs','gt','gu','gw','gy','hk','hm','hn','hr','ht','hu','id','ie','il','im','in','info','int','io','iq','ir','is','it','je','jm','jo','jobs','jp','ke','kg','kh','ki','km','kn','kp','kr','kw','ky','kz','la','lb','lc','li','lk','lr','ls','lt','lu','lv','ly','ma','mc','md','me','mg','mh','mil','mk','ml','mm','mn','mo','mobi','mp','mq','mr','ms','mt','mu','museum','mv','mw','mx','my','mz','na','name','nc','ne','net','nf','ng','ni','nl','no','np','nr','nu','nz','om','org','pa','pe','pf','pg','ph','pk','pl','pm','pn','post','pr','pro','ps','pt','pw','py','qa','re','ro','rs','ru','rw','sa','sb','sc','sd','se','sg','sh','si','sj','sk','sl','sm','sn','so','social','sr','ss','st','su','sv','sx','sy','sz','tc','td','tel','tf','tg','th','tj','tk','tl','tm','tn','to','tp','tr','travel','tt','tv','tw','tz','ua','ug','uk','us','uy','uz','va','vc','ve','vg','vi','vn','vu','wf','win','ws','xn','xxx','xyz','ye','yt','yu','za','zm','zw')
allowed_tags = ('b','blockquote','br','code','del','em','h1','h2','h3','h4','h5','h6','hr','i','li','ol','p','pre','strong','sub','sup','table','tbody','th','thead','td','tr','ul','marquee','a','span','ruby','rp','rt','spoiler','img','lite-youtube','video','source')
def allowed_attributes(tag, name, value):
if name == 'style': return True
if tag == 'marquee':
if name in ['direction', 'behavior', 'scrollamount']: return True
if name in {'height', 'width'}:
try: value = int(value.replace('px', ''))
except: return False
if 0 < value <= 250: return True
return False
if tag == 'a':
if name == 'href': return True
if name == 'rel' and value == 'nofollow noopener noreferrer': return True
if name == 'target' and value == '_blank': return True
return False
if tag == 'img':
if name in ['src','data-src']:
if value.startswith('/') or value.startswith(f'{SITE_FULL}/') or embed_fullmatch_regex.fullmatch(value): return True
else: return False
if name == 'loading' and value == 'lazy': return True
if name == 'referrpolicy' and value == 'no-referrer': return True
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name in ['alt','title','g','b','pat']: return True
if name == 'class' and value == 'pat-hand': return True
return False
if tag == 'lite-youtube':
if name == 'params' and value.startswith('autoplay=1&modestbranding=1'): return True
if name == 'videoid': return True
return False
if tag == 'video':
if name == 'controls' and value == '': return True
if name == 'preload' and value == 'none': return True
return False
if tag == 'source':
if name == 'src' and embed_fullmatch_regex.fullmatch(value): return True
return False
if tag == 'p':
if name == 'class' and value == 'mb-0': return True
return False
if tag == 'span':
if name == 'class' and value in ['pat-container', 'pat-hand']: return True
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name == 'title': return True
if name == 'alt': return True
return False
url_re = build_url_re(tlds=TLDS, protocols=['http', 'https'])
def callback(attrs, new=False):
href = attrs[(None, "href")]
if not href.startswith('/') and not href.startswith(f'{SITE_FULL}/'):
attrs[(None, "target")] = "_blank"
attrs[(None, "rel")] = "nofollow noopener noreferrer"
return attrs
def handler(signum, frame):
print("Timeout!")
raise Exception("Timeout")
def render_emoji(html, regexp, edit, marseys_used=set(), b=False):
emojis = list(regexp.finditer(html))
captured = set()
for i in emojis:
if i.group(0) in captured: continue
captured.add(i.group(0))
emoji = i.group(1).lower()
attrs = ''
if b: attrs += ' b'
if not edit and len(emojis) <= 20 and random() < 0.0025 and ('marsey' in emoji or emoji in marseys_const2): attrs += ' g'
old = emoji
emoji = emoji.replace('!','').replace('#','')
if emoji == 'marseyrandom': emoji = choice(marseys_const2)
emoji_partial_pat = '<img loading="lazy" alt=":{0}:" src="{1}"{2}>'
emoji_partial = '<img loading="lazy" data-bs-toggle="tooltip" alt=":{0}:" title=":{0}:" src="{1}"{2}>'
emoji_html = None
if emoji.endswith('pat'):
if path.isfile(f"files/assets/images/emojis/{emoji.replace('pat','')}.webp"):
attrs += ' pat'
emoji_html = f'<span class="pat-container" data-bs-toggle="tooltip" alt=":{old}:" title=":{old}:"><img src="/assets/images/hand.webp" class="pat-hand">{emoji_partial_pat.format(old, f"/e/{emoji[:-3]}.webp", attrs)}</span>'
elif emoji.startswith('@'):
if u := get_user(emoji[1:-3], graceful=True):
attrs += ' pat'
emoji_html = f'<span class="pat-container" data-bs-toggle="tooltip" alt=":{old}:" title=":{old}:"><img src="/assets/images/hand.webp" class="pat-hand">{emoji_partial_pat.format(old, f"/pp/{u.id}", attrs)}</span>'
elif path.isfile(f'files/assets/images/emojis/{emoji}.webp'):
emoji_html = emoji_partial.format(old, f'/e/{emoji}.webp', attrs)
if emoji_html:
html = re.sub(f'(?<!"){i.group(0)}', emoji_html, html)
return html
def sanitize(sanitized, alert=False, comment=False, edit=False):
signal.signal(signal.SIGALRM, handler)
signal.alarm(1)
sanitized = linefeeds_regex.sub(r'\1\n\n\2', sanitized)
sanitized = image_regex.sub(r'\1![](\2)\4', sanitized)
sanitized = image_check_regex.sub(r'\1', sanitized)
sanitized = markdown(sanitized)
sanitized = strikethrough_regex.sub(r'<del>\1</del>', sanitized)
sanitized = sanitized.replace('','').replace('','').replace("\ufeff", "").replace("𒐪","")
if alert:
captured = []
for i in mention_regex2.finditer(sanitized):
if i.group(0) in captured: continue
captured.append(i.group(0))
u = get_user(i.group(1), graceful=True)
if u:
sanitized = sanitized.replace(i.group(0), f'''<p><a href="/id/{u.id}"><img loading="lazy" src="/pp/{u.id}">@{u.username}</a>''')
else:
sanitized = reddit_regex.sub(r'\1<a href="https://old.reddit.com/\2" rel="nofollow noopener noreferrer">/\2</a>', sanitized)
sanitized = sub_regex.sub(r'\1<a href="/\2">/\2</a>', sanitized)
captured = []
for i in mention_regex.finditer(sanitized):
if i.group(0) in captured: continue
captured.append(i.group(0))
u = get_user(i.group(2), graceful=True)
if u and (not (g.v and g.v.any_block_exists(u)) or g.v.admin_level > 1):
sanitized = sanitized.replace(i.group(0), f'''{i.group(1)}<a href="/id/{u.id}"><img loading="lazy" src="/pp/{u.id}">@{u.username}</a>''')
sanitized = imgur_regex.sub(r'\1_d.webp?maxwidth=9999&fidelity=high', sanitized)
soup = BeautifulSoup(sanitized, 'lxml')
for tag in soup.find_all("img"):
if tag.get("src") and not tag["src"].startswith('/pp/'):
tag["loading"] = "lazy"
tag["data-src"] = tag["src"]
tag["src"] = "/assets/images/loading.webp"
tag['alt'] = f'![]({tag["data-src"]})'
tag['referrerpolicy'] = "no-referrer"
for tag in soup.find_all("a"):
if tag.get("href") and fishylinks_regex.fullmatch(str(tag.string)):
tag.string = tag["href"]
sanitized = str(soup)
sanitized = spoiler_regex.sub(r'<spoiler>\1</spoiler>', sanitized)
marseys_used = set()
emojis = list(emoji_regex.finditer(sanitized))
if len(emojis) > 20: edit = True
captured = []
for i in emojis:
if i.group(0) in captured: continue
captured.append(i.group(0))
old = i.group(0)
if 'marseylong1' in old or 'marseylong2' in old or 'marseyllama1' in old or 'marseyllama2' in old: new = old.lower().replace(">", " class='mb-0'>")
else: new = old.lower()
new = render_emoji(new, emoji_regex2, edit, marseys_used, True)
sanitized = sanitized.replace(old, new)
emojis = list(emoji_regex2.finditer(sanitized))
if len(emojis) > 20: edit = True
sanitized = render_emoji(sanitized, emoji_regex2, edit, marseys_used)
for rd in ["://reddit.com", "://new.reddit.com", "://www.reddit.com", "://redd.it", "://libredd.it", "://teddit.net"]:
sanitized = sanitized.replace(rd, "://old.reddit.com")
sanitized = sanitized.replace("nitter.net", "twitter.com").replace("old.reddit.com/gallery", "reddit.com/gallery").replace("https://youtu.be/", "https://youtube.com/watch?v=").replace("https://music.youtube.com/watch?v=", "https://youtube.com/watch?v=").replace("https://streamable.com/", "https://streamable.com/e/").replace("https://youtube.com/shorts/", "https://youtube.com/watch?v=").replace("https://mobile.twitter", "https://twitter").replace("https://m.facebook", "https://facebook").replace("m.wikipedia.org", "wikipedia.org").replace("https://m.youtube", "https://youtube").replace("https://www.youtube", "https://youtube").replace("https://www.twitter", "https://twitter").replace("https://www.instagram", "https://instagram").replace("https://www.tiktok", "https://tiktok")
if "https://youtube.com/watch?v=" in sanitized: sanitized = sanitized.replace("?t=", "&t=")
captured = []
for i in youtube_regex.finditer(sanitized):
if i.group(0) in captured: continue
captured.append(i.group(0))
params = parse_qs(urlparse(i.group(2).replace('&amp;','&')).query)
t = params.get('t', params.get('start', [0]))[0]
if isinstance(t, str): t = t.replace('s','')
htmlsource = f'{i.group(1)}<lite-youtube videoid="{i.group(3)}" params="autoplay=1&modestbranding=1'
if t: htmlsource += f'&start={t}'
htmlsource += '"></lite-youtube>'
sanitized = sanitized.replace(i.group(0), htmlsource)
sanitized = video_sub_regex.sub(r'\1<video controls preload="none"><source src="\2"></video>', sanitized)
if comment:
for marsey in g.db.query(Marsey).filter(Marsey.name.in_(marseys_used)).all():
marsey.count += 1
g.db.add(marsey)
if '#fortune' in sanitized:
sanitized = sanitized.replace('#fortune', '')
sanitized += '\n\n<p>' + choice(FORTUNE_REPLIES) + '</p>'
sanitized = sanitized.replace('&amp;','&')
sanitized = utm_regex.sub('', sanitized)
sanitized = utm_regex2.sub('', sanitized)
sanitized = sanitized.replace('<html><body>','').replace('</body></html>','')
sanitized = bleach.Cleaner(tags=allowed_tags,
attributes=allowed_attributes,
protocols=['http', 'https'],
styles=['color', 'background-color', 'font-weight', 'text-align'],
filters=[partial(LinkifyFilter, skip_tags=["pre"], parse_email=False, callbacks=[callback], url_re=url_re)]
).clean(sanitized)
soup = BeautifulSoup(sanitized, 'lxml')
links = soup.find_all("a")
domain_list = set()
for link in links:
href = link.get("href")
if not href: continue
url = urlparse(href)
domain = url.netloc
url_path = url.path
domain_list.add(domain+url_path)
parts = domain.split(".")
for i in range(len(parts)):
new_domain = parts[i]
for j in range(i + 1, len(parts)):
new_domain += "." + parts[j]
domain_list.add(new_domain)
bans = g.db.query(BannedDomain.domain).filter(BannedDomain.domain.in_(list(domain_list))).all()
if bans: abort(403, description=f"Remove the banned domains {bans} and try again!")
signal.alarm(0)
return sanitized
def allowed_attributes_emojis(tag, name, value):
if tag == 'img':
if name == 'loading' and value == 'lazy': return True
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name in ['src','alt','title','g']: return True
return False
def filter_emojis_only(title, edit=False, graceful=False):
signal.signal(signal.SIGALRM, handler)
signal.alarm(1)
title = title.replace('','').replace('','').replace("\ufeff", "").replace("𒐪","").replace("\n", "").replace("\r", "").replace("\t", "").replace("&", "&amp;").replace('<','&lt;').replace('>','&gt;').replace('"', '&quot;').replace("'", "&#039;").strip()
title = render_emoji(title, emoji_regex3, edit)
title = strikethrough_regex.sub(r'<del>\1</del>', title)
sanitized = bleach.clean(title, tags=['img','del'], attributes=allowed_attributes_emojis, protocols=['http','https'])
signal.alarm(0)
if len(title) > 1500 and not graceful: abort(400)
else: return title
import bleach
from bs4 import BeautifulSoup
from bleach.linkifier import LinkifyFilter, build_url_re
from functools import partial
from .get import *
from os import path, environ
import re
from mistletoe import markdown
from json import loads, dump
from random import random, choice
import signal
import time
import requests
TLDS = ('ac','ad','ae','aero','af','ag','ai','al','am','an','ao','aq','ar','arpa','as','asia','at','au','aw','ax','az','ba','bb','bd','be','bf','bg','bh','bi','biz','bj','bm','bn','bo','br','bs','bt','bv','bw','by','bz','ca','cafe','cat','cc','cd','cf','cg','ch','ci','ck','cl','club','cm','cn','co','com','coop','cr','cu','cv','cx','cy','cz','de','dj','dk','dm','do','dz','ec','edu','ee','eg','er','es','et','eu','fi','fj','fk','fm','fo','fr','ga','gb','gd','ge','gf','gg','gh','gi','gl','gm','gn','gov','gp','gq','gr','gs','gt','gu','gw','gy','hk','hm','hn','hr','ht','hu','id','ie','il','im','in','info','int','io','iq','ir','is','it','je','jm','jo','jobs','jp','ke','kg','kh','ki','km','kn','kp','kr','kw','ky','kz','la','lb','lc','li','lk','lr','ls','lt','lu','lv','ly','ma','mc','md','me','mg','mh','mil','mk','ml','mm','mn','mo','mobi','mp','mq','mr','ms','mt','mu','museum','mv','mw','mx','my','mz','na','name','nc','ne','net','nf','ng','ni','nl','no','np','nr','nu','nz','om','org','pa','pe','pf','pg','ph','pk','pl','pm','pn','post','pr','pro','ps','pt','pw','py','qa','re','ro','rs','ru','rw','sa','sb','sc','sd','se','sg','sh','si','sj','sk','sl','sm','sn','so','social','sr','ss','st','su','sv','sx','sy','sz','tc','td','tel','tf','tg','th','tj','tk','tl','tm','tn','to','tp','tr','travel','tt','tv','tw','tz','ua','ug','uk','us','uy','uz','va','vc','ve','vg','vi','vn','vu','wf','win','ws','xn','xxx','xyz','ye','yt','yu','za','zm','zw')
allowed_tags = ('b','blockquote','br','code','del','em','h1','h2','h3','h4','h5','h6','hr','i','li','ol','p','pre','strong','sub','sup','table','tbody','th','thead','td','tr','ul','marquee','a','span','ruby','rp','rt','spoiler','img','lite-youtube','video','source','audio')
def allowed_attributes(tag, name, value):
if name == 'style': return True
if tag == 'marquee':
if name in ['direction', 'behavior', 'scrollamount']: return True
if name in {'height', 'width'}:
try: value = int(value.replace('px', ''))
except: return False
if 0 < value <= 250: return True
return False
if tag == 'a':
if name == 'href': return True
if name == 'rel' and value == 'nofollow noopener noreferrer': return True
if name == 'target' and value == '_blank': return True
return False
if tag == 'img':
if name in ['src','data-src']:
if value.startswith('/') or value.startswith(f'{SITE_FULL}/') or embed_fullmatch_regex.fullmatch(value): return True
else: return False
if name == 'loading' and value == 'lazy': return True
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name in ['g','b'] and not value: return True
if name in ['alt','title']: return True
if name == 'referrpolicy' and value == 'no-referrer': return True
return False
if tag == 'lite-youtube':
if name == 'params' and value.startswith('autoplay=1&modestbranding=1'): return True
if name == 'videoid': return True
return False
if tag == 'video':
if name == 'controls' and value == '': return True
if name == 'preload' and value == 'none': return True
return False
if tag == 'source':
if name == 'src' and embed_fullmatch_regex.fullmatch(value): return True
return False
if tag == 'audio':
if name == 'controls' and value == '': return True
if name == 'preload' and value == 'none': return True
if name == 'src' and embed_fullmatch_regex.fullmatch(value): return True
return False
if tag == 'p':
if name == 'class' and value == 'mb-0': return True
return False
if tag == 'span':
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name == 'title': return True
if name == 'alt': return True
return False
url_re = build_url_re(tlds=TLDS, protocols=['http', 'https'])
def callback(attrs, new=False):
if (None, "href") not in attrs:
return # Incorrect <a> tag
href = attrs[(None, "href")]
# \ in href right after / makes most browsers ditch site hostname and allows for a host injection bypassing the check, see <a href="/\google.com">cool</a>
if "\\" in href:
attrs["_text"] = href # Laugh at this user
del attrs[(None, "href")] # Make unclickable and reset harmful payload
return attrs
if not href.startswith('/') and not href.startswith(f'{SITE_FULL}/'):
attrs[(None, "target")] = "_blank"
attrs[(None, "rel")] = "nofollow noopener noreferrer"
return attrs
def handler(signum, frame):
print("Timeout!")
raise Exception("Timeout")
def render_emoji(html, regexp, edit, marseys_used=set(), b=False):
emojis = list(regexp.finditer(html))
captured = set()
for i in emojis:
if i.group(0) in captured: continue
captured.add(i.group(0))
emoji = i.group(1).lower()
attrs = ''
if b: attrs += ' b'
if not edit and len(emojis) <= 20 and random() < 0.0025 and ('marsey' in emoji or emoji in marseys_const2): attrs += ' g'
old = emoji
emoji = emoji.replace('!','').replace('#','')
if emoji == 'marseyrandom': emoji = choice(marseys_const2)
emoji_partial_pat = '<img loading="lazy" alt=":{0}:" src="{1}"{2}>'
emoji_partial = '<img loading="lazy" data-bs-toggle="tooltip" alt=":{0}:" title=":{0}:" src="{1}"{2}>'
emoji_html = None
if emoji.endswith('pat'):
if path.isfile(f"files/assets/images/emojis/{emoji.replace('pat','')}.webp"):
emoji_html = f'<span data-bs-toggle="tooltip" alt=":{old}:" title=":{old}:"><img src="/assets/images/hand.webp">{emoji_partial_pat.format(old, f"/e/{emoji[:-3]}.webp", attrs)}</span>'
elif emoji.startswith('@'):
if u := get_user(emoji[1:-3], graceful=True):
emoji_html = f'<span data-bs-toggle="tooltip" alt=":{old}:" title=":{old}:"><img src="/assets/images/hand.webp">{emoji_partial_pat.format(old, f"/pp/{u.id}", attrs)}</span>'
elif path.isfile(f'files/assets/images/emojis/{emoji}.webp'):
emoji_html = emoji_partial.format(old, f'/e/{emoji}.webp', attrs)
if emoji_html:
marseys_used.add(emoji)
html = re.sub(f'(?<!"){i.group(0)}', emoji_html, html)
return html
def sanitize(sanitized, alert=False, comment=False, edit=False):
signal.signal(signal.SIGALRM, handler)
signal.alarm(1)
if not sanitized.startswith('<pre>'):
sanitized = linefeeds_regex.sub(r'\1\n\n\2', sanitized)
sanitized = image_regex.sub(r'\1![](\2)\4', sanitized)
sanitized = image_check_regex.sub(r'\1', sanitized)
sanitized = link_fix_regex.sub(r'\2https://\3', sanitized)
sanitized = markdown(sanitized)
sanitized = strikethrough_regex.sub(r'<del>\1</del>', sanitized)
sanitized = sanitized.replace('','').replace('','').replace("\ufeff", "").replace("𒐪","")
if alert:
captured = []
for i in mention_regex2.finditer(sanitized):
if i.group(0) in captured: continue
captured.append(i.group(0))
u = get_user(i.group(1), graceful=True)
if u:
sanitized = sanitized.replace(i.group(0), f'''<p><a href="/id/{u.id}"><img loading="lazy" src="/pp/{u.id}">@{u.username}</a>''')
else:
sanitized = reddit_regex.sub(r'\1<a href="https://old.reddit.com/\2" rel="nofollow noopener noreferrer">/\2</a>', sanitized)
sanitized = sub_regex.sub(r'\1<a href="/\2">/\2</a>', sanitized)
captured = []
for i in mention_regex.finditer(sanitized):
if i.group(0) in captured: continue
captured.append(i.group(0))
u = get_user(i.group(2), graceful=True)
if u and (not (g.v and g.v.any_block_exists(u)) or g.v.admin_level > 1):
sanitized = sanitized.replace(i.group(0), f'''{i.group(1)}<a href="/id/{u.id}"><img loading="lazy" src="/pp/{u.id}">@{u.username}</a>''')
sanitized = imgur_regex.sub(r'\1_d.webp?maxwidth=9999&fidelity=high', sanitized)
soup = BeautifulSoup(sanitized, 'lxml')
for tag in soup.find_all("img"):
if tag.get("src") and not tag["src"].startswith('/pp/'):
tag["loading"] = "lazy"
tag["data-src"] = tag["src"]
tag["src"] = "/assets/images/loading.webp"
tag['alt'] = f'![]({tag["data-src"]})'
tag['referrerpolicy'] = "no-referrer"
for tag in soup.find_all("a"):
if tag.get("href") and fishylinks_regex.fullmatch(str(tag.string)):
tag.string = tag["href"]
sanitized = str(soup)
sanitized = spoiler_regex.sub(r'<spoiler>\1</spoiler>', sanitized)
marseys_used = set()
emojis = list(emoji_regex.finditer(sanitized))
if len(emojis) > 20: edit = True
captured = []
for i in emojis:
if i.group(0) in captured: continue
captured.append(i.group(0))
old = i.group(0)
if 'marseylong1' in old or 'marseylong2' in old or 'marseyllama1' in old or 'marseyllama2' in old: new = old.lower().replace(">", " class='mb-0'>")
else: new = old.lower()
new = render_emoji(new, emoji_regex2, edit, marseys_used, True)
sanitized = sanitized.replace(old, new)
emojis = list(emoji_regex2.finditer(sanitized))
if len(emojis) > 20: edit = True
sanitized = render_emoji(sanitized, emoji_regex2, edit, marseys_used)
for rd in ["://reddit.com", "://new.reddit.com", "://www.reddit.com", "://redd.it", "://libredd.it", "://teddit.net"]:
sanitized = sanitized.replace(rd, "://old.reddit.com")
sanitized = sanitized.replace("nitter.net", "twitter.com").replace("old.reddit.com/gallery", "reddit.com/gallery").replace("https://youtu.be/", "https://youtube.com/watch?v=").replace("https://music.youtube.com/watch?v=", "https://youtube.com/watch?v=").replace("https://streamable.com/", "https://streamable.com/e/").replace("https://youtube.com/shorts/", "https://youtube.com/watch?v=").replace("https://mobile.twitter", "https://twitter").replace("https://m.facebook", "https://facebook").replace("m.wikipedia.org", "wikipedia.org").replace("https://m.youtube", "https://youtube").replace("https://www.youtube", "https://youtube").replace("https://www.twitter", "https://twitter").replace("https://www.instagram", "https://instagram").replace("https://www.tiktok", "https://tiktok")
if "https://youtube.com/watch?v=" in sanitized: sanitized = sanitized.replace("?t=", "&t=")
captured = []
for i in youtube_regex.finditer(sanitized):
if i.group(0) in captured: continue
captured.append(i.group(0))
params = parse_qs(urlparse(i.group(2).replace('&amp;','&')).query)
t = params.get('t', params.get('start', [0]))[0]
if isinstance(t, str): t = t.replace('s','')
htmlsource = f'{i.group(1)}<lite-youtube videoid="{i.group(3)}" params="autoplay=1&modestbranding=1'
if t: htmlsource += f'&start={t}'
htmlsource += '"></lite-youtube>'
sanitized = sanitized.replace(i.group(0), htmlsource)
sanitized = video_sub_regex.sub(r'\1<video controls preload="none"><source src="\2"></video>', sanitized)
if comment:
for marsey in g.db.query(Marsey).filter(Marsey.name.in_(marseys_used)).all():
marsey.count += 1
g.db.add(marsey)
if '#fortune' in sanitized:
sanitized = sanitized.replace('#fortune', '')
sanitized += '\n\n<p>' + choice(FORTUNE_REPLIES) + '</p>'
if '#factcheck' in sanitized:
sanitized = sanitized.replace('#factcheck', '')
sanitized += '\n\n<p>' + choice(FACTCHECK_REPLIES) + '</p>'
sanitized = sanitized.replace('<p></p>', '')
sanitized = sanitized.replace('&amp;','&')
sanitized = utm_regex.sub('', sanitized)
sanitized = utm_regex2.sub('', sanitized)
sanitized = sanitized.replace('<html><body>','').replace('</body></html>','')
sanitized = bleach.Cleaner(tags=allowed_tags,
attributes=allowed_attributes,
protocols=['http', 'https'],
styles=['color', 'background-color', 'font-weight', 'text-align'],
filters=[partial(LinkifyFilter, skip_tags=["pre"], parse_email=False, callbacks=[callback], url_re=url_re)]
).clean(sanitized)
soup = BeautifulSoup(sanitized, 'lxml')
links = soup.find_all("a")
domain_list = set()
for link in links:
href = link.get("href")
if not href: continue
url = urlparse(href)
domain = url.netloc
url_path = url.path
domain_list.add(domain+url_path)
parts = domain.split(".")
for i in range(len(parts)):
new_domain = parts[i]
for j in range(i + 1, len(parts)):
new_domain += "." + parts[j]
domain_list.add(new_domain)
bans = g.db.query(BannedDomain.domain).filter(BannedDomain.domain.in_(list(domain_list))).all()
if bans: abort(403, description=f"Remove the banned domains {bans} and try again!")
signal.alarm(0)
return sanitized
def allowed_attributes_emojis(tag, name, value):
if tag == 'img':
if name == 'src' and value.startswith('/'): return True
if name == 'loading' and value == 'lazy': return True
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name == 'g' and not value: return True
if name in ['alt','title']: return True
if tag == 'span':
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name == 'title': return True
if name == 'alt': return True
return False
return False
def filter_emojis_only(title, edit=False, graceful=False):
signal.signal(signal.SIGALRM, handler)
signal.alarm(1)
title = title.replace('','').replace('','').replace("\ufeff", "").replace("𒐪","").replace("\n", "").replace("\r", "").replace("\t", "").replace("&", "&amp;").replace('<','&lt;').replace('>','&gt;').replace('"', '&quot;').replace("'", "&#039;").strip()
title = render_emoji(title, emoji_regex3, edit)
title = strikethrough_regex.sub(r'<del>\1</del>', title)
title = bleach.clean(title, tags=['img','del','span'], attributes=allowed_attributes_emojis, protocols=['http','https'])
signal.alarm(0)
if len(title) > 1500 and not graceful: abort(400)
else: return title

View File

@ -1,23 +1,23 @@
from werkzeug.security import *
from os import environ
def generate_hash(string):
msg = bytes(string, "utf-16")
return hmac.new(key=bytes(environ.get("MASTER_KEY"), "utf-16"),
msg=msg,
digestmod='md5'
).hexdigest()
def validate_hash(string, hashstr):
return hmac.compare_digest(hashstr, generate_hash(string))
def hash_password(password):
return generate_password_hash(
password, method='pbkdf2:sha512', salt_length=8)
from werkzeug.security import *
from os import environ
def generate_hash(string):
msg = bytes(string, "utf-16")
return hmac.new(key=bytes(environ.get("MASTER_KEY"), "utf-16"),
msg=msg,
digestmod='md5'
).hexdigest()
def validate_hash(string, hashstr):
return hmac.compare_digest(hashstr, generate_hash(string))
def hash_password(password):
return generate_password_hash(
password, method='pbkdf2:sha512', salt_length=8)

View File

@ -1,118 +1,114 @@
from .get import *
from .alerts import *
from files.helpers.const import *
from files.__main__ import db_session
from random import randint
def get_logged_in_user():
if not (hasattr(g, 'db') and g.db): g.db = db_session()
v = None
token = request.headers.get("Authorization","").strip()
if token:
client = g.db.query(ClientAuth).filter(ClientAuth.access_token == token).one_or_none()
if client:
v = client.user
v.client = client
else:
lo_user = session.get("lo_user")
if lo_user:
id = int(lo_user)
v = g.db.query(User).filter_by(id=id).one_or_none()
if v:
nonce = session.get("login_nonce", 0)
if nonce < v.login_nonce or v.id != id: abort(401)
if request.method != "GET":
submitted_key = request.values.get("formkey")
if not submitted_key: abort(401)
if not v.validate_formkey(submitted_key): abort(401)
v.client = None
if request.method.lower() != "get" and app.config['SETTINGS']['Read-only mode'] and not (v and v.admin_level):
abort(403)
if v and v.patron:
if request.content_length and request.content_length > 16 * 1024 * 1024: abort(413)
elif request.content_length and request.content_length > 8 * 1024 * 1024: abort(413)
return v
def check_ban_evade(v):
if v and not v.patron and v.admin_level < 2 and v.ban_evade and not v.unban_utc:
v.shadowbanned = "AutoJanny"
g.db.add(v)
g.db.commit()
def auth_desired(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
check_ban_evade(v)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
return wrapper
def auth_required(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
check_ban_evade(v)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
return wrapper
def is_not_permabanned(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
check_ban_evade(v)
if v.is_banned and v.unban_utc == 0:
return {"error": "Interal server error"}, 500
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
return wrapper
def admin_level_required(x):
def wrapper_maker(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
if v.admin_level < x: abort(403)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
return wrapper
from .get import *
from .alerts import *
from files.helpers.const import *
from files.__main__ import db_session
from random import randint
def get_logged_in_user():
if not (hasattr(g, 'db') and g.db): g.db = db_session()
v = None
token = request.headers.get("Authorization","").strip()
if token:
client = g.db.query(ClientAuth).filter(ClientAuth.access_token == token).one_or_none()
if client:
v = client.user
v.client = client
else:
lo_user = session.get("lo_user")
if lo_user:
id = int(lo_user)
v = g.db.query(User).filter_by(id=id).one_or_none()
if v:
nonce = session.get("login_nonce", 0)
if nonce < v.login_nonce or v.id != id: abort(401)
if request.method != "GET":
submitted_key = request.values.get("formkey")
if not submitted_key: abort(401)
if not v.validate_formkey(submitted_key): abort(401)
v.client = None
if request.method.lower() != "get" and app.config['SETTINGS']['Read-only mode'] and not (v and v.admin_level):
abort(403)
return v
def check_ban_evade(v):
if v and not v.patron and v.admin_level < 2 and v.ban_evade and not v.unban_utc:
v.shadowbanned = "AutoJanny"
g.db.add(v)
g.db.commit()
def auth_desired(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
check_ban_evade(v)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
return wrapper
def auth_required(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
check_ban_evade(v)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
return wrapper
def is_not_permabanned(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
check_ban_evade(v)
if v.is_banned and v.unban_utc == 0:
return {"error": "Interal server error"}, 500
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
return wrapper
def admin_level_required(x):
def wrapper_maker(f):
def wrapper(*args, **kwargs):
v = get_logged_in_user()
if not v: abort(401)
if v.admin_level < x: abort(403)
g.v = v
return make_response(f(*args, v=v, **kwargs))
wrapper.__name__ = f.__name__
return wrapper
return wrapper_maker

View File

@ -1,94 +1,94 @@
from os import environ
import time
from flask import *
from urllib.parse import quote
from files.helpers.security import *
from files.helpers.wrappers import *
from files.helpers.const import *
from files.classes import *
from files.__main__ import app, mail, limiter
from flask_mail import Message
name = environ.get("SITE_NAME").strip()
def send_mail(to_address, subject, html):
msg = Message(html=html, subject=subject, sender=f"{name}@{SITE}", recipients=[to_address])
mail.send(msg)
def send_verification_email(user, email=None):
if not email:
email = user.email
url = f"https://{app.config['SERVER_NAME']}/activate"
now = int(time.time())
token = generate_hash(f"{email}+{user.id}+{now}")
params = f"?email={quote(email)}&id={user.id}&time={now}&token={token}"
link = url + params
send_mail(to_address=email,
html=render_template("email/email_verify.html",
action_url=link,
v=user),
subject=f"Validate your {name} account email."
)
@app.post("/verify_email")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def api_verify_email(v):
send_verification_email(v)
return {"message": "Email has been sent (ETA ~5 minutes)"}
@app.get("/activate")
@auth_required
def activate(v):
email = request.values.get("email", "").strip().lower()
if not email_regex.fullmatch(email):
return render_template("message.html", v=v, title="Invalid email.", error="Invalid email."), 400
id = request.values.get("id", "").strip()
timestamp = int(request.values.get("time", "0"))
token = request.values.get("token", "").strip()
if int(time.time()) - timestamp > 3600:
return render_template("message.html", v=v, title="Verification link expired.",
message="That link has expired. Visit your settings to send yourself another verification email."), 410
if not validate_hash(f"{email}+{id}+{timestamp}", token):
abort(403)
user = g.db.query(User).filter_by(id=id).one_or_none()
if not user:
abort(404)
if user.is_activated and user.email == email:
return render_template("message_success.html", v=v, title="Email already verified.", message="Email already verified."), 404
user.email = email
user.is_activated = True
if not any(b.badge_id == 2 for b in user.badges):
mail_badge = Badge(user_id=user.id, badge_id=2)
g.db.add(mail_badge)
g.db.flush()
send_notification(user.id, f"@AutoJanny has given you the following profile badge:\n\n![]({mail_badge.path})\n\n{mail_badge.name}")
g.db.add(user)
g.db.commit()
return render_template("message_success.html", v=v, title="Email verified.", message=f"Your email {email} has been verified. Thank you.")
from os import environ
import time
from flask import *
from urllib.parse import quote
from files.helpers.security import *
from files.helpers.wrappers import *
from files.helpers.const import *
from files.classes import *
from files.__main__ import app, mail, limiter
from flask_mail import Message
name = environ.get("SITE_NAME").strip()
def send_mail(to_address, subject, html):
msg = Message(html=html, subject=subject, sender=f"{name}@{SITE}", recipients=[to_address])
mail.send(msg)
def send_verification_email(user, email=None):
if not email:
email = user.email
url = f"https://{app.config['SERVER_NAME']}/activate"
now = int(time.time())
token = generate_hash(f"{email}+{user.id}+{now}")
params = f"?email={quote(email)}&id={user.id}&time={now}&token={token}"
link = url + params
send_mail(to_address=email,
html=render_template("email/email_verify.html",
action_url=link,
v=user),
subject=f"Validate your {name} account email."
)
@app.post("/verify_email")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def api_verify_email(v):
send_verification_email(v)
return {"message": "Email has been sent (ETA ~5 minutes)"}
@app.get("/activate")
@auth_required
def activate(v):
email = request.values.get("email", "").strip().lower()
if not email_regex.fullmatch(email):
return render_template("message.html", v=v, title="Invalid email.", error="Invalid email."), 400
id = request.values.get("id", "").strip()
timestamp = int(request.values.get("time", "0"))
token = request.values.get("token", "").strip()
if int(time.time()) - timestamp > 3600:
return render_template("message.html", v=v, title="Verification link expired.",
message="That link has expired. Visit your settings to send yourself another verification email."), 410
if not validate_hash(f"{email}+{id}+{timestamp}", token):
abort(403)
user = g.db.query(User).filter_by(id=id).one_or_none()
if not user:
abort(404)
if user.is_activated and user.email == email:
return render_template("message_success.html", v=v, title="Email already verified.", message="Email already verified."), 404
user.email = email
user.is_activated = True
if not any(b.badge_id == 2 for b in user.badges):
mail_badge = Badge(user_id=user.id, badge_id=2)
g.db.add(mail_badge)
g.db.flush()
send_notification(user.id, f"@AutoJanny has given you the following profile badge:\n\n![]({mail_badge.path})\n\n{mail_badge.name}")
g.db.add(user)
g.db.commit()
return render_template("message_success.html", v=v, title="Email verified.", message=f"Your email {email} has been verified. Thank you.")

View File

@ -1,18 +1,18 @@
from .admin import *
from .comments import *
from .discord import *
from .errors import *
from .reporting import *
from .front import *
from .login import *
from .oauth import *
from .posts import *
from .search import *
from .settings import *
from .static import *
from .users import *
from .votes import *
from .feeds import *
from .awards import *
from .giphy import *
from .admin import *
from .comments import *
from .discord import *
from .errors import *
from .reporting import *
from .front import *
from .login import *
from .oauth import *
from .posts import *
from .search import *
from .settings import *
from .static import *
from .users import *
from .votes import *
from .feeds import *
from .awards import *
from .giphy import *
from .subs import *

File diff suppressed because it is too large Load Diff

View File

@ -81,6 +81,7 @@ def buy(v, award):
send_notification(v.id, f"@AutoJanny has given you the following profile badge:\n\n![]({new_badge.path})\n\n{new_badge.name}")
g.db.add(v)
if award == "lootbox":
lootbox_items = []
for i in [1,2,3,4,5]:
@ -93,7 +94,7 @@ def buy(v, award):
v.lootboxes_bought += 1
lootbox_msg = "You open your lootbox and receive: " + ', '.join(lootbox_items)
send_repeatable_notification(v.id, lootbox_msg)
if v.lootboxes_bought == 10 and not v.has_badge(76):
new_badge = Badge(badge_id=76, user_id=v.id)
g.db.add(new_badge)
@ -351,6 +352,8 @@ def award_post(pid, v):
g.db.add(badge)
g.db.flush()
send_notification(author.id, f"@AutoJanny has given you the following profile badge:\n\n![]({badge.path})\n\n{badge.name}")
elif kind == "checkmark":
author.verified = "Verified"
if author.received_award_count: author.received_award_count += 1
else: author.received_award_count = 1
@ -590,6 +593,8 @@ def award_comment(cid, v):
g.db.add(badge)
g.db.flush()
send_notification(author.id, f"@AutoJanny has given you the following profile badge:\n\n![]({badge.path})\n\n{badge.name}")
elif kind == "checkmark":
author.verified = "Verified"
if author.received_award_count: author.received_award_count += 1
else: author.received_award_count = 1

File diff suppressed because it is too large Load Diff

View File

@ -1,141 +1,141 @@
from files.helpers.wrappers import *
from files.helpers.security import *
from files.helpers.discord import add_role
from files.__main__ import app
import requests
SERVER_ID = environ.get("DISCORD_SERVER_ID",'').strip()
CLIENT_ID = environ.get("DISCORD_CLIENT_ID",'').strip()
CLIENT_SECRET = environ.get("DISCORD_CLIENT_SECRET",'').strip()
BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN").strip()
DISCORD_ENDPOINT = "https://discordapp.com/api/v6"
WELCOME_CHANNEL="846509313941700618"
@app.get("/discord")
@is_not_permabanned
def join_discord(v):
if v.shadowbanned: return {"error": "Internal server error"}
if SITE_NAME == 'rDrama' and v.admin_level < 2 and v.patron == 0 and v.truecoins < 150:
return "You must receive 150 upvotes/downvotes from other users before being able to join the Discord server."
now=int(time.time())
state=generate_hash(f"{now}+{v.id}+discord")
state=f"{now}.{state}"
return redirect(f"https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&redirect_uri=https%3A%2F%2F{app.config['SERVER_NAME']}%2Fdiscord_redirect&response_type=code&scope=identify%20guilds.join&state={state}")
@app.get("/discord_redirect")
@auth_required
def discord_redirect(v):
now=int(time.time())
state=request.values.get('state','').split('.')
timestamp=state[0]
state=state[1]
if int(timestamp) < now-600:
abort(400)
if not validate_hash(f"{timestamp}+{v.id}+discord", state):
abort(400)
code = request.values.get("code","")
if not code:
abort(400)
data={
"client_id":CLIENT_ID,
'client_secret': CLIENT_SECRET,
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': f"https://{app.config['SERVER_NAME']}/discord_redirect",
'scope': 'identify guilds.join'
}
headers={
'Content-Type': 'application/x-www-form-urlencoded'
}
url="https://discord.com/api/oauth2/token"
x=requests.post(url, headers=headers, data=data, timeout=5)
x=x.json()
token=x["access_token"]
url="https://discord.com/api/users/@me"
headers={
'Authorization': f"Bearer {token}"
}
x=requests.get(url, headers=headers, timeout=5)
x=x.json()
headers={
'Authorization': f"Bot {BOT_TOKEN}",
'Content-Type': "application/json"
}
if v.discord_id and v.discord_id != x['id']:
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{v.discord_id}"
requests.delete(url, headers=headers, timeout=5)
if g.db.query(User).filter(User.id!=v.id, User.discord_id==x["id"]).one_or_none():
return render_template("message.html", title="Discord account already linked.", error="That Discord account is already in use by another user.", v=v)
v.discord_id=x["id"]
g.db.add(v)
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{x['id']}"
name=v.username
data={
"access_token":token,
"nick":name,
}
x=requests.put(url, headers=headers, json=data, timeout=5)
if x.status_code in {201, 204}:
if v.admin_level > 2:
add_role(v, "owner")
time.sleep(0.1)
if v.admin_level > 1: add_role(v, "admin")
time.sleep(0.1)
add_role(v, "linked")
if v.patron:
time.sleep(0.1)
add_role(v, str(v.patron))
else:
return x.json()
if x.status_code==204:
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{v.discord_id}"
data={
"nick": name
}
requests.patch(url, headers=headers, json=data, timeout=5)
g.db.commit()
from files.helpers.wrappers import *
from files.helpers.security import *
from files.helpers.discord import add_role
from files.__main__ import app
import requests
SERVER_ID = environ.get("DISCORD_SERVER_ID",'').strip()
CLIENT_ID = environ.get("DISCORD_CLIENT_ID",'').strip()
CLIENT_SECRET = environ.get("DISCORD_CLIENT_SECRET",'').strip()
BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN").strip()
DISCORD_ENDPOINT = "https://discordapp.com/api/v6"
WELCOME_CHANNEL="846509313941700618"
@app.get("/discord")
@is_not_permabanned
def join_discord(v):
if v.shadowbanned: return {"error": "Internal server error"}
if SITE_NAME == 'rDrama' and v.admin_level < 2 and v.patron == 0 and v.truecoins < 150:
return "You must receive 150 upvotes/downvotes from other users before being able to join the Discord server."
now=int(time.time())
state=generate_hash(f"{now}+{v.id}+discord")
state=f"{now}.{state}"
return redirect(f"https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&redirect_uri=https%3A%2F%2F{app.config['SERVER_NAME']}%2Fdiscord_redirect&response_type=code&scope=identify%20guilds.join&state={state}")
@app.get("/discord_redirect")
@auth_required
def discord_redirect(v):
now=int(time.time())
state=request.values.get('state','').split('.')
timestamp=state[0]
state=state[1]
if int(timestamp) < now-600:
abort(400)
if not validate_hash(f"{timestamp}+{v.id}+discord", state):
abort(400)
code = request.values.get("code","")
if not code:
abort(400)
data={
"client_id":CLIENT_ID,
'client_secret': CLIENT_SECRET,
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': f"https://{app.config['SERVER_NAME']}/discord_redirect",
'scope': 'identify guilds.join'
}
headers={
'Content-Type': 'application/x-www-form-urlencoded'
}
url="https://discord.com/api/oauth2/token"
x=requests.post(url, headers=headers, data=data, timeout=5)
x=x.json()
token=x["access_token"]
url="https://discord.com/api/users/@me"
headers={
'Authorization': f"Bearer {token}"
}
x=requests.get(url, headers=headers, timeout=5)
x=x.json()
headers={
'Authorization': f"Bot {BOT_TOKEN}",
'Content-Type': "application/json"
}
if v.discord_id and v.discord_id != x['id']:
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{v.discord_id}"
requests.delete(url, headers=headers, timeout=5)
if g.db.query(User).filter(User.id!=v.id, User.discord_id==x["id"]).one_or_none():
return render_template("message.html", title="Discord account already linked.", error="That Discord account is already in use by another user.", v=v)
v.discord_id=x["id"]
g.db.add(v)
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{x['id']}"
name=v.username
data={
"access_token":token,
"nick":name,
}
x=requests.put(url, headers=headers, json=data, timeout=5)
if x.status_code in {201, 204}:
if v.admin_level > 2:
add_role(v, "owner")
time.sleep(0.1)
if v.admin_level > 1: add_role(v, "admin")
time.sleep(0.1)
add_role(v, "linked")
if v.patron:
time.sleep(0.1)
add_role(v, str(v.patron))
else:
return x.json()
if x.status_code==204:
url=f"https://discord.com/api/guilds/{SERVER_ID}/members/{v.discord_id}"
data={
"nick": name
}
requests.patch(url, headers=headers, json=data, timeout=5)
g.db.commit()
return redirect(f"https://discord.com/channels/{SERVER_ID}/{WELCOME_CHANNEL}")

View File

@ -1,76 +1,76 @@
from files.helpers.wrappers import *
from flask import *
from urllib.parse import quote, urlencode
import time
from files.__main__ import app, limiter
@app.errorhandler(400)
def error_400(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "400 Bad Request"}, 400
else: return render_template('errors/400.html', err=True), 400
@app.errorhandler(401)
def error_401(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "401 Not Authorized"}, 401
else:
path = request.path
qs = urlencode(dict(request.values))
argval = quote(f"{path}?{qs}", safe='')
return redirect(f"/login?redirect={argval}")
@app.errorhandler(403)
def error_403(e):
description = e.description
if description == "You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.": description = ''
if request.headers.get("Authorization") or request.headers.get("xhr"):
if not description: description = "403 Forbidden"
return {"error": description}, 403
else:
if not description: description = "YOU AREN'T WELCOME HERE GO AWAY"
return render_template('errors/403.html', description=description, err=True), 403
@app.errorhandler(404)
def error_404(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "404 Not Found"}, 404
else: return render_template('errors/404.html', err=True), 404
@app.errorhandler(405)
def error_405(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "405 Method Not Allowed"}, 405
else: return render_template('errors/405.html', err=True), 405
@app.errorhandler(413)
def error_413(e):
return {"error": "Max file size is 8 MB (16 MB for paypigs)"}, 413
if request.headers.get("Authorization") or request.headers.get("xhr"):
return {"error": "Max file size is 8 MB (16 MB for paypigs)"}, 413
else: return render_template('errors/413.html', err=True), 413
@app.errorhandler(429)
def error_429(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "429 Too Many Requests"}, 429
else: return render_template('errors/429.html', err=True), 429
@app.errorhandler(500)
def error_500(e):
g.db.rollback()
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "500 Internal Server Error"}, 500
else: return render_template('errors/500.html', err=True), 500
@app.post("/allow_nsfw")
def allow_nsfw():
session["over_18"] = int(time.time()) + 3600
redir = request.values.get("redir")
if redir:
if redir.startswith(f'{SITE_FULL}/'): return redirect(redir)
if redir.startswith('/'): return redirect(f'{SITE_FULL}{redir}')
from files.helpers.wrappers import *
from flask import *
from urllib.parse import quote, urlencode
import time
from files.__main__ import app, limiter
@app.errorhandler(400)
def error_400(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "400 Bad Request"}, 400
else: return render_template('errors/400.html', err=True), 400
@app.errorhandler(401)
def error_401(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "401 Not Authorized"}, 401
else:
path = request.path
qs = urlencode(dict(request.values))
argval = quote(f"{path}?{qs}", safe='')
return redirect(f"/login?redirect={argval}")
@app.errorhandler(403)
def error_403(e):
description = e.description
if description == "You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.": description = ''
if request.headers.get("Authorization") or request.headers.get("xhr"):
if not description: description = "403 Forbidden"
return {"error": description}, 403
else:
if not description: description = "YOU AREN'T WELCOME HERE GO AWAY"
return render_template('errors/403.html', description=description, err=True), 403
@app.errorhandler(404)
def error_404(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "404 Not Found"}, 404
else: return render_template('errors/404.html', err=True), 404
@app.errorhandler(405)
def error_405(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "405 Method Not Allowed"}, 405
else: return render_template('errors/405.html', err=True), 405
@app.errorhandler(413)
def error_413(e):
return {"error": "Max image size is 8 MB (16 MB for paypigs)"}, 413
if request.headers.get("Authorization") or request.headers.get("xhr"):
return {"error": "Max image size is 8 MB (16 MB for paypigs)"}, 413
else: return render_template('errors/413.html', err=True), 413
@app.errorhandler(429)
def error_429(e):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "429 Too Many Requests"}, 429
else: return render_template('errors/429.html', err=True), 429
@app.errorhandler(500)
def error_500(e):
g.db.rollback()
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "500 Internal Server Error"}, 500
else: return render_template('errors/500.html', err=True), 500
@app.post("/allow_nsfw")
def allow_nsfw():
session["over_18"] = int(time.time()) + 3600
redir = request.values.get("redir")
if redir:
if redir.startswith(f'{SITE_FULL}/'): return redirect(redir)
if redir.startswith('/'): return redirect(f'{SITE_FULL}{redir}')
return redirect('/')

View File

@ -1,69 +1,70 @@
import html
from .front import frontlist
from datetime import datetime
from files.helpers.get import *
from yattag import Doc
from files.helpers.wrappers import *
from files.helpers.jinja2 import *
from files.__main__ import app
@app.get('/rss/<sort>/<t>')
@auth_required
def feeds_user(v=None, sort='hot', t='all'):
try: page = max(int(request.values.get("page", 1)), 1)
except: page = 1
ids, next_exists = frontlist(
sort=sort,
page=page,
t=t,
v=None,
)
posts = get_posts(ids)
domain = environ.get("DOMAIN").strip()
doc, tag, text = Doc().tagtext()
with tag("feed", ("xmlns:media","http://search.yahoo.com/mrss/"), xmlns="http://www.w3.org/2005/Atom",):
with tag("title", type="text"):
text(f"{sort} posts from {domain}")
doc.stag("link", href=SITE_FULL + request.full_path)
doc.stag("link", href=SITE_FULL)
for post in posts:
with tag("entry", ("xml:base", SITE_FULL + request.full_path)):
with tag("title", type="text"):
text(post.realtitle(None))
with tag("id"):
text(post.fullname)
if (post.edited_utc):
with tag("updated"):
text(datetime.utcfromtimestamp(post.edited_utc).isoformat())
with tag("published"):
text(datetime.utcfromtimestamp(post.created_utc).isoformat())
with tag("author"):
with tag("name"):
text(post.author_name)
with tag("uri"):
text(f'/@{post.author_name}')
doc.stag("link", href=post.permalink)
image_url = post.thumb_url or post.embed_url or post.url
doc.stag("media:thumbnail", url=image_url)
if len(post.body_html):
with tag("content", type="html"):
doc.cdata(f'''<img alt="{post.realtitle(None)}" loading="lazy" src={image_url}><br>{post.realbody(None)}''')
import html
from .front import frontlist
from datetime import datetime
from files.helpers.get import *
from yattag import Doc
from files.helpers.wrappers import *
from files.helpers.jinja2 import *
from files.__main__ import app
@app.get('/rss')
@app.get('/feed')
@app.get('/rss/<sort>/<t>')
def feeds_user(sort='hot', t='all'):
try: page = max(int(request.values.get("page", 1)), 1)
except: page = 1
ids, next_exists = frontlist(
sort=sort,
page=page,
t=t,
v=None,
)
posts = get_posts(ids)
domain = environ.get("DOMAIN").strip()
doc, tag, text = Doc().tagtext()
with tag("feed", ("xmlns:media","http://search.yahoo.com/mrss/"), xmlns="http://www.w3.org/2005/Atom",):
with tag("title", type="text"):
text(f"{sort} posts from {domain}")
doc.stag("link", href=SITE_FULL + request.full_path)
doc.stag("link", href=SITE_FULL)
for post in posts:
with tag("entry", ("xml:base", SITE_FULL + request.full_path)):
with tag("title", type="text"):
text(post.realtitle(None))
with tag("id"):
text(post.fullname)
if (post.edited_utc):
with tag("updated"):
text(datetime.utcfromtimestamp(post.edited_utc).isoformat())
with tag("published"):
text(datetime.utcfromtimestamp(post.created_utc).isoformat())
with tag("author"):
with tag("name"):
text(post.author_name)
with tag("uri"):
text(f'/@{post.author_name}')
doc.stag("link", href=post.permalink)
image_url = post.thumb_url or post.embed_url or post.url
doc.stag("media:thumbnail", url=image_url)
if len(post.body_html):
with tag("content", type="html"):
doc.cdata(f'''<img alt="{post.realtitle(None)}" loading="lazy" src={image_url}><br>{post.realbody(None)}''')
return Response( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+ doc.getvalue(), mimetype="application/xml")

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,24 @@
from flask import *
from os import environ
import requests
from files.helpers.wrappers import *
from files.__main__ import app
GIPHY_KEY = environ.get('GIPHY_KEY').rstrip()
@app.get("/giphy")
@app.get("/giphy<path>")
@auth_required
def giphy(v=None, path=None):
searchTerm = request.values.get("searchTerm", "").strip()
limit = int(request.values.get("limit", 48))
if searchTerm and limit:
url = f"https://api.giphy.com/v1/gifs/search?q={searchTerm}&api_key={GIPHY_KEY}&limit={limit}"
elif searchTerm and not limit:
url = f"https://api.giphy.com/v1/gifs/search?q={searchTerm}&api_key={GIPHY_KEY}&limit=48"
else:
url = f"https://api.giphy.com/v1/gifs?api_key={GIPHY_KEY}&limit=48"
return jsonify(requests.get(url, timeout=5).json())
from flask import *
from os import environ
import requests
from files.helpers.wrappers import *
from files.__main__ import app
GIPHY_KEY = environ.get('GIPHY_KEY').rstrip()
@app.get("/giphy")
@app.get("/giphy<path>")
@auth_required
def giphy(v=None, path=None):
searchTerm = request.values.get("searchTerm", "").strip()
limit = int(request.values.get("limit", 48))
if searchTerm and limit:
url = f"https://api.giphy.com/v1/gifs/search?q={searchTerm}&api_key={GIPHY_KEY}&limit={limit}"
elif searchTerm and not limit:
url = f"https://api.giphy.com/v1/gifs/search?q={searchTerm}&api_key={GIPHY_KEY}&limit=48"
else:
url = f"https://api.giphy.com/v1/gifs?api_key={GIPHY_KEY}&limit=48"
return jsonify(requests.get(url, timeout=5).json())

File diff suppressed because it is too large Load Diff

View File

@ -1,286 +1,286 @@
from files.helpers.wrappers import *
from files.helpers.alerts import *
from files.helpers.get import *
from files.helpers.const import *
from files.classes import *
from flask import *
from files.__main__ import app, limiter
import sqlalchemy.exc
@app.get("/authorize")
@auth_required
def authorize_prompt(v):
client_id = request.values.get("client_id")
application = g.db.query(OauthApp).filter_by(client_id=client_id).one_or_none()
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
return render_template("oauth.html", v=v, application=application)
@app.post("/authorize")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def authorize(v):
client_id = request.values.get("client_id")
application = g.db.query(OauthApp).filter_by(client_id=client_id).one_or_none()
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
access_token = secrets.token_urlsafe(128)[:128]
try:
new_auth = ClientAuth(oauth_client = application.id, user_id = v.id, access_token=access_token)
g.db.add(new_auth)
g.db.commit()
except sqlalchemy.exc.IntegrityError:
g.db.rollback()
old_auth = g.db.query(ClientAuth).filter_by(oauth_client = application.id, user_id = v.id).one()
access_token = old_auth.access_token
return redirect(f"{application.redirect_uri}?token={access_token}")
@app.post("/api_keys")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@is_not_permabanned
def request_api_keys(v):
new_app = OauthApp(
app_name=request.values.get('name').replace('<','').replace('>',''),
redirect_uri=request.values.get('redirect_uri'),
author_id=v.id,
description=request.values.get("description")[:256]
)
g.db.add(new_app)
body = f"@{v.username} has requested API keys for `{request.values.get('name')}`. You can approve or deny the request [here](/admin/apps)."
body_html = sanitize(body)
new_comment = Comment(author_id=NOTIFICATIONS_ID,
parent_submission=None,
level=1,
body_html=body_html,
sentto=2,
distinguish_level=6
)
g.db.add(new_comment)
g.db.flush()
new_comment.top_comment_id = new_comment.id
for admin in g.db.query(User).filter(User.admin_level > 2).all():
notif = Notification(comment_id=new_comment.id, user_id=admin.id)
g.db.add(notif)
g.db.commit()
return redirect('/settings/apps')
@app.post("/delete_app/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def delete_oauth_app(v, aid):
aid = int(aid)
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if app.author_id != v.id: abort(403)
for auth in g.db.query(ClientAuth).filter_by(oauth_client=app.id).all():
g.db.delete(auth)
g.db.delete(app)
g.db.commit()
return redirect('/apps')
@app.post("/edit_app/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@is_not_permabanned
def edit_oauth_app(v, aid):
aid = int(aid)
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if app.author_id != v.id: abort(403)
app.redirect_uri = request.values.get('redirect_uri')
app.app_name = request.values.get('name')
app.description = request.values.get("description")[:256]
g.db.add(app)
g.db.commit()
return redirect('/settings/apps')
@app.post("/admin/app/approve/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(3)
def admin_app_approve(v, aid):
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
user = app.author
app.client_id = secrets.token_urlsafe(64)[:64]
g.db.add(app)
access_token = secrets.token_urlsafe(128)[:128]
new_auth = ClientAuth(
oauth_client = app.id,
user_id = user.id,
access_token=access_token
)
g.db.add(new_auth)
send_repeatable_notification(user.id, f"@{v.username} has approved your application `{app.app_name}`. Here's your access token: `{access_token}`\nPlease check the guide [here](/api) if you don't know what to do next.")
ma = ModAction(
kind="approve_app",
user_id=v.id,
target_user_id=user.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "Application approved"}
@app.post("/admin/app/revoke/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
def admin_app_revoke(v, aid):
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if app:
for auth in g.db.query(ClientAuth).filter_by(oauth_client=app.id).all(): g.db.delete(auth)
send_repeatable_notification(app.author.id, f"@{v.username} has revoked your application `{app.app_name}`.")
g.db.delete(app)
ma = ModAction(
kind="revoke_app",
user_id=v.id,
target_user_id=app.author.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "App revoked"}
@app.post("/admin/app/reject/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
def admin_app_reject(v, aid):
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if app:
for auth in g.db.query(ClientAuth).filter_by(oauth_client=app.id).all(): g.db.delete(auth)
send_repeatable_notification(app.author.id, f"@{v.username} has rejected your application `{app.app_name}`.")
g.db.delete(app)
ma = ModAction(
kind="reject_app",
user_id=v.id,
target_user_id=app.author.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "App rejected"}
@app.get("/admin/app/<aid>")
@admin_level_required(2)
def admin_app_id(v, aid):
aid=aid
oauth = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
pids=oauth.idlist(page=int(request.values.get("page",1)))
next_exists=len(pids)==101
pids=pids[:100]
posts=get_posts(pids, v=v)
return render_template("admin/app.html",
v=v,
app=oauth,
listing=posts,
next_exists=next_exists
)
@app.get("/admin/app/<aid>/comments")
@admin_level_required(2)
def admin_app_id_comments(v, aid):
aid=aid
oauth = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
cids=oauth.comments_idlist(page=int(request.values.get("page",1)),
)
next_exists=len(cids)==101
cids=cids[:100]
comments=get_comments(cids, v=v)
return render_template("admin/app.html",
v=v,
app=oauth,
comments=comments,
next_exists=next_exists,
standalone=True
)
@app.get("/admin/apps")
@admin_level_required(2)
def admin_apps_list(v):
apps = g.db.query(OauthApp).order_by(OauthApp.id.desc()).all()
return render_template("admin/apps.html", v=v, apps=apps)
@app.post("/oauth/reroll/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def reroll_oauth_tokens(aid, v):
aid = aid
a = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if a.author_id != v.id: abort(403)
a.client_id = secrets.token_urlsafe(64)[:64]
g.db.add(a)
g.db.commit()
return {"message": "Client ID Rerolled", "id": a.client_id}
from files.helpers.wrappers import *
from files.helpers.alerts import *
from files.helpers.get import *
from files.helpers.const import *
from files.classes import *
from flask import *
from files.__main__ import app, limiter
import sqlalchemy.exc
@app.get("/authorize")
@auth_required
def authorize_prompt(v):
client_id = request.values.get("client_id")
application = g.db.query(OauthApp).filter_by(client_id=client_id).one_or_none()
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
return render_template("oauth.html", v=v, application=application)
@app.post("/authorize")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def authorize(v):
client_id = request.values.get("client_id")
application = g.db.query(OauthApp).filter_by(client_id=client_id).one_or_none()
if not application: return {"oauth_error": "Invalid `client_id`"}, 401
access_token = secrets.token_urlsafe(128)[:128]
try:
new_auth = ClientAuth(oauth_client = application.id, user_id = v.id, access_token=access_token)
g.db.add(new_auth)
g.db.commit()
except sqlalchemy.exc.IntegrityError:
g.db.rollback()
old_auth = g.db.query(ClientAuth).filter_by(oauth_client = application.id, user_id = v.id).one()
access_token = old_auth.access_token
return redirect(f"{application.redirect_uri}?token={access_token}")
@app.post("/api_keys")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@is_not_permabanned
def request_api_keys(v):
new_app = OauthApp(
app_name=request.values.get('name').replace('<','').replace('>',''),
redirect_uri=request.values.get('redirect_uri'),
author_id=v.id,
description=request.values.get("description")[:256]
)
g.db.add(new_app)
body = f"@{v.username} has requested API keys for `{request.values.get('name')}`. You can approve or deny the request [here](/admin/apps)."
body_html = sanitize(body)
new_comment = Comment(author_id=NOTIFICATIONS_ID,
parent_submission=None,
level=1,
body_html=body_html,
sentto=2,
distinguish_level=6
)
g.db.add(new_comment)
g.db.flush()
new_comment.top_comment_id = new_comment.id
for admin in g.db.query(User).filter(User.admin_level > 2).all():
notif = Notification(comment_id=new_comment.id, user_id=admin.id)
g.db.add(notif)
g.db.commit()
return redirect('/settings/apps')
@app.post("/delete_app/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def delete_oauth_app(v, aid):
aid = int(aid)
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if app.author_id != v.id: abort(403)
for auth in g.db.query(ClientAuth).filter_by(oauth_client=app.id).all():
g.db.delete(auth)
g.db.delete(app)
g.db.commit()
return redirect('/apps')
@app.post("/edit_app/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@is_not_permabanned
def edit_oauth_app(v, aid):
aid = int(aid)
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if app.author_id != v.id: abort(403)
app.redirect_uri = request.values.get('redirect_uri')
app.app_name = request.values.get('name')
app.description = request.values.get("description")[:256]
g.db.add(app)
g.db.commit()
return redirect('/settings/apps')
@app.post("/admin/app/approve/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(3)
def admin_app_approve(v, aid):
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
user = app.author
app.client_id = secrets.token_urlsafe(64)[:64]
g.db.add(app)
access_token = secrets.token_urlsafe(128)[:128]
new_auth = ClientAuth(
oauth_client = app.id,
user_id = user.id,
access_token=access_token
)
g.db.add(new_auth)
send_repeatable_notification(user.id, f"@{v.username} has approved your application `{app.app_name}`. Here's your access token: `{access_token}`\nPlease check the guide [here](/api) if you don't know what to do next.")
ma = ModAction(
kind="approve_app",
user_id=v.id,
target_user_id=user.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "Application approved"}
@app.post("/admin/app/revoke/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
def admin_app_revoke(v, aid):
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if app:
for auth in g.db.query(ClientAuth).filter_by(oauth_client=app.id).all(): g.db.delete(auth)
send_repeatable_notification(app.author.id, f"@{v.username} has revoked your application `{app.app_name}`.")
g.db.delete(app)
ma = ModAction(
kind="revoke_app",
user_id=v.id,
target_user_id=app.author.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "App revoked"}
@app.post("/admin/app/reject/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
def admin_app_reject(v, aid):
app = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if app:
for auth in g.db.query(ClientAuth).filter_by(oauth_client=app.id).all(): g.db.delete(auth)
send_repeatable_notification(app.author.id, f"@{v.username} has rejected your application `{app.app_name}`.")
g.db.delete(app)
ma = ModAction(
kind="reject_app",
user_id=v.id,
target_user_id=app.author.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "App rejected"}
@app.get("/admin/app/<aid>")
@admin_level_required(2)
def admin_app_id(v, aid):
aid=aid
oauth = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
pids=oauth.idlist(page=int(request.values.get("page",1)))
next_exists=len(pids)==101
pids=pids[:100]
posts=get_posts(pids, v=v)
return render_template("admin/app.html",
v=v,
app=oauth,
listing=posts,
next_exists=next_exists
)
@app.get("/admin/app/<aid>/comments")
@admin_level_required(2)
def admin_app_id_comments(v, aid):
aid=aid
oauth = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
cids=oauth.comments_idlist(page=int(request.values.get("page",1)),
)
next_exists=len(cids)==101
cids=cids[:100]
comments=get_comments(cids, v=v)
return render_template("admin/app.html",
v=v,
app=oauth,
comments=comments,
next_exists=next_exists,
standalone=True
)
@app.get("/admin/apps")
@admin_level_required(2)
def admin_apps_list(v):
apps = g.db.query(OauthApp).order_by(OauthApp.id.desc()).all()
return render_template("admin/apps.html", v=v, apps=apps)
@app.post("/oauth/reroll/<aid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def reroll_oauth_tokens(aid, v):
aid = aid
a = g.db.query(OauthApp).filter_by(id=aid).one_or_none()
if a.author_id != v.id: abort(403)
a.client_id = secrets.token_urlsafe(64)[:64]
g.db.add(a)
g.db.commit()
return {"message": "Client ID Rerolled", "id": a.client_id}

File diff suppressed because it is too large Load Diff

View File

@ -1,140 +1,140 @@
from files.helpers.wrappers import *
from files.helpers.get import *
from flask import g
from files.__main__ import app, limiter
from os import path
from files.helpers.sanitize import filter_emojis_only
@app.post("/report/post/<pid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def api_flag_post(pid, v):
post = get_post(pid)
reason = request.values.get("reason", "").strip()
if blackjack and any(i in reason.lower() for i in blackjack.split()):
v.shadowbanned = 'AutoJanny'
send_repeatable_notification(CARP_ID, f"reports on {post.permalink}")
reason = reason[:100]
if not reason.startswith('!'):
existing = g.db.query(Flag.post_id).filter_by(user_id=v.id, post_id=post.id).one_or_none()
if existing: return "", 409
reason = filter_emojis_only(reason)
if len(reason) > 350: return {"error": "Too long."}
if reason.startswith('!') and v.admin_level > 1:
post.flair = reason[1:]
g.db.add(post)
ma=ModAction(
kind="flair_post",
user_id=v.id,
target_submission_id=post.id,
_note=f'"{post.flair}"'
)
g.db.add(ma)
elif reason.startswith('/h/') and v.admin_level > 2:
post.sub = reason[3:]
g.db.add(post)
ma=ModAction(
kind="move_hole",
user_id=v.id,
target_submission_id=post.id,
)
g.db.add(ma)
else:
flag = Flag(post_id=post.id, user_id=v.id, reason=reason)
g.db.add(flag)
g.db.commit()
return {"message": "Post reported!"}
@app.post("/report/comment/<cid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def api_flag_comment(cid, v):
comment = get_comment(cid)
existing = g.db.query(CommentFlag.comment_id).filter_by( user_id=v.id, comment_id=comment.id).one_or_none()
if existing: return "", 409
reason = request.values.get("reason", "").strip()
if blackjack and any(i in reason.lower() for i in blackjack.split()):
v.shadowbanned = 'AutoJanny'
send_repeatable_notification(CARP_ID, f"reports on {comment.permalink}")
reason = reason[:100]
reason = filter_emojis_only(reason)
if len(reason) > 350: return {"error": "Too long."}
flag = CommentFlag(comment_id=comment.id, user_id=v.id, reason=reason)
g.db.add(flag)
g.db.commit()
return {"message": "Comment reported!"}
@app.post('/del_report/post/<pid>/<uid>')
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
def remove_report_post(v, pid, uid):
try:
pid = int(pid)
uid = int(uid)
except: abort(400)
report = g.db.query(Flag).filter_by(post_id=pid, user_id=uid).one()
g.db.delete(report)
ma=ModAction(
kind="delete_report",
user_id=v.id,
target_submission_id=pid
)
g.db.add(ma)
g.db.commit()
return {"message": "Report removed successfully!"}
@app.post('/del_report/comment/<cid>/<uid>')
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
def remove_report_comment(v, cid, uid):
cid = int(cid)
uid = int(uid)
report = g.db.query(CommentFlag).filter_by(comment_id=cid, user_id=uid).one()
g.db.delete(report)
ma=ModAction(
kind="delete_report",
user_id=v.id,
target_comment_id=cid
)
g.db.add(ma)
g.db.commit()
from files.helpers.wrappers import *
from files.helpers.get import *
from flask import g
from files.__main__ import app, limiter
from os import path
from files.helpers.sanitize import filter_emojis_only
@app.post("/report/post/<pid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def api_flag_post(pid, v):
post = get_post(pid)
reason = request.values.get("reason", "").strip()
if blackjack and any(i in reason.lower() for i in blackjack.split()):
v.shadowbanned = 'AutoJanny'
send_repeatable_notification(CARP_ID, f"reports on {post.permalink}")
reason = reason[:100]
if not reason.startswith('!'):
existing = g.db.query(Flag.post_id).filter_by(user_id=v.id, post_id=post.id).one_or_none()
if existing: return "", 409
reason = filter_emojis_only(reason)
if len(reason) > 350: return {"error": "Too long."}
if reason.startswith('!') and v.admin_level > 1:
post.flair = reason[1:]
g.db.add(post)
ma=ModAction(
kind="flair_post",
user_id=v.id,
target_submission_id=post.id,
_note=f'"{post.flair}"'
)
g.db.add(ma)
elif reason.startswith('/h/') and v.admin_level > 1:
post.sub = reason[3:]
g.db.add(post)
ma=ModAction(
kind="move_hole",
user_id=v.id,
target_submission_id=post.id,
)
g.db.add(ma)
else:
flag = Flag(post_id=post.id, user_id=v.id, reason=reason)
g.db.add(flag)
g.db.commit()
return {"message": "Post reported!"}
@app.post("/report/comment/<cid>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def api_flag_comment(cid, v):
comment = get_comment(cid)
existing = g.db.query(CommentFlag.comment_id).filter_by( user_id=v.id, comment_id=comment.id).one_or_none()
if existing: return "", 409
reason = request.values.get("reason", "").strip()
if blackjack and any(i in reason.lower() for i in blackjack.split()):
v.shadowbanned = 'AutoJanny'
send_repeatable_notification(CARP_ID, f"reports on {comment.permalink}")
reason = reason[:100]
reason = filter_emojis_only(reason)
if len(reason) > 350: return {"error": "Too long."}
flag = CommentFlag(comment_id=comment.id, user_id=v.id, reason=reason)
g.db.add(flag)
g.db.commit()
return {"message": "Comment reported!"}
@app.post('/del_report/post/<pid>/<uid>')
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
def remove_report_post(v, pid, uid):
try:
pid = int(pid)
uid = int(uid)
except: abort(400)
report = g.db.query(Flag).filter_by(post_id=pid, user_id=uid).one()
g.db.delete(report)
ma=ModAction(
kind="delete_report",
user_id=v.id,
target_submission_id=pid
)
g.db.add(ma)
g.db.commit()
return {"message": "Report removed successfully!"}
@app.post('/del_report/comment/<cid>/<uid>')
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
def remove_report_comment(v, cid, uid):
cid = int(cid)
uid = int(uid)
report = g.db.query(CommentFlag).filter_by(comment_id=cid, user_id=uid).one()
g.db.delete(report)
ma=ModAction(
kind="delete_report",
user_id=v.id,
target_comment_id=cid
)
g.db.add(ma)
g.db.commit()
return {"message": "Report removed successfully!"}

View File

@ -1,290 +1,290 @@
from files.helpers.wrappers import *
import re
from sqlalchemy import *
from flask import *
from files.__main__ import app
valid_params=[
'author',
'domain',
'over18'
]
def searchparse(text):
criteria = {x[0]:x[1] for x in query_regex.findall(text)}
for x in criteria:
if x in valid_params:
text = text.replace(f"{x}:{criteria[x]}", "")
text=text.strip()
if text:
criteria['q']=text
return criteria
@app.get("/search/posts")
@auth_required
def searchposts(v):
query = request.values.get("q", '').strip()
page = max(1, int(request.values.get("page", 1)))
sort = request.values.get("sort", "new").lower()
t = request.values.get('t', 'all').lower()
criteria=searchparse(query)
posts = g.db.query(Submission.id)
if not v.paid_dues: posts = posts.filter_by(club=False)
if v.admin_level < 2:
posts = posts.filter(Submission.deleted_utc == 0, Submission.is_banned == False, Submission.private == False, Submission.author_id.notin_(v.userblocks))
if 'author' in criteria:
posts = posts.filter(Submission.ghost == False)
author = get_user(criteria['author'])
if not author: return {"error": "User not found"}
if author.is_private and author.id != v.id and v.admin_level < 2 and not v.eye:
if request.headers.get("Authorization"):
return {"error": f"@{author.username}'s profile is private; You can't use the 'author' syntax on them"}
return render_template("search.html",
v=v,
query=query,
total=0,
page=page,
listing=[],
sort=sort,
t=t,
next_exists=False,
domain=None,
domain_obj=None,
error=f"@{author.username}'s profile is private; You can't use the 'author' syntax on them."
)
else: posts = posts.filter(Submission.author_id == author.id)
if 'q' in criteria:
words=criteria['q'].split()
words = criteria['q'].replace('\\', '').replace('_', '\_').replace('%', '\%').strip().split()
words=[Submission.title.ilike('%'+x+'%') for x in words]
posts=posts.filter(*words)
if 'over18' in criteria: posts = posts.filter(Submission.over_18==True)
if 'domain' in criteria:
domain=criteria['domain']
domain = domain.replace('\\', '').replace('_', '\_').replace('%', '').strip()
posts=posts.filter(
or_(
Submission.url.ilike("https://"+domain+'/%'),
Submission.url.ilike("https://"+domain+'/%'),
Submission.url.ilike("https://"+domain),
Submission.url.ilike("https://"+domain),
Submission.url.ilike("https://www."+domain+'/%'),
Submission.url.ilike("https://www."+domain+'/%'),
Submission.url.ilike("https://www."+domain),
Submission.url.ilike("https://www."+domain),
Submission.url.ilike("https://old." + domain + '/%'),
Submission.url.ilike("https://old." + domain + '/%'),
Submission.url.ilike("https://old." + domain),
Submission.url.ilike("https://old." + domain)
)
)
if t:
now = int(time.time())
if t == 'hour':
cutoff = now - 3600
elif t == 'day':
cutoff = now - 86400
elif t == 'week':
cutoff = now - 604800
elif t == 'month':
cutoff = now - 2592000
elif t == 'year':
cutoff = now - 31536000
else:
cutoff = 0
posts = posts.filter(Submission.created_utc >= cutoff)
if sort == "new":
posts = posts.order_by(Submission.created_utc.desc())
elif sort == "old":
posts = posts.order_by(Submission.created_utc)
elif sort == "controversial":
posts = posts.order_by((Submission.upvotes+1)/(Submission.downvotes+1) + (Submission.downvotes+1)/(Submission.upvotes+1), Submission.downvotes.desc())
elif sort == "top":
posts = posts.order_by(Submission.downvotes - Submission.upvotes)
elif sort == "bottom":
posts = posts.order_by(Submission.upvotes - Submission.downvotes)
elif sort == "comments":
posts = posts.order_by(Submission.comment_count.desc())
total = posts.count()
posts = posts.offset(25 * (page - 1)).limit(26).all()
ids = [x[0] for x in posts]
next_exists = (len(ids) > 25)
ids = ids[:25]
posts = get_posts(ids, v=v)
if request.headers.get("Authorization"): return {"total":total, "data":[x.json for x in posts]}
return render_template("search.html",
v=v,
query=query,
total=total,
page=page,
listing=posts,
sort=sort,
t=t,
next_exists=next_exists
)
@app.get("/search/comments")
@auth_required
def searchcomments(v):
query = request.values.get("q", '').strip()
try: page = max(1, int(request.values.get("page", 1)))
except: page = 1
sort = request.values.get("sort", "new").lower()
t = request.values.get('t', 'all').lower()
criteria = searchparse(query)
comments = g.db.query(Comment.id).filter(Comment.parent_submission != None)
if 'author' in criteria:
comments = comments.filter(Comment.ghost == False)
author = get_user(criteria['author'])
if not author: return {"error": "User not found"}
if author.is_private and author.id != v.id and v.admin_level < 2 and not v.eye:
if request.headers.get("Authorization"):
return {"error": f"@{author.username}'s profile is private; You can't use the 'author' syntax on them"}
return render_template("search_comments.html", v=v, query=query, total=0, page=page, comments=[], sort=sort, t=t, next_exists=False, error=f"@{author.username}'s profile is private; You can't use the 'author' syntax on them.")
else: comments = comments.filter(Comment.author_id == author.id)
if 'q' in criteria:
words = criteria['q'].replace('\\', '').replace('_', '\_').replace('%', '\%').strip().split()
words = [Comment.body.ilike('%'+x+'%') for x in words]
comments = comments.filter(*words)
if 'over18' in criteria: comments = comments.filter(Comment.over_18 == True)
if t:
now = int(time.time())
if t == 'hour':
cutoff = now - 3600
elif t == 'day':
cutoff = now - 86400
elif t == 'week':
cutoff = now - 604800
elif t == 'month':
cutoff = now - 2592000
elif t == 'year':
cutoff = now - 31536000
else:
cutoff = 0
comments = comments.filter(Comment.created_utc >= cutoff)
if v.admin_level < 2:
private = [x[0] for x in g.db.query(Submission.id).filter(Submission.private == True).all()]
comments = comments.filter(Comment.author_id.notin_(v.userblocks), Comment.is_banned==False, Comment.deleted_utc == 0, Comment.parent_submission.notin_(private))
if not v.paid_dues:
club = [x[0] for x in g.db.query(Submission.id).filter(Submission.club == True).all()]
comments = comments.filter(Comment.parent_submission.notin_(club))
if sort == "new":
comments = comments.order_by(Comment.created_utc.desc())
elif sort == "old":
comments = comments.order_by(Comment.created_utc)
elif sort == "controversial":
comments = comments.order_by((Comment.upvotes+1)/(Comment.downvotes+1) + (Comment.downvotes+1)/(Comment.upvotes+1), Comment.downvotes.desc())
elif sort == "top":
comments = comments.order_by(Comment.downvotes - Comment.upvotes)
elif sort == "bottom":
comments = comments.order_by(Comment.upvotes - Comment.downvotes)
total = comments.count()
comments = comments.offset(25 * (page - 1)).limit(26).all()
ids = [x[0] for x in comments]
next_exists = (len(ids) > 25)
ids = ids[:25]
comments = get_comments(ids, v=v)
if request.headers.get("Authorization"): return {"total":total, "data":[x.json for x in comments]}
return render_template("search_comments.html", v=v, query=query, total=total, page=page, comments=comments, sort=sort, t=t, next_exists=next_exists, standalone=True)
@app.get("/search/users")
@auth_required
def searchusers(v):
query = request.values.get("q", '').strip()
page = max(1, int(request.values.get("page", 1)))
sort = request.values.get("sort", "new").lower()
t = request.values.get('t', 'all').lower()
term=query.lstrip('@')
term = term.replace('\\','').replace('_','\_').replace('%','')
users=g.db.query(User).filter(User.username.ilike(f'%{term}%'))
users=users.order_by(User.username.ilike(term).desc(), User.stored_subscriber_count.desc())
total=users.count()
users=[x for x in users.offset(25 * (page-1)).limit(26)]
next_exists=(len(users)>25)
users=users[:25]
if request.headers.get("Authorization"): return {"data": [x.json for x in users]}
from files.helpers.wrappers import *
import re
from sqlalchemy import *
from flask import *
from files.__main__ import app
valid_params=[
'author',
'domain',
'over18'
]
def searchparse(text):
criteria = {x[0]:x[1] for x in query_regex.findall(text)}
for x in criteria:
if x in valid_params:
text = text.replace(f"{x}:{criteria[x]}", "")
text=text.strip()
if text:
criteria['q']=text
return criteria
@app.get("/search/posts")
@auth_required
def searchposts(v):
query = request.values.get("q", '').strip()
page = max(1, int(request.values.get("page", 1)))
sort = request.values.get("sort", "new").lower()
t = request.values.get('t', 'all').lower()
criteria=searchparse(query)
posts = g.db.query(Submission.id)
if not v.paid_dues: posts = posts.filter_by(club=False)
if v.admin_level < 2:
posts = posts.filter(Submission.deleted_utc == 0, Submission.is_banned == False, Submission.private == False, Submission.author_id.notin_(v.userblocks))
if 'author' in criteria:
posts = posts.filter(Submission.ghost == False)
author = get_user(criteria['author'])
if not author: return {"error": "User not found"}
if author.is_private and author.id != v.id and v.admin_level < 2 and not v.eye:
if request.headers.get("Authorization"):
return {"error": f"@{author.username}'s profile is private; You can't use the 'author' syntax on them"}
return render_template("search.html",
v=v,
query=query,
total=0,
page=page,
listing=[],
sort=sort,
t=t,
next_exists=False,
domain=None,
domain_obj=None,
error=f"@{author.username}'s profile is private; You can't use the 'author' syntax on them."
)
else: posts = posts.filter(Submission.author_id == author.id)
if 'q' in criteria:
words=criteria['q'].split()
words = criteria['q'].replace('\\', '').replace('_', '\_').replace('%', '\%').strip().split()
words=[Submission.title.ilike('%'+x+'%') for x in words]
posts=posts.filter(*words)
if 'over18' in criteria: posts = posts.filter(Submission.over_18==True)
if 'domain' in criteria:
domain=criteria['domain']
domain = domain.replace('\\', '').replace('_', '\_').replace('%', '').strip()
posts=posts.filter(
or_(
Submission.url.ilike("https://"+domain+'/%'),
Submission.url.ilike("https://"+domain+'/%'),
Submission.url.ilike("https://"+domain),
Submission.url.ilike("https://"+domain),
Submission.url.ilike("https://www."+domain+'/%'),
Submission.url.ilike("https://www."+domain+'/%'),
Submission.url.ilike("https://www."+domain),
Submission.url.ilike("https://www."+domain),
Submission.url.ilike("https://old." + domain + '/%'),
Submission.url.ilike("https://old." + domain + '/%'),
Submission.url.ilike("https://old." + domain),
Submission.url.ilike("https://old." + domain)
)
)
if t:
now = int(time.time())
if t == 'hour':
cutoff = now - 3600
elif t == 'day':
cutoff = now - 86400
elif t == 'week':
cutoff = now - 604800
elif t == 'month':
cutoff = now - 2592000
elif t == 'year':
cutoff = now - 31536000
else:
cutoff = 0
posts = posts.filter(Submission.created_utc >= cutoff)
if sort == "new":
posts = posts.order_by(Submission.created_utc.desc())
elif sort == "old":
posts = posts.order_by(Submission.created_utc)
elif sort == "controversial":
posts = posts.order_by((Submission.upvotes+1)/(Submission.downvotes+1) + (Submission.downvotes+1)/(Submission.upvotes+1), Submission.downvotes.desc())
elif sort == "top":
posts = posts.order_by(Submission.downvotes - Submission.upvotes)
elif sort == "bottom":
posts = posts.order_by(Submission.upvotes - Submission.downvotes)
elif sort == "comments":
posts = posts.order_by(Submission.comment_count.desc())
total = posts.count()
posts = posts.offset(25 * (page - 1)).limit(26).all()
ids = [x[0] for x in posts]
next_exists = (len(ids) > 25)
ids = ids[:25]
posts = get_posts(ids, v=v)
if request.headers.get("Authorization"): return {"total":total, "data":[x.json for x in posts]}
return render_template("search.html",
v=v,
query=query,
total=total,
page=page,
listing=posts,
sort=sort,
t=t,
next_exists=next_exists
)
@app.get("/search/comments")
@auth_required
def searchcomments(v):
query = request.values.get("q", '').strip()
try: page = max(1, int(request.values.get("page", 1)))
except: page = 1
sort = request.values.get("sort", "new").lower()
t = request.values.get('t', 'all').lower()
criteria = searchparse(query)
comments = g.db.query(Comment.id).filter(Comment.parent_submission != None)
if 'author' in criteria:
comments = comments.filter(Comment.ghost == False)
author = get_user(criteria['author'])
if not author: return {"error": "User not found"}
if author.is_private and author.id != v.id and v.admin_level < 2 and not v.eye:
if request.headers.get("Authorization"):
return {"error": f"@{author.username}'s profile is private; You can't use the 'author' syntax on them"}
return render_template("search_comments.html", v=v, query=query, total=0, page=page, comments=[], sort=sort, t=t, next_exists=False, error=f"@{author.username}'s profile is private; You can't use the 'author' syntax on them.")
else: comments = comments.filter(Comment.author_id == author.id)
if 'q' in criteria:
words = criteria['q'].replace('\\', '').replace('_', '\_').replace('%', '\%').strip().split()
words = [Comment.body.ilike('%'+x+'%') for x in words]
comments = comments.filter(*words)
if 'over18' in criteria: comments = comments.filter(Comment.over_18 == True)
if t:
now = int(time.time())
if t == 'hour':
cutoff = now - 3600
elif t == 'day':
cutoff = now - 86400
elif t == 'week':
cutoff = now - 604800
elif t == 'month':
cutoff = now - 2592000
elif t == 'year':
cutoff = now - 31536000
else:
cutoff = 0
comments = comments.filter(Comment.created_utc >= cutoff)
if v.admin_level < 2:
private = [x[0] for x in g.db.query(Submission.id).filter(Submission.private == True).all()]
comments = comments.filter(Comment.author_id.notin_(v.userblocks), Comment.is_banned==False, Comment.deleted_utc == 0, Comment.parent_submission.notin_(private))
if not v.paid_dues:
club = [x[0] for x in g.db.query(Submission.id).filter(Submission.club == True).all()]
comments = comments.filter(Comment.parent_submission.notin_(club))
if sort == "new":
comments = comments.order_by(Comment.created_utc.desc())
elif sort == "old":
comments = comments.order_by(Comment.created_utc)
elif sort == "controversial":
comments = comments.order_by((Comment.upvotes+1)/(Comment.downvotes+1) + (Comment.downvotes+1)/(Comment.upvotes+1), Comment.downvotes.desc())
elif sort == "top":
comments = comments.order_by(Comment.downvotes - Comment.upvotes)
elif sort == "bottom":
comments = comments.order_by(Comment.upvotes - Comment.downvotes)
total = comments.count()
comments = comments.offset(25 * (page - 1)).limit(26).all()
ids = [x[0] for x in comments]
next_exists = (len(ids) > 25)
ids = ids[:25]
comments = get_comments(ids, v=v)
if request.headers.get("Authorization"): return {"total":total, "data":[x.json for x in comments]}
return render_template("search_comments.html", v=v, query=query, total=total, page=page, comments=comments, sort=sort, t=t, next_exists=next_exists, standalone=True)
@app.get("/search/users")
@auth_required
def searchusers(v):
query = request.values.get("q", '').strip()
page = max(1, int(request.values.get("page", 1)))
sort = request.values.get("sort", "new").lower()
t = request.values.get('t', 'all').lower()
term=query.lstrip('@')
term = term.replace('\\','').replace('_','\_').replace('%','')
users=g.db.query(User).filter(User.username.ilike(f'%{term}%'))
users=users.order_by(User.username.ilike(term).desc(), User.stored_subscriber_count.desc())
total=users.count()
users=[x for x in users.offset(25 * (page-1)).limit(26)]
next_exists=(len(users)>25)
users=users[:25]
if request.headers.get("Authorization"): return {"data": [x.json for x in users]}
return render_template("search_users.html", v=v, query=query, total=total, page=page, users=users, sort=sort, t=t, next_exists=next_exists)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,7 @@ from files.helpers.alerts import *
from files.helpers.wrappers import *
from files.classes import *
from .front import frontlist
import tldextract
@app.post("/exile/post/<pid>")
@is_not_permabanned
@ -224,7 +223,7 @@ def remove_mod(v, sub):
@app.get("/create_sub")
@is_not_permabanned
def create_sub(v):
if SITE_NAME == 'rDrama' and v.admin_level < 3: abort(403)
if SITE_NAME != 'PCM' and v.admin_level < 3: abort(403)
if request.host == 'rdrama.net': cost = 0
else:
@ -336,9 +335,22 @@ def post_sub_css(v, sub):
if not v.mods(sub.name): abort(403)
sub.css = request.values.get('css', '').strip()
g.db.add(sub)
css = request.values.get('css', '').strip()
urls = list(css_regex.finditer(css)) + list(css_regex2.finditer(css))
for i in urls:
url = i.group(1)
if url.startswith('/'): continue
domain = tldextract.extract(url).registered_domain
if domain not in approved_embed_hosts:
error = f"The domain '{domain}' is not allowed, please use one of these domains\n\n{approved_embed_hosts}."
return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub, error=error)
sub.css = css
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@ -369,7 +381,7 @@ def sub_banner(v, sub):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
bannerurl = process_image(name)
bannerurl = process_image(v.patron, name)
if bannerurl:
if sub.bannerurl and '/images/' in sub.bannerurl:
@ -396,7 +408,7 @@ def sub_sidebar(v, sub):
file = request.files["sidebar"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
sidebarurl = process_image(name)
sidebarurl = process_image(v.patron, name)
if sidebarurl:
if sub.sidebarurl and '/images/' in sub.sidebarurl:

File diff suppressed because it is too large Load Diff

View File

@ -49,10 +49,9 @@ def admin_vote_info_get(v):
downs=downs)
@app.post("/vote/post/<post_id>/<new>")
@limiter.limit("5/second;60/minute;600/hour;1000/day")
@limiter.limit("5/second;60/minute;600/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@limiter.limit("5/second;60/minute;1000/hour;2000/day")
@limiter.limit("5/second;60/minute;1000/hour;2000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@is_not_permabanned
def api_vote_post(post_id, new, v):
@ -113,17 +112,17 @@ def api_vote_post(post_id, new, v):
g.db.add(vote)
g.db.flush()
post.upvotes = g.db.query(Vote.submission_id).filter_by(submission_id=post.id, vote_type=1).count()
post.downvotes = g.db.query(Vote.submission_id).filter_by(submission_id=post.id, vote_type=-1).count()
post.realupvotes = g.db.query(Vote.submission_id).filter_by(submission_id=post.id, real=True).count()
post.upvotes = g.db.query(Vote).filter_by(submission_id=post.id, vote_type=1).count()
post.downvotes = g.db.query(Vote).filter_by(submission_id=post.id, vote_type=-1).count()
post.realupvotes = g.db.query(Vote).filter_by(submission_id=post.id, real=True).count()
if post.author.progressivestack: post.realupvotes *= 2
g.db.add(post)
g.db.commit()
return "", 204
@app.post("/vote/comment/<comment_id>/<new>")
@limiter.limit("5/second;60/minute;600/hour;1000/day")
@limiter.limit("5/second;60/minute;600/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@limiter.limit("5/second;60/minute;1000/hour;2000/day")
@limiter.limit("5/second;60/minute;1000/hour;2000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@is_not_permabanned
def api_vote_comment(comment_id, new, v):
@ -190,9 +189,9 @@ def api_vote_comment(comment_id, new, v):
g.db.add(vote)
g.db.flush()
comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=1).count()
comment.downvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=-1).count()
comment.realupvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, real=True).count()
comment.upvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, vote_type=1).count()
comment.downvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, vote_type=-1).count()
comment.realupvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, real=True).count()
if comment.author.progressivestack: comment.realupvotes *= 2
g.db.add(comment)
g.db.commit()
@ -225,7 +224,7 @@ def api_vote_poll(comment_id, v):
g.db.add(vote)
g.db.flush()
comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=1).count()
comment.upvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, vote_type=1).count()
g.db.add(comment)
g.db.commit()
return "", 204
@ -283,12 +282,12 @@ def api_vote_choice(comment_id, v):
else: parent = comment.post
for vote in parent.total_choice_voted(v):
vote.comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=vote.comment.id, vote_type=1).count() - 1
vote.comment.upvotes = g.db.query(CommentVote).filter_by(comment_id=vote.comment.id, vote_type=1).count() - 1
g.db.add(vote.comment)
g.db.delete(vote)
g.db.flush()
comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=1).count()
comment.upvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, vote_type=1).count()
g.db.add(comment)
g.db.commit()
return "", 204

View File

@ -1,91 +1,96 @@
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}}</title>
{% endblock %}
{% block content %}
<pre></pre>
<pre></pre>
<h3>&nbsp;Admin Tools</h3>
<h4>Content</h4>
<ul>
<li><a href="/admin/image_posts">Image Posts</a></li>
<li><a href="/admin/reported/posts">Reported Posts/Comments</a></li>
<li><a href="/admin/removed/posts">Removed Posts/Comments</a></li>
</ul>
<h4>Users</h4>
<ul>
<li><a href="/admin/users">Users Feed</a></li>
<li><a href="/admin/shadowbanned">Shadowbanned Users</a></li>
<li><a href="/banned">Permabanned Users</a></li>
<li><a href="/agendaposters">Users with Chud Theme</a></li>
<li><a href="/grassed">Currently Grassed Users</a></li>
</ul>
<h4>Safety</h4>
<ul>
<li><a href="/admin/banned_domains">Banned Domains</a></li>
<li><a href="/admin/alt_votes">Multi Vote Analysis</a></li>
</ul>
<h4>Grant</h4>
<ul>
<li><a href="/admin/awards">Give User Award</a></li>
<li><a href="/admin/badge_grant">Grant Badges</a></li>
<li><a href="/admin/badge_remove">Remove Badges</a></li>
</ul>
<h4>API Access Control</h4>
<ul>
<li><a href="/admin/apps">Apps</a></li>
</ul>
<h4>Statistics</h4>
<ul>
<li><a href="/stats">Content Stats</a></li>
<li><a href="/weekly_chart">Weekly Stat Chart</a></li>
<li><a href="/daily_chart">Daily Stat Chart</a></li>
</ul>
{% if SITE_NAME == 'PCM' %}
<h4>Configuration</h4>
<ul>
<li><a href="/admin/sidebar">Edit Sidebar</a></li>
</ul>
{% endif %}
{% if v.admin_level > 2 %}
<pre></pre>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="signups" {% if site_settings['Signups'] %}checked{% endif %} onchange="post_toast(this,'/admin/site_settings/Signups');">
<label class="custom-control-label" for="signups">Signups</label>
</div>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="bots" {% if site_settings['Bots'] %}checked{% endif %} onchange="post_toast(this,'/admin/site_settings/Bots');">
<label class="custom-control-label" for="bots">Bots</label>
</div>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="Fart mode" {% if site_settings['Fart mode'] %}checked{% endif %} onchange="post_toast(this,'/admin/site_settings/Fart mode');">
<label class="custom-control-label" for="Fart mode">Fart mode</label>
</div>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="Read-only mode" {% if site_settings['Read-only mode'] %}checked{% endif %} onchange="post_toast(this,'/admin/site_settings/Read-only mode');">
<label class="custom-control-label" for="Read-only mode">Read-only mode</label>
</div>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="under_attack" name="under_attack" {% if under_attack%}checked{% endif %} onchange="post_toast(this,'/admin/under_attack');">
<label class="custom-control-label" for="under_attack">Under attack mode</label>
</div>
<button class="btn btn-primary mt-3" onclick="post_toast(this,'/admin/purge_cache');">PURGE CACHE</button>
{% endif %}
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}}</title>
{% endblock %}
{% block content %}
<pre></pre>
<pre></pre>
<h3>&nbsp;Admin Tools</h3>
<h4>Content</h4>
<ul>
<li><a href="/admin/image_posts">Image Posts</a></li>
<li><a href="/admin/reported/posts">Reported Posts/Comments</a></li>
<li><a href="/admin/removed/posts">Removed Posts/Comments</a></li>
</ul>
<h4>Users</h4>
<ul>
<li><a href="/admin/users">Users Feed</a></li>
<li><a href="/admin/shadowbanned">Shadowbanned Users</a></li>
<li><a href="/banned">Permabanned Users</a></li>
<li><a href="/agendaposters">Users with Chud Theme</a></li>
<li><a href="/grassed">Currently Grassed Users</a></li>
</ul>
<h4>Safety</h4>
<ul>
<li><a href="/admin/banned_domains">Banned Domains</a></li>
<li><a href="/admin/alt_votes">Multi Vote Analysis</a></li>
</ul>
<h4>Grant</h4>
<ul>
<li><a href="/admin/awards">Give User Award</a></li>
<li><a href="/admin/badge_grant">Grant Badges</a></li>
<li><a href="/admin/badge_remove">Remove Badges</a></li>
</ul>
<h4>API Access Control</h4>
<ul>
<li><a href="/admin/apps">Apps</a></li>
</ul>
<h4>Statistics</h4>
<ul>
<li><a href="/stats">Content Stats</a></li>
<li><a href="/weekly_chart">Weekly Stat Chart</a></li>
<li><a href="/daily_chart">Daily Stat Chart</a></li>
</ul>
{% if SITE_NAME == 'PCM' %}
<h4>Configuration</h4>
<ul>
<li><a href="/admin/sidebar">Edit Sidebar</a></li>
</ul>
{% endif %}
{% if v.admin_level > 2 %}
<pre></pre>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="signups" {% if site_settings['Signups'] %}checked{% endif %} onchange="post_toast(this,'/admin/site_settings/Signups');">
<label class="custom-control-label" for="signups">Signups</label>
</div>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="bots" {% if site_settings['Bots'] %}checked{% endif %} onchange="post_toast(this,'/admin/site_settings/Bots');">
<label class="custom-control-label" for="bots">Bots</label>
</div>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="Fart mode" {% if site_settings['Fart mode'] %}checked{% endif %} onchange="post_toast(this,'/admin/site_settings/Fart mode');">
<label class="custom-control-label" for="Fart mode">Fart mode</label>
</div>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="Read-only mode" {% if site_settings['Read-only mode'] %}checked{% endif %} onchange="post_toast(this,'/admin/site_settings/Read-only mode');">
<label class="custom-control-label" for="Read-only mode">Read-only mode</label>
</div>
<div class="custom-control custom-switch">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="under_attack" name="under_attack" {% if under_attack%}checked{% endif %} onchange="post_toast(this,'/admin/under_attack');">
<label class="custom-control-label" for="under_attack">Under attack mode</label>
</div>
<button class="btn btn-primary mt-3" onclick="post_toast(this,'/admin/purge_cache');" style="margin-bottom: 2em;">PURGE CACHE</button>
{% endif %}
<h4>Server Status</h4>
<div>
Live Revision: <code>{{ gitref }}</code> <br>
</div>
{% endblock %}

View File

@ -1,88 +1,88 @@
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}}</title>
{% endblock %}
{% block content %}
<pre>
</pre>
<h5>Vote Info</h5>
<form action="/admin/alt_votes" method="get" class="mb-6">
<label for="link-input">Usernames</label>
<input autocomplete="off" id="link-input" type="text" class="form-control mb-2" name="u1" value="{{u1.username if u1 else ''}}" placeholder="User 1">
<input autocomplete="off" id="link-input" type="text" class="form-control mb-2" name="u2" value="{{u2.username if u2 else ''}}" placeholder="User 2">
<input type="submit" value="Submit" class="btn btn-primary">
</form>
{% if u1 and u2 %}
<h2>Analysis</h2>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th></th>
<th>@{{u1.username}} only(% unique)</th>
<th>Both</th>
<th>@{{u2.username}} only (% unique)</th>
</tr>
</thead>
<tr>
<td><b>Post Upvotes</b></td>
<td>{{data['u1_only_post_ups']}} ({{data['u1_post_ups_unique']}}%)</td>
<td>{{data['both_post_ups']}}</td>
<td>{{data['u2_only_post_ups']}} ({{data['u2_post_ups_unique']}}%)</td>
</tr>
<tr>
<td><b>Post Downvotes</b></td>
<td>{{data['u1_only_post_downs']}} ({{data['u1_post_downs_unique']}}%)</td>
<td>{{data['both_post_downs']}}</td>
<td>{{data['u2_only_post_downs']}} ({{data['u2_post_downs_unique']}}%)</td>
</tr>
<tr>
<td><b>Comment Upvotes</b></td>
<td>{{data['u1_only_comment_ups']}} ({{data['u1_comment_ups_unique']}}%)</td>
<td>{{data['both_comment_ups']}}</td>
<td>{{data['u2_only_comment_ups']}} ({{data['u2_comment_ups_unique']}}%)</td>
</tr>
<tr>
<td><b>Comment Downvotes</b></td>
<td>{{data['u1_only_comment_downs']}} ({{data['u1_comment_downs_unique']}}%)</td>
<td>{{data['both_comment_downs']}}</td>
<td>{{data['u2_only_comment_downs']}} ({{data['u2_comment_downs_unique']}}%)</td>
</tr>
</table>
<h2>Link Accounts</h2>
{% if u2 in u1.alts %}
<p>Accounts are known alts of eachother.</p>
{% else %}
<p>Two accounts controlled by different people should have most uniqueness percentages at or above 70-80%</p>
<p>A sockpuppet account will have its uniqueness percentages significantly lower.</p>
<a role="button" class="btn btn-secondary" onclick="document.getElementById('linkbtn').classList.toggle('d-none');">Link Accounts</a>
<form action="/admin/link_accounts" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input type="hidden" name="u1" value="{{u1.id}}">
<input type="hidden" name="u2" value="{{u2.id}}">
<input type="submit" id="linkbtn" class="btn btn-primary d-none" value="Confirm Link: {{u1.username}} and {{u2.username}}">
</form>
{% endif %}
{% endif %}
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}}</title>
{% endblock %}
{% block content %}
<pre>
</pre>
<h5>Vote Info</h5>
<form action="/admin/alt_votes" method="get" class="mb-6">
<label for="link-input">Usernames</label>
<input autocomplete="off" id="link-input" type="text" class="form-control mb-2" name="u1" value="{{u1.username if u1 else ''}}" placeholder="User 1">
<input autocomplete="off" id="link-input" type="text" class="form-control mb-2" name="u2" value="{{u2.username if u2 else ''}}" placeholder="User 2">
<input type="submit" value="Submit" class="btn btn-primary">
</form>
{% if u1 and u2 %}
<h2>Analysis</h2>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th></th>
<th>@{{u1.username}} only(% unique)</th>
<th>Both</th>
<th>@{{u2.username}} only (% unique)</th>
</tr>
</thead>
<tr>
<td><b>Post Upvotes</b></td>
<td>{{data['u1_only_post_ups']}} ({{data['u1_post_ups_unique']}}%)</td>
<td>{{data['both_post_ups']}}</td>
<td>{{data['u2_only_post_ups']}} ({{data['u2_post_ups_unique']}}%)</td>
</tr>
<tr>
<td><b>Post Downvotes</b></td>
<td>{{data['u1_only_post_downs']}} ({{data['u1_post_downs_unique']}}%)</td>
<td>{{data['both_post_downs']}}</td>
<td>{{data['u2_only_post_downs']}} ({{data['u2_post_downs_unique']}}%)</td>
</tr>
<tr>
<td><b>Comment Upvotes</b></td>
<td>{{data['u1_only_comment_ups']}} ({{data['u1_comment_ups_unique']}}%)</td>
<td>{{data['both_comment_ups']}}</td>
<td>{{data['u2_only_comment_ups']}} ({{data['u2_comment_ups_unique']}}%)</td>
</tr>
<tr>
<td><b>Comment Downvotes</b></td>
<td>{{data['u1_only_comment_downs']}} ({{data['u1_comment_downs_unique']}}%)</td>
<td>{{data['both_comment_downs']}}</td>
<td>{{data['u2_only_comment_downs']}} ({{data['u2_comment_downs_unique']}}%)</td>
</tr>
</table>
<h2>Link Accounts</h2>
{% if u2 in u1.alts %}
<p>Accounts are known alts of eachother.</p>
{% else %}
<p>Two accounts controlled by different people should have most uniqueness percentages at or above 70-80%</p>
<p>A sockpuppet account will have its uniqueness percentages significantly lower.</p>
<a role="button" class="btn btn-secondary" onclick="document.getElementById('linkbtn').classList.toggle('d-none');">Link Accounts</a>
<form action="/admin/link_accounts" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input type="hidden" name="u1" value="{{u1.id}}">
<input type="hidden" name="u2" value="{{u2.id}}">
<input type="submit" id="linkbtn" class="btn btn-primary d-none" value="Confirm Link: {{u1.username}} and {{u2.username}}">
</form>
{% endif %}
{% endif %}
{% endblock %}

View File

@ -1,72 +1,72 @@
{% extends "default.html" %}
{% block title %}
<title>API App Administration</title>
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-lg-8">
<div class="settings">
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="title w-lg-25">
<label for="over18">{{app.app_name}}</label>
</div>
<div class="body w-lg-100">
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label>
<input autocomplete="off" id="edit-{{app.id}}-author" class="form-control" type="text" name="name" value="{{app.author.username}}" readonly=readonly>
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label>
<input autocomplete="off" id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>
<label for="edit-{{app.id}}-redirect" class="mb-0 w-lg-25">Redirect URI</label>
<input autocomplete="off" id="edit-{{app.id}}-redirect" class="form-control" type="text" name="redirect_uri" value="{{app.redirect_uri}}" readonly="readonly">
<label for="edit-{{app.id}}-desc" class="mb-0 w-lg-25">Description</label>
<textarea rows="10" autocomplete="off" form="edit-app-{{app.id}}" class="form-control" name="description" id="edit-{{app.id}}-desc" maxlength="256" readonly="readonly">{{app.description}}</textarea>
</div>
</div>
<div class="footer">
<div class="d-flex">
{% if not app.client_id%}
<a role="button" class="btn btn-primary ml-auto" onclick="post_toast(this,'/admin/app/approve/{{app.id}}')">Approve</a>
<a role="button" class="btn btn-secondary mr-0" onclick="post_toast(this,'/admin/app/reject/{{app.id}}')">Reject</a>
{% else %}
<a role="button" class="btn btn-primary ml-auto" onclick="post_toast(this,'/admin/app/revoke/{{app.id}}')">Revoke</a>
{% endif %}
</div>
</div>
</div>
</div>
{% if listing %}
{% include "submission_listing.html" %}
{% elif comments %}
{% include "comments.html" %}
{% endif %}
</div>
</div>
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text">Action successful!</span>
</div>
</div>
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
</div>
</div>
{% extends "default.html" %}
{% block title %}
<title>API App Administration</title>
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-lg-8">
<div class="settings">
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="title w-lg-25">
<label for="over18">{{app.app_name}}</label>
</div>
<div class="body w-lg-100">
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label>
<input autocomplete="off" id="edit-{{app.id}}-author" class="form-control" type="text" name="name" value="{{app.author.username}}" readonly=readonly>
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label>
<input autocomplete="off" id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>
<label for="edit-{{app.id}}-redirect" class="mb-0 w-lg-25">Redirect URI</label>
<input autocomplete="off" id="edit-{{app.id}}-redirect" class="form-control" type="text" name="redirect_uri" value="{{app.redirect_uri}}" readonly="readonly">
<label for="edit-{{app.id}}-desc" class="mb-0 w-lg-25">Description</label>
<textarea rows="10" autocomplete="off" form="edit-app-{{app.id}}" class="form-control" name="description" id="edit-{{app.id}}-desc" maxlength="256" readonly="readonly">{{app.description}}</textarea>
</div>
</div>
<div class="footer">
<div class="d-flex">
{% if not app.client_id%}
<a role="button" class="btn btn-primary ml-auto" onclick="post_toast(this,'/admin/app/approve/{{app.id}}')">Approve</a>
<a role="button" class="btn btn-secondary mr-0" onclick="post_toast(this,'/admin/app/reject/{{app.id}}')">Reject</a>
{% else %}
<a role="button" class="btn btn-primary ml-auto" onclick="post_toast(this,'/admin/app/revoke/{{app.id}}')">Revoke</a>
{% endif %}
</div>
</div>
</div>
</div>
{% if listing %}
{% include "submission_listing.html" %}
{% elif comments %}
{% include "comments.html" %}
{% endif %}
</div>
</div>
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text">Action successful!</span>
</div>
</div>
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
</div>
</div>
{% endblock %}

View File

@ -1,72 +1,72 @@
{% extends "default.html" %}
{% block title %}
<title>API App Administration</title>
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-lg-8">
<div class="settings">
{% for app in apps %}
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="title w-lg-25">
<label for="over18"><a href="{{app.permalink}}" {% if v and v.newtab and not g.webview %}target="_blank"{% endif %}>{{app.app_name}}</a></label>
</div>
<div class="body w-lg-100">
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label>
<input autocomplete="off" id="edit-{{app.id}}-author" class="form-control" type="text" name="name" value="{{app.author.username}}" readonly=readonly>
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label>
<input autocomplete="off" id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>
{% if app.client_id %}
<label for="edit-{{app.id}}-client-id" class="mb-0 w-lg-25">Client ID</label>
<input autocomplete="off" id="edit-{{app.id}}-client-id" class="form-control" type="text" name="name" value="{{app.client_id}}" readonly="readonly">
{% endif %}
<label for="edit-{{app.id}}-redirect" class="mb-0 w-lg-25">Redirect URI</label>
<input autocomplete="off" id="edit-{{app.id}}-redirect" class="form-control" type="text" name="redirect_uri" value="{{app.redirect_uri}}" readonly="readonly">
<label for="edit-{{app.id}}-desc" class="mb-0 w-lg-25">Description</label>
<textarea rows="10" autocomplete="off" form="edit-app-{{app.id}}" class="form-control" name="description" id="edit-{{app.id}}-desc" maxlength="256" readonly="readonly">{{app.description}}</textarea>
</div>
</div>
<div class="footer">
<div class="d-flex">
{% if not app.client_id %}
<a role="button" class="btn btn-primary ml-auto" onclick="post_toast(this,'/admin/app/approve/{{app.id}}')">Approve</a>
<a role="button" class="btn btn-secondary mr-0" onclick="post_toast(this,'/admin/app/reject/{{app.id}}')">Reject</a>
{% else %}
<a role="button" class="btn btn-primary ml-auto" onclick="post_toast(this,'/admin/app/revoke/{{app.id}}')">Revoke</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text">Action successful!</span>
</div>
</div>
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
</div>
</div>
{% extends "default.html" %}
{% block title %}
<title>API App Administration</title>
{% endblock %}
{% block content %}
<div class="row">
<div class="col col-lg-8">
<div class="settings">
{% for app in apps %}
<div class="settings-section rounded">
<div class="d-lg-flex">
<div class="title w-lg-25">
<label for="over18"><a href="{{app.permalink}}" {% if v and v.newtab and not g.webview %}target="_blank"{% endif %}>{{app.app_name}}</a></label>
</div>
<div class="body w-lg-100">
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label>
<input autocomplete="off" id="edit-{{app.id}}-author" class="form-control" type="text" name="name" value="{{app.author.username}}" readonly=readonly>
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label>
<input autocomplete="off" id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>
{% if app.client_id %}
<label for="edit-{{app.id}}-client-id" class="mb-0 w-lg-25">Client ID</label>
<input autocomplete="off" id="edit-{{app.id}}-client-id" class="form-control" type="text" name="name" value="{{app.client_id}}" readonly="readonly">
{% endif %}
<label for="edit-{{app.id}}-redirect" class="mb-0 w-lg-25">Redirect URI</label>
<input autocomplete="off" id="edit-{{app.id}}-redirect" class="form-control" type="text" name="redirect_uri" value="{{app.redirect_uri}}" readonly="readonly">
<label for="edit-{{app.id}}-desc" class="mb-0 w-lg-25">Description</label>
<textarea rows="10" autocomplete="off" form="edit-app-{{app.id}}" class="form-control" name="description" id="edit-{{app.id}}-desc" maxlength="256" readonly="readonly">{{app.description}}</textarea>
</div>
</div>
<div class="footer">
<div class="d-flex">
{% if not app.client_id %}
<a role="button" class="btn btn-primary ml-auto" onclick="post_toast(this,'/admin/app/approve/{{app.id}}')">Approve</a>
<a role="button" class="btn btn-secondary mr-0" onclick="post_toast(this,'/admin/app/reject/{{app.id}}')">Reject</a>
{% else %}
<a role="button" class="btn btn-primary ml-auto" onclick="post_toast(this,'/admin/app/revoke/{{app.id}}')">Revoke</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text">Action successful!</span>
</div>
</div>
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
</div>
</div>
{% endblock %}

View File

@ -1,89 +1,89 @@
{% extends "default.html" %}
{% block title %}
<title>Badge Grant</title>
{% endblock %}
{% block pagetype %}message{% endblock %}
{% block content %}
{% if error %}
<div class="alert alert-danger alert-dismissible fade show my-3" role="alert">
<i class="fas fa-exclamation-circle my-auto"></i>
<span>
{{error}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
<span>
{{msg}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
<pre></pre>
<pre></pre>
<h5>Badge Grant</h5>
<form action="/admin/badge_grant", method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label for="input-username">Username</label><br>
<input autocomplete="off" id="input-username" class="form-control" type="text" name="username" required>
<div class="overflow-x-auto"><table class="table table-striped">
<thead class="bg-primary text-white">
<tr>
<th scope="col">Select</th>
<th scope="col">Image</th>
<th scope="col">Name</th>
<th scope="col">Default Description</th>
</tr>
</thead>
<tbody>
{% for badge in badge_types %}
<tr>
<td>
<div class="custom-control">
<input autocomplete="off" class="custom-control-input" type="radio" id="{{badge.id}}" name="badge_id" value="{{badge.id}}">
<label class="custom-control-label" for="{{badge.id}}"></label>
</div>
</td>
<td><label for="badge-{{badge.id}}"><img alt="{{badge.name}}" loading="lazy" src="/assets/images/badges/{{badge.id}}.webp?v=1016" width=64.16 height=70></label></td>
<td>{{badge.name}}</td>
<td>{{badge.description}}</td>
</tr>
{% endfor %}
</table>
<label for="input-url">URL</label><br>
<input autocomplete="off" id="input-url" class="form-control" type="text" name="url" type="url" placeholder="Optional">
<label for="input-description">Custom description</label><br>
<input autocomplete="off" id="input-description" class="form-control" type="text" name="description" placeholder="Leave blank for badge default">
<input autocomplete="off" class="btn btn-primary" type="submit">
</form>
<style>
@media (max-width: 767.98px) {
table {
display: inline-block;
overflow: auto;
}
}
</style>
{% endblock %}
{% extends "default.html" %}
{% block title %}
<title>Badge Grant</title>
{% endblock %}
{% block pagetype %}message{% endblock %}
{% block content %}
{% if error %}
<div class="alert alert-danger alert-dismissible fade show my-3" role="alert">
<i class="fas fa-exclamation-circle my-auto"></i>
<span>
{{error}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
<span>
{{msg}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
<pre></pre>
<pre></pre>
<h5>Badge Grant</h5>
<form action="/admin/badge_grant", method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label for="input-username">Username</label><br>
<input autocomplete="off" id="input-username" class="form-control" type="text" name="username" required>
<div class="overflow-x-auto"><table class="table table-striped">
<thead class="bg-primary text-white">
<tr>
<th scope="col">Select</th>
<th scope="col">Image</th>
<th scope="col">Name</th>
<th scope="col">Default Description</th>
</tr>
</thead>
<tbody>
{% for badge in badge_types %}
<tr>
<td>
<div class="custom-control">
<input autocomplete="off" class="custom-control-input" type="radio" id="{{badge.id}}" name="badge_id" value="{{badge.id}}">
<label class="custom-control-label" for="{{badge.id}}"></label>
</div>
</td>
<td><label for="badge-{{badge.id}}"><img alt="{{badge.name}}" loading="lazy" src="/assets/images/badges/{{badge.id}}.webp?v=1016" width=64.16 height=70></label></td>
<td>{{badge.name}}</td>
<td>{{badge.description}}</td>
</tr>
{% endfor %}
</table>
<label for="input-url">URL</label><br>
<input autocomplete="off" id="input-url" class="form-control" type="text" name="url" type="url" placeholder="Optional">
<label for="input-description">Custom description</label><br>
<input autocomplete="off" id="input-description" class="form-control" type="text" name="description" placeholder="Leave blank for badge default">
<input autocomplete="off" class="btn btn-primary" type="submit">
</form>
<style>
@media (max-width: 767.98px) {
table {
display: inline-block;
overflow: auto;
}
}
</style>
{% endblock %}

View File

@ -1,37 +1,37 @@
{% extends "default.html" %}
{% block title %}
<title>Banned Domains</title>
{% endblock %}
{% block content %}
<pre>
</pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>Domain</th>
<th>Ban reason</th>
</tr>
</thead>
{% for domain in banned_domains %}
<tr>
<td>{{domain.domain}}</td>
<td>{{domain.reason}}</td>
</tr>
{% endfor %}
</table>
<form action="/admin/banned_domains" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input autocomplete="off" name="domain" placeholder="Enter domain here.." class="form-control" required>
<input autocomplete="off" name="reason" placeholder="Enter ban reason here.." oninput="document.getElementById('ban-submit').disabled=false" class="form-control">
<input autocomplete="off" id="ban-submit" type="submit" class="btn btn-primary" value="Toggle ban" disabled>
</form>
{% extends "default.html" %}
{% block title %}
<title>Banned Domains</title>
{% endblock %}
{% block content %}
<pre>
</pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>Domain</th>
<th>Ban reason</th>
</tr>
</thead>
{% for domain in banned_domains %}
<tr>
<td>{{domain.domain}}</td>
<td>{{domain.reason}}</td>
</tr>
{% endfor %}
</table>
<form action="/admin/banned_domains" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input autocomplete="off" name="domain" placeholder="Enter domain here.." class="form-control" required>
<input autocomplete="off" name="reason" placeholder="Enter ban reason here.." oninput="document.getElementById('ban-submit').disabled=false" class="form-control">
<input autocomplete="off" id="ban-submit" type="submit" class="btn btn-primary" value="Toggle ban" disabled>
</form>
{% endblock %}

View File

@ -1,25 +1,25 @@
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}}</title>
{% endblock %}
{% block content %}
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>Statistic</th>
<th>Value</th>
</tr>
</thead>
{% for entry in data %}
<tr>
<td>{{entry}}</td>
<td>{{data[entry]}}</td>
</tr>
{% endfor %}
</table>
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}}</title>
{% endblock %}
{% block content %}
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>Statistic</th>
<th>Value</th>
</tr>
</thead>
{% for entry in data %}
<tr>
<td>{{entry}}</td>
<td>{{data[entry]}}</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -1,56 +1,56 @@
{% extends "userpage.html" %}
{% block adminpanel %}{% endblock %}
{% block pagetype %}userpage{% endblock %}
{% block banner %}{% endblock %}
{% block mobileBanner %}{% endblock %}
{% block desktopBanner %}{% endblock %}
{% block desktopUserBanner %}{% endblock %}
{% block mobileUserBanner %}{% endblock %}
{% block postNav %}{% endblock %}
{% block fixedMobileBarJS %}
{% endblock %}
{% block title %}
<title>Image feed</title>
{% endblock %}
{% block content %}
<div class="row no-gutters">
<div class="col">
{% block listing %}
<div class="posts">
{% include "submission_listing.html" %}
</div>
{% endblock %}
</div>
</div>
{% endblock %}
{% block pagenav %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
{% if page>1 %}
<li class="page-item">
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Prev</span></li>
{% endif %}
{% if next_exists %}
<li class="page-item">
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% extends "userpage.html" %}
{% block adminpanel %}{% endblock %}
{% block pagetype %}userpage{% endblock %}
{% block banner %}{% endblock %}
{% block mobileBanner %}{% endblock %}
{% block desktopBanner %}{% endblock %}
{% block desktopUserBanner %}{% endblock %}
{% block mobileUserBanner %}{% endblock %}
{% block postNav %}{% endblock %}
{% block fixedMobileBarJS %}
{% endblock %}
{% block title %}
<title>Image feed</title>
{% endblock %}
{% block content %}
<div class="row no-gutters">
<div class="col">
{% block listing %}
<div class="posts">
{% include "submission_listing.html" %}
</div>
{% endblock %}
</div>
</div>
{% endblock %}
{% block pagenav %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
{% if page>1 %}
<li class="page-item">
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Prev</span></li>
{% endif %}
{% if next_exists %}
<li class="page-item">
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "mine.html" %}
{% block maincontent %}
{% include "user_listing.html" %}
{% endblock %}
{% extends "mine.html" %}
{% block maincontent %}
{% include "user_listing.html" %}
{% endblock %}
{% block navbar %}{% endblock %}

View File

@ -1,66 +1,66 @@
{% extends "userpage.html" %}
{% block adminpanel %}{% endblock %}
{% block pagetype %}userpage{% endblock %}
{% block banner %}{% endblock %}
{% block mobileBanner %}{% endblock %}
{% block desktopBanner %}{% endblock %}
{% block desktopUserBanner %}{% endblock %}
{% block mobileUserBanner %}{% endblock %}
{% block fixedMobileBarJS %}
{% endblock %}
{% block title %}
<title>removed Posts</title>
{% endblock %}
{% block content %}
<ul class="nav post-nav py-2">
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/removed/posts" %} active{% endif %}" href="/admin/removed/posts">
<div>Posts</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/removed/comments" %} active{% endif %}" href="/admin/removed/comments">
<div>Comments</div>
</a>
</li>
</ul>
<div class="row no-gutters">
<div class="col">
{% block listing %}
<div class="posts">
{% include "submission_listing.html" %}
</div>
{% endblock %}
</div>
</div>
{% endblock %}
{% block pagenav %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
{% if page>1 %}
<li class="page-item">
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Prev</span></li>
{% endif %}
{% if next_exists %}
<li class="page-item">
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% extends "userpage.html" %}
{% block adminpanel %}{% endblock %}
{% block pagetype %}userpage{% endblock %}
{% block banner %}{% endblock %}
{% block mobileBanner %}{% endblock %}
{% block desktopBanner %}{% endblock %}
{% block desktopUserBanner %}{% endblock %}
{% block mobileUserBanner %}{% endblock %}
{% block fixedMobileBarJS %}
{% endblock %}
{% block title %}
<title>removed Posts</title>
{% endblock %}
{% block content %}
<ul class="nav post-nav py-2">
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/removed/posts" %} active{% endif %}" href="/admin/removed/posts">
<div>Posts</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/removed/comments" %} active{% endif %}" href="/admin/removed/comments">
<div>Comments</div>
</a>
</li>
</ul>
<div class="row no-gutters">
<div class="col">
{% block listing %}
<div class="posts">
{% include "submission_listing.html" %}
</div>
{% endblock %}
</div>
</div>
{% endblock %}
{% block pagenav %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
{% if page>1 %}
<li class="page-item">
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Prev</span></li>
{% endif %}
{% if next_exists %}
<li class="page-item">
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% endblock %}

View File

@ -1,26 +1,26 @@
{% extends "admin/reported_posts.html" %}
{% block title %}
<title>Comments</title>
{% endblock %}
{% block listing %}
<div class="posts">
{% with comments=listing %}
{% include "comments.html" %}
{% endwith %}
{% if not listing %}
<div class="row no-gutters">
<div class="col">
<div class="text-center py-7">
<div class="h4 p-2">There are no comments here (yet).</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% extends "admin/reported_posts.html" %}
{% block title %}
<title>Comments</title>
{% endblock %}
{% block listing %}
<div class="posts">
{% with comments=listing %}
{% include "comments.html" %}
{% endwith %}
{% if not listing %}
<div class="row no-gutters">
<div class="col">
<div class="text-center py-7">
<div class="h4 p-2">There are no comments here (yet).</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -1,66 +1,66 @@
{% extends "userpage.html" %}
{% block adminpanel %}{% endblock %}
{% block pagetype %}userpage{% endblock %}
{% block banner %}{% endblock %}
{% block mobileBanner %}{% endblock %}
{% block desktopBanner %}{% endblock %}
{% block desktopUserBanner %}{% endblock %}
{% block mobileUserBanner %}{% endblock %}
{% block fixedMobileBarJS %}
{% endblock %}
{% block title %}
<title>Reported Posts</title>
{% endblock %}
{% block content %}
<ul class="nav post-nav py-2">
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/reported/posts" %} active{% endif %}" href="/admin/reported/posts">
<div>Posts</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/reported/comments" %} active{% endif %}" href="/admin/reported/comments">
<div>Comments</div>
</a>
</li>
</ul>
<div class="row no-gutters">
<div class="col">
{% block listing %}
<div class="posts">
{% include "submission_listing.html" %}
</div>
{% endblock %}
</div>
</div>
{% endblock %}
{% block pagenav %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
{% if page>1 %}
<li class="page-item">
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Prev</span></li>
{% endif %}
{% if next_exists %}
<li class="page-item">
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% extends "userpage.html" %}
{% block adminpanel %}{% endblock %}
{% block pagetype %}userpage{% endblock %}
{% block banner %}{% endblock %}
{% block mobileBanner %}{% endblock %}
{% block desktopBanner %}{% endblock %}
{% block desktopUserBanner %}{% endblock %}
{% block mobileUserBanner %}{% endblock %}
{% block fixedMobileBarJS %}
{% endblock %}
{% block title %}
<title>Reported Posts</title>
{% endblock %}
{% block content %}
<ul class="nav post-nav py-2">
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/reported/posts" %} active{% endif %}" href="/admin/reported/posts">
<div>Posts</div>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path=="/admin/reported/comments" %} active{% endif %}" href="/admin/reported/comments">
<div>Comments</div>
</a>
</li>
</ul>
<div class="row no-gutters">
<div class="col">
{% block listing %}
<div class="posts">
{% include "submission_listing.html" %}
</div>
{% endblock %}
</div>
</div>
{% endblock %}
{% block pagenav %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm py-3 pl-3 mb-0">
{% if page>1 %}
<li class="page-item">
<small><a class="page-link" href="?page={{page-1}}" tabindex="-1">Prev</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Prev</span></li>
{% endif %}
{% if next_exists %}
<li class="page-item">
<small><a class="page-link" href="?page={{page+1}}">Next</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% endblock %}

View File

@ -1,31 +1,31 @@
{% extends "settings2.html" %}
{% block pagetitle %}Admins{% endblock %}
{% block content %}
<script src="/assets/js/sort_table.js?v=242"></script>
<pre class="d-none d-md-inline-block"></pre>
<h5 style="font-weight:bold;">Admins</h5>
<pre></pre>
<div class="overflow-x-auto">
<table id="sortable_table" class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th role="button" onclick="sort_table(2)" style="text-align:right;">Truescore</th>
<th role="button" onclick="sort_table(3)" style="text-align:right;">Mod actions</th>
</tr>
</thead>
{% for user in admins %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a>{% if user.admin_level == 1 and v and v.admin_level > 1 %}<i class="fas fa-broom align-middle ml-2 color-white" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Meme Admin"></i>{% endif %}</td>
<td style="text-align:right;">{{user.truecoins}}</td>
<td style="text-align:right;"><a href="/log?admin={{user.username}}">{{user.modaction_num}}</a></td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% extends "settings2.html" %}
{% block pagetitle %}Admins{% endblock %}
{% block content %}
<script src="/assets/js/sort_table.js?v=242"></script>
<pre class="d-none d-md-inline-block"></pre>
<h5 style="font-weight:bold;">Admins</h5>
<pre></pre>
<div class="overflow-x-auto">
<table id="sortable_table" class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th role="button" onclick="sort_table(2)" style="text-align:right;">Truescore</th>
<th role="button" onclick="sort_table(3)" style="text-align:right;">Mod actions</th>
</tr>
</thead>
{% for user in admins %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a>{% if user.admin_level == 1 and v and v.admin_level > 1 %}<i class="fas fa-broom align-middle ml-2 color-white" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Meme Admin"></i>{% endif %}</td>
<td style="text-align:right;">{{user.truecoins}}</td>
<td style="text-align:right;"><a href="/log?admin={{user.username}}">{{user.modaction_num}}</a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -1,112 +1,112 @@
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}} - API</title>
{% endblock %}
{% block content %}
<pre>
</pre>
<h1>API Guide for Bots</h1>
<pre></pre>
<p>This page explains how to obtain and use an access token. </p>
<h2>Step 1: Create your Application</h2>
<p>In the <a href="/settings/apps">apps tab of {{SITE_NAME}} settings</a>, fill in and submit the form to request an access token. You will need:</p>
<ul>
<li>an application name</li>
<li>a Redirect URI. May not use HTTP unless using localhost (use HTTPS instead).</li>
<li>a brief description of what your bot is intended to do</li>
</ul>
<p>Don't worry too much about accuracy; you will be able to change all of these later.</p>
<p>{{SITE_NAME}} administrators will review and approve or deny your request for an access token. You'll know when your request has been approved when you get a private message with an access token tied to your account.</p>
<p>DO NOT reveal your Client ID or Access Token. Anyone with these information will be able to pretend to be you. You are responsible for keeping them a secret!</p>
<h2>Step 2: Using the Access Token</h2>
<p>To use the access token, include the following header in subsequent API requests to {{SITE_NAME}}: <code>Authorization: access_token_goes_here</code></p>
<p>Python example:</p>
<pre> import requests
headers={"Authorization": "access_token_goes_here"}
url="{{SITE_FULL}}/?sort=comments"
r=requests.get(url, headers=headers)
print(r.json())
</pre>
<p>The expected result of this would be a large JSON representation of the posts on the frontpage sorted by the number of comments</p>
<br>
<p>Aother python example:</p>
<pre> import requests
headers={"Authorization": "access_token_goes_here"}
url="{{SITE_FULL}}/unread"
r=requests.get(url, headers=headers)
print(r.json())
</pre>
<p>The expected result of this would be a JSON representation of unread notifications for your account</p>
<pre>
</pre>
<h1>API Guide for Applications</h1>
<pre></pre>
<p>The OAuth2 authorization flow is used to enable users to authorize third-party applications to access their {{SITE_NAME}} account without having to provide their login information to the application.</p>
<p>This page explains how to obtain API application keys, how to prompt a user for authorization, and how to obtain and use access tokens. </p>
<h2>Step 1: Create your Application</h2>
<p>In the <a href="/settings/apps">apps tab of {{SITE_NAME}} settings</a>, fill in and submit the form to request new API keys. You will need:</p>
<ul>
<li>an application name</li>
<li>a Redirect URI. May not use HTTP unless using localhost (use HTTPS instead).</li>
<li>a brief description of what your application is intended to do</li>
</ul>
<p>Don't worry too much about accuracy; you will be able to change all of these later.</p>
<p>{{SITE_NAME}} administrators will review and approve or deny your request for API keys. You'll know when your request has been approved when you get a private message with an access token tied to your account.</p>
<p>DO NOT reveal your Client ID or Access Token. Anyone with these information will be able to pretend to be you. You are responsible for keeping them a secret!</p>
<h2>Step 2: Prompt Your User for Authorization</h2>
<p>Send your user to <code>{{SITE_FULL}}/authorize/?client_id=YOUR_CLIENT_ID</code></p>
<p>If done correctly, the user will see that your application wants to access their {{SITE_NAME}} account, and be prompted to approve or deny the request.</p>
<h2>Step 3: Catch the redirect</h2>
<p>The user clicks "Authorize". {{SITE_NAME}} will redirect the user's browser to GET the designated redirect URI. The access token URL parameter will be included in the redirect, which your server should process.</p>
<h2>Step 4: Using the Access Token</h2>
<p>To use the access token, include the following header in subsequent API requests to {{SITE_NAME}}: <code>Authorization: access_token_goes_here</code></p>
<p>Python example:</p>
<pre> import requests
headers={"Authorization": "access_token_goes_here"}
url="{{SITE_FULL}}/?sort=comments"
r=requests.get(url, headers=headers)
print(r.json())
</pre>
<p>The expected result of this would be a large JSON representation of the posts on the frontpage sorted by the number of comments</p>
<br>
<p>Aother python example:</p>
<pre> import requests
headers={"Authorization": "access_token_goes_here"}
url="{{SITE_FULL}}/unread"
r=requests.get(url, headers=headers)
print(r.json())
</pre>
<p>The expected result of this would be a JSON representation of unread notifications for your account</p>
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}} - API</title>
{% endblock %}
{% block content %}
<pre>
</pre>
<h1>API Guide for Bots</h1>
<pre></pre>
<p>This page explains how to obtain and use an access token. </p>
<h2>Step 1: Create your Application</h2>
<p>In the <a href="/settings/apps">apps tab of {{SITE_NAME}} settings</a>, fill in and submit the form to request an access token. You will need:</p>
<ul>
<li>an application name</li>
<li>a Redirect URI. May not use HTTP unless using localhost (use HTTPS instead).</li>
<li>a brief description of what your bot is intended to do</li>
</ul>
<p>Don't worry too much about accuracy; you will be able to change all of these later.</p>
<p>{{SITE_NAME}} administrators will review and approve or deny your request for an access token. You'll know when your request has been approved when you get a private message with an access token tied to your account.</p>
<p>DO NOT reveal your Client ID or Access Token. Anyone with these information will be able to pretend to be you. You are responsible for keeping them a secret!</p>
<h2>Step 2: Using the Access Token</h2>
<p>To use the access token, include the following header in subsequent API requests to {{SITE_NAME}}: <code>Authorization: access_token_goes_here</code></p>
<p>Python example:</p>
<pre> import requests
headers={"Authorization": "access_token_goes_here"}
url="{{SITE_FULL}}/?sort=comments"
r=requests.get(url, headers=headers)
print(r.json())
</pre>
<p>The expected result of this would be a large JSON representation of the posts on the frontpage sorted by the number of comments</p>
<br>
<p>Aother python example:</p>
<pre> import requests
headers={"Authorization": "access_token_goes_here"}
url="{{SITE_FULL}}/unread"
r=requests.get(url, headers=headers)
print(r.json())
</pre>
<p>The expected result of this would be a JSON representation of unread notifications for your account</p>
<pre>
</pre>
<h1>API Guide for Applications</h1>
<pre></pre>
<p>The OAuth2 authorization flow is used to enable users to authorize third-party applications to access their {{SITE_NAME}} account without having to provide their login information to the application.</p>
<p>This page explains how to obtain API application keys, how to prompt a user for authorization, and how to obtain and use access tokens. </p>
<h2>Step 1: Create your Application</h2>
<p>In the <a href="/settings/apps">apps tab of {{SITE_NAME}} settings</a>, fill in and submit the form to request new API keys. You will need:</p>
<ul>
<li>an application name</li>
<li>a Redirect URI. May not use HTTP unless using localhost (use HTTPS instead).</li>
<li>a brief description of what your application is intended to do</li>
</ul>
<p>Don't worry too much about accuracy; you will be able to change all of these later.</p>
<p>{{SITE_NAME}} administrators will review and approve or deny your request for API keys. You'll know when your request has been approved when you get a private message with an access token tied to your account.</p>
<p>DO NOT reveal your Client ID or Access Token. Anyone with these information will be able to pretend to be you. You are responsible for keeping them a secret!</p>
<h2>Step 2: Prompt Your User for Authorization</h2>
<p>Send your user to <code>{{SITE_FULL}}/authorize/?client_id=YOUR_CLIENT_ID</code></p>
<p>If done correctly, the user will see that your application wants to access their {{SITE_NAME}} account, and be prompted to approve or deny the request.</p>
<h2>Step 3: Catch the redirect</h2>
<p>The user clicks "Authorize". {{SITE_NAME}} will redirect the user's browser to GET the designated redirect URI. The access token URL parameter will be included in the redirect, which your server should process.</p>
<h2>Step 4: Using the Access Token</h2>
<p>To use the access token, include the following header in subsequent API requests to {{SITE_NAME}}: <code>Authorization: access_token_goes_here</code></p>
<p>Python example:</p>
<pre> import requests
headers={"Authorization": "access_token_goes_here"}
url="{{SITE_FULL}}/?sort=comments"
r=requests.get(url, headers=headers)
print(r.json())
</pre>
<p>The expected result of this would be a large JSON representation of the posts on the frontpage sorted by the number of comments</p>
<br>
<p>Aother python example:</p>
<pre> import requests
headers={"Authorization": "access_token_goes_here"}
url="{{SITE_FULL}}/unread"
r=requests.get(url, headers=headers)
print(r.json())
</pre>
<p>The expected result of this would be a JSON representation of unread notifications for your account</p>
{% endblock %}

View File

@ -1,125 +1,125 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="description" content="{{config('DESCRIPTION')}}">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none';">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="author" content="">
<title>{% block pagetitle %}{{SITE_NAME}}{% endblock %}</title>
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
{% endif %}
</head>
<body id="login">
<nav class="navbar navbar-expand-lg navbar-dark bg-transparent fixed-top border-0">
<div class="container-fluid">
<button class="navbar-toggler d-none" role="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</nav>
<div class="container-fluid position-absolute h-100 p-0">
<div class="row no-gutters h-100">
<div class="col-12 col-md-6 my-auto p-3">
<div class="row justify-content-center">
<div class="col-10 col-md-7">
<div class="mb-5">
<a href="/" class="text-decoration-none"><span class="h3 text-primary"></span></a>
</div>
<h1 class="h2">{% block authtitle %}{% endblock %}</h1>
<p class="text-muted mb-md-5">{% block authtext %}{% endblock %}</p>
{% if error %}
<div class="alert alert-danger alert-dismissible fade show d-flex my-3" role="alert">
<i class="fas fa-exclamation-circle my-auto"></i>
<span>
{{error}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show d-flex my-3" role="alert">
<i class="fas fa-info-circle my-auto" aria-hidden="true"></i>
<span>
{{msg}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
{% block content %}
{% endblock %}
</div>
</div>
</div>
<div class="col-12 col-md-6 d-none d-md-block">
<div class="splash-wrapper">
<div class="splash-overlay"></div>
<img alt="cover" loading="lazy" class="splash-img" src="/assets/images/{{SITE_NAME}}/cover.webp?v=1014"></img>
</div>
</div>
</div>
</div>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="description" content="{{config('DESCRIPTION')}}">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none';">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="author" content="">
<title>{% block pagetitle %}{{SITE_NAME}}{% endblock %}</title>
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=57">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=266">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
{% endif %}
</head>
<body id="login">
<nav class="navbar navbar-expand-lg navbar-dark bg-transparent fixed-top border-0">
<div class="container-fluid">
<button class="navbar-toggler d-none" role="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive"
aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</nav>
<div class="container-fluid position-absolute h-100 p-0">
<div class="row no-gutters h-100">
<div class="col-12 col-md-6 my-auto p-3">
<div class="row justify-content-center">
<div class="col-10 col-md-7">
<div class="mb-5">
<a href="/" class="text-decoration-none"><span class="h3 text-primary"></span></a>
</div>
<h1 class="h2">{% block authtitle %}{% endblock %}</h1>
<p class="text-muted mb-md-5">{% block authtext %}{% endblock %}</p>
{% if error %}
<div class="alert alert-danger alert-dismissible fade show d-flex my-3" role="alert">
<i class="fas fa-exclamation-circle my-auto"></i>
<span>
{{error}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show d-flex my-3" role="alert">
<i class="fas fa-info-circle my-auto" aria-hidden="true"></i>
<span>
{{msg}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
{% block content %}
{% endblock %}
</div>
</div>
</div>
<div class="col-12 col-md-6 d-none d-md-block">
<div class="splash-wrapper">
<div class="splash-overlay"></div>
<img alt="cover" loading="lazy" class="splash-img" src="/assets/images/{{SITE_NAME}}/cover.webp?v=1014"></img>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,47 +1,52 @@
<div class="modal fade" id="awardModal" tabindex="-1" role="dialog" aria-labelledby="awardModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered awardmodal my-5" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Give Award</h5>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
<div id="awardModalBody" class="modal-body mb-3">
<form id="awardTarget" class="pt-3 pb-0" action="" method="post">
<input type="hidden" name="formkey", value="{{v.formkey}}">
<div class="card-columns award-columns awards-wrapper">
{% for award in v.user_awards %}
<a role="button" id="{{award.kind}}" class="card" onclick="pick('{{award.kind}}', {{award.price}}*{{v.discount}} <= {{v.procoins}}, {{award.price}}*{{v.discount}} <= {{v.coins}})">
<i class="{{award.icon}} {{award.color}}"></i>
<div class="pt-2" style="font-weight: bold; font-size: 14px; color:#E1E1E1">{{award.title}}</div>
<div class="text-muted"><span id="{{award.kind}}-owned">{{award.owned}}</span> owned</div>
</a>
{% endfor %}
</div>
<label id="notelabel" for="note" class="pt-4">Note (optional):</label>
<input autocomplete="off" id="kind" name="kind" value="" hidden>
<textarea autocomplete="off" id="note" maxlength="200" name="note" class="form-control" placeholder="Note to include in award notification..."></textarea>
<input autocomplete="off" id="giveaward" class="awardbtn btn btn-primary mt-3" style="float:right" type="submit" value="Give Award" disabled>
<button id="buy1" class="awardbtn btn btn-primary mt-3 mx-3" type="button" disabled style="float:right" onclick="buy(true)">Buy with marseybux</button>
<button id="buy2" class="awardbtn btn btn-primary mt-3" type="button" disabled style="float:right" onclick="buy()">Buy with coins</button>
<pre>
</pre>
</form>
</div>
</div>
</div>
<div class="toast" id="toast-post-success2" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text2">Action successful!</span>
</div>
</div>
<div class="toast" id="toast-post-error2" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text2">Error, please try again later.</span>
</div>
</div>
</div>
<script src="/assets/js/award_modal.js?v=248" data-cfasync="false"></script>
<div class="modal fade" id="awardModal" tabindex="-1" role="dialog" aria-labelledby="awardModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered awardmodal my-5" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Give Award</h5>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
<div id="awardModalBody" class="modal-body mb-3">
<form id="awardTarget" class="pt-3 pb-0" action="" method="post">
<input type="hidden" name="formkey", value="{{v.formkey}}">
<div class="card-columns award-columns awards-wrapper">
{% for award in v.user_awards %}
<a role="button" id="{{award.kind}}" class="card" onclick="pick('{{award.kind}}', {{award.price}}*{{v.discount}} <= {{v.procoins}}, {{award.price}}*{{v.discount}} <= {{v.coins}})">
<i class="{{award.icon}} {{award.color}}"></i>
<div class="pt-2" style="font-weight: bold; font-size: 14px; color:#E1E1E1">{{award.title}}</div>
<div class="text-muted"><span id="{{award.kind}}-owned">{{award.owned}}</span> owned</div>
</a>
{% endfor %}
<a class="card disabled d-md-none" style="border:none">
<i class="fas fa-volume-mute" style="opacity:0"></i>
<div class="pt-2" style="font-weight: bold; font-size: 14px; color:#E1E1E1">&nbsp;</div>
<div class="text-muted">&nbsp;</div>
</a>
</div>
<label id="notelabel" for="note" class="pt-4">Note (optional):</label>
<input autocomplete="off" id="kind" name="kind" value="" hidden>
<textarea autocomplete="off" id="note" maxlength="200" name="note" class="form-control" placeholder="Note to include in award notification..."></textarea>
<input autocomplete="off" id="giveaward" class="awardbtn btn btn-primary mt-3" style="float:right" type="submit" value="Give Award" disabled>
<button id="buy1" class="awardbtn btn btn-primary mt-3 mx-3 {% if SITE_NAME in ('Cringetopia', 'WPD') %}d-none{% endif %}" type="button" disabled style="float:right" onclick="buy(true)">Buy with marseybux</button>
<button id="buy2" class="awardbtn btn btn-primary mt-3" type="button" disabled style="float:right" onclick="buy()">Buy</button>
<pre>
</pre>
</form>
</div>
</div>
</div>
<div class="toast" id="toast-post-success2" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text2">Action successful!</span>
</div>
</div>
<div class="toast" id="toast-post-error2" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text2">Error, please try again later.</span>
</div>
</div>
</div>
<script src="/assets/js/award_modal.js?v=249" data-cfasync="false"></script>

View File

@ -1,39 +1,39 @@
{% extends "default.html" %}
{% block content %}
<script src="/assets/js/sort_table.js?v=242"></script>
<pre>
</pre>
<h1>User Badges</h1>
<div>This page describes the requirements for obtaining all profile badges.</div>
<pre>
</pre>
<div class="overflow-x-auto">
<table id="sortable_table" class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Image</th>
<th>Description</th>
<th role="button" onclick="sort_table(4)">#</th>
<th role="button" onclick="sort_table(5)">Rarity</th>
</tr>
</thead>
{% for badge in badges %}
<tr>
<td>{{loop.index}}</td>
<td>{{badge.name}}</td>
<td><img alt="{{badge.name}}" loading="lazy" src="/assets/images/badges/{{badge.id}}.webp?v=1016" width=45.83 height=50>
<td>{{badge.description}}</td>
{%- set ct = counts[badge.id] if badge.id in counts else (0, 0) %}
<td class="badges-rarity-qty">{{ ct[0] }}</td>
<td class="badges-rarity-ratio">{{ "{:0.3f}".format(ct[1]) }}%</td>
</tr>
{% endfor %}
</table>
{% extends "default.html" %}
{% block content %}
<script src="/assets/js/sort_table.js?v=242"></script>
<pre>
</pre>
<h1>User Badges</h1>
<div>This page describes the requirements for obtaining all profile badges.</div>
<pre>
</pre>
<div class="overflow-x-auto">
<table id="sortable_table" class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Image</th>
<th>Description</th>
<th role="button" onclick="sort_table(4)">#</th>
<th role="button" onclick="sort_table(4)">Rarity</th>
</tr>
</thead>
{% for badge in badges %}
<tr>
<td>{{loop.index}}</td>
<td>{{badge.name}}</td>
<td><img alt="{{badge.name}}" loading="lazy" src="/assets/images/badges/{{badge.id}}.webp?v=1016" width=45.83 height=50>
<td>{{badge.description}}</td>
{%- set ct = counts[badge.id] if badge.id in counts else (0, 0) %}
<td class="badges-rarity-qty"><a href="/badge_owners/{{badge.id}}">{{ ct[0] }}</a></td>
<td class="badges-rarity-ratio">{{ "{:0.3f}".format(ct[1]) }}%</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -1,38 +1,38 @@
<script src="/assets/js/ban_modal.js?v=241"></script>
<div class="modal fade" id="banModal" tabindex="-1" role="dialog" aria-labelledby="banModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header pt-3">
<h5 id="banModalTitle"></h5>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
<div class="modal-body" id="ban-modal-body">
<form id="banModalForm">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label for="ban-modal-link">Public ban reason (optional)</label>
<textarea autocomplete="off" maxlength="64" name="reason" form="banModalForm" class="form-control" id="ban-modal-link" aria-label="With textarea" placeholder="Enter reason"></textarea>
<label for="days" class="mt-3">Duration days</label>
<input autocomplete="off" type="number" step="any" name="days" id="days" class="form-control" placeholder="leave blank for permanent">
<div class="custom-control custom-switch mt-3">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="alts" name="alts">
<label class="custom-control-label" for="alts">Ban known alts</label>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-link text-muted" data-bs-dismiss="modal">Cancel</button>
<button id="banUserButton" class="btn btn-danger" data-bs-dismiss="modal"></button>
</div>
</div>
</div>
<script src="/assets/js/ban_modal.js?v=241"></script>
<div class="modal fade" id="banModal" tabindex="-1" role="dialog" aria-labelledby="banModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header pt-3">
<h5 id="banModalTitle"></h5>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
<div class="modal-body" id="ban-modal-body">
<form id="banModalForm">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<label for="ban-modal-link">Public ban reason (optional)</label>
<textarea autocomplete="off" maxlength="64" name="reason" form="banModalForm" class="form-control" id="ban-modal-link" aria-label="With textarea" placeholder="Enter reason"></textarea>
<label for="days" class="mt-3">Duration days</label>
<input autocomplete="off" type="number" step="any" name="days" id="days" class="form-control" placeholder="leave blank for permanent">
<div class="custom-control custom-switch mt-3">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="alts" name="alts">
<label class="custom-control-label" for="alts">Ban known alts</label>
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn btn-link text-muted" data-bs-dismiss="modal">Cancel</button>
<button id="banUserButton" class="btn btn-danger" data-bs-dismiss="modal"></button>
</div>
</div>
</div>
</div>

View File

@ -1,23 +1,23 @@
{% extends "settings2.html" %}
{% block content %}
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Ban reason</th>
<th>Banned by</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
<td>{% if user.ban_reason %}{{user.ban_reason}}{% endif %}</td>
<td href="/@{{user.banned_by.username}}"><img loading="lazy" src="{{user.banned_by.profile_url}}" class="pp20"><span {% if user.banned_by.patron %}class="patron" style="background-color:#{{user.banned_by.namecolor}}"{% endif %}>{{user.banned_by.username}}</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% extends "settings2.html" %}
{% block content %}
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Ban reason</th>
<th>Banned by</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
<td>{% if user.ban_reason %}{{user.ban_reason}}{% endif %}</td>
<td href="/@{{user.banned_by.username}}"><img loading="lazy" src="{{user.banned_by.profile_url}}" class="pp20"><span {% if user.banned_by.patron %}class="patron" style="background-color:#{{user.banned_by.namecolor}}"{% endif %}>{{user.banned_by.username}}</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -1,24 +1,24 @@
{% extends "settings2.html" %}
{% block pagetitle %}Blocks{% endblock %}
{% block content %}
<h1> Blocks</h1>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>User</th>
<th>Target</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="font-weight:bold;color:#{{user.namecolor}}" href="/@{{user.username}}"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
<td><a style="font-weight:bold;color:#{{targets[loop.index-1].namecolor}}" href="/@{{targets[loop.index-1].username}}"><span {% if targets[loop.index-1].patron %}class="patron" style="background-color:#{{targets[loop.index-1].namecolor}}"{% endif %}>{{targets[loop.index-1].username}}</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% extends "settings2.html" %}
{% block pagetitle %}Blocks{% endblock %}
{% block content %}
<h1> Blocks</h1>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>User</th>
<th>Target</th>
</tr>
</thead>
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="font-weight:bold;color:#{{user.namecolor}}" href="/@{{user.username}}"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
<td><a style="font-weight:bold;color:#{{targets[loop.index-1].namecolor}}" href="/@{{targets[loop.index-1].username}}"><span {% if targets[loop.index-1].patron %}class="patron" style="background-color:#{{targets[loop.index-1].namecolor}}"{% endif %}>{{targets[loop.index-1].username}}</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

View File

@ -1,109 +1,109 @@
{% extends "settings2.html" %}
{% block pagetitle %}Changelog{% endblock %}
{% block desktopBanner %}
<div class="row" style="overflow: visible;padding-top:5px;">
<div class="col">
<div class="d-flex justify-content-between align-items-center">
{% block navbar %}
<div class="font-weight-bold py-3"></div>
<div class="d-flex align-items-center">
<div class="text-small font-weight-bold mr-2"></div>
<div class="dropdown dropdown-actions">
<button class="btn btn-secondary dropdown-toggle" role="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if t=="hour" %}<i class="fas fa-clock mr-1"></i>
{% elif t=="day" %}<i class="fas fa-calendar-day mr-1"></i>
{% elif t=="week" %}<i class="fas fa-calendar-week mr-1"></i>
{% elif t=="month" %}<i class="fas fa-calendar-alt mr-1"></i>
{% elif t=="year" %}<i class="fas fa-calendar mr-1"></i>
{% elif t=="all" %}<i class="fas fa-infinity mr-1"></i>
{% endif %}
{{t | capitalize}}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
{% if t != "hour" %}<a class="dropdown-item" href="?sort={{sort}}&t=hour"><i class="fas fa-clock mr-2"></i>Hour</a>{% endif %}
{% if t != "day" %}<a class="dropdown-item" href="?sort={{sort}}&t=day"><i class="fas fa-calendar-day mr-2"></i>Day</a>{% endif %}
{% if t != "week" %}<a class="dropdown-item" href="?sort={{sort}}&t=week"><i class="fas fa-calendar-week mr-2"></i>Week</a>{% endif %}
{% if t != "month" %}<a class="dropdown-item" href="?sort={{sort}}&t=month"><i class="fas fa-calendar-alt mr-2"></i>Month</a>{% endif %}
{% if t != "year" %}<a class="dropdown-item" href="?sort={{sort}}&t=year"><i class="fas fa-calendar mr-2"></i>Year</a>{% endif %}
{% if t != "all" %}<a class="dropdown-item" href="?sort={{sort}}&t=all"><i class="fas fa-infinity mr-2"></i>All</a>{% endif %}
</div>
</div>
<div class="text-small font-weight-bold ml-3 mr-2"></div>
<div class="dropdown dropdown-actions">
<button class="btn btn-secondary dropdown-toggle" role="button" id="dropdownMenuButton2" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if sort=="hot" %}<i class="fas fa-fire mr-1"></i>{% endif %}
{% if sort=="top" %}<i class="fas fa-arrow-alt-circle-up mr-1"></i>{% endif %}
{% if sort=="bottom" %}<i class="fas fa-arrow-alt-circle-down mr-1"></i>{% endif %}
{% if sort=="new" %}<i class="fas fa-sparkles mr-1"></i>{% endif %}
{% if sort=="old" %}<i class="fas fa-book mr-1"></i>{% endif %}
{% if sort=="controversial" %}<i class="fas fa-bullhorn mr-1"></i>{% endif %}
{% if sort=="comments" %}<i class="fas fa-comments mr-1"></i>{% endif %}
{{sort | capitalize}}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton2" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
{% if sort != "hot" %}<a class="dropdown-item" href="?sort=hot&t={{t}}"><i class="fas fa-fire mr-2"></i>Hot</a>{% endif %}
{% if sort != "top" %}<a class="dropdown-item" href="?sort=top&t={{t}}"><i class="fas fa-arrow-alt-circle-up mr-2"></i>Top</a>{% endif %}
{% if sort != "bottom" %}<a class="dropdown-item" href="?sort=bottom&t={{t}}"><i class="fas fa-arrow-alt-circle-down mr-2"></i>Bottom</a>{% endif %}
{% if sort != "new" %}<a class="dropdown-item" href="?sort=new&t={{t}}"><i class="fas fa-sparkles mr-2"></i>New</a>{% endif %}
{% if sort != "old" %}<a class="dropdown-item" href="?sort=old&t={{t}}"><i class="fas fa-book mr-2"></i>Old</a>{% endif %}
{% if sort != "controversial" %}<a class="dropdown-item" href="?sort=controversial&t={{t}}"><i class="fas fa-bullhorn mr-2"></i>Controversial</a>{% endif %}
{% if sort != "comments" %}<a class="dropdown-item" href="?sort=comments&t={{t}}"><i class="fas fa-comments mr-2"></i>Comments</a>{% endif %}
</div>
</div>
</div>
{% endblock %}
</div>
</div>
</div>
{% endblock %}
{% block content %}
{% if v %}
<a id="subscribe" class="{% if v.changelogsub %}d-none{% endif %} btn btn-primary followbutton" role="button" onclick="post_toast2(this, '/changelogsub','subscribe','unsubscribe')">Subscribe</a>
<a id="unsubscribe" class="{% if not v.changelogsub %}d-none{% endif %} btn btn-primary followbutton" role="button" onclick="post_toast2(this, '/changelogsub','subscribe','unsubscribe')">Unsubscribe</a>
{% endif %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}">
<div class="col-12">
<div class="posts" id="posts">
{% include "submission_listing.html" %}
</div>
</div>
</div>
{% if listing %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm mb-0">
{% if page>1 %}
<li class="page-item">
<small><a class="page-link" href="?sort={{sort}}&page={{page-1}}&t={{t}}{% if only %}&only={{only}}{% endif %}" tabindex="-1">Prev</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Prev</span></li>
{% endif %}
{% if next_exists %}
<li class="page-item">
<small><a class="page-link" href="?sort={{sort}}&page={{page+1}}&t={{t}}{% if only %}&only={{only}}{% endif %}">Next</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% endif %}
<script src="/assets/js/post_toast2.js?v=243"></script>
{% extends "settings2.html" %}
{% block pagetitle %}Changelog{% endblock %}
{% block desktopBanner %}
<div class="row" style="overflow: visible;padding-top:5px;">
<div class="col">
<div class="d-flex justify-content-between align-items-center">
{% block navbar %}
<div class="font-weight-bold py-3"></div>
<div class="d-flex align-items-center">
<div class="text-small font-weight-bold mr-2"></div>
<div class="dropdown dropdown-actions">
<button class="btn btn-secondary dropdown-toggle" role="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if t=="hour" %}<i class="fas fa-clock mr-1"></i>
{% elif t=="day" %}<i class="fas fa-calendar-day mr-1"></i>
{% elif t=="week" %}<i class="fas fa-calendar-week mr-1"></i>
{% elif t=="month" %}<i class="fas fa-calendar-alt mr-1"></i>
{% elif t=="year" %}<i class="fas fa-calendar mr-1"></i>
{% elif t=="all" %}<i class="fas fa-infinity mr-1"></i>
{% endif %}
{{t | capitalize}}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
{% if t != "hour" %}<a class="dropdown-item" href="?sort={{sort}}&t=hour"><i class="fas fa-clock mr-2"></i>Hour</a>{% endif %}
{% if t != "day" %}<a class="dropdown-item" href="?sort={{sort}}&t=day"><i class="fas fa-calendar-day mr-2"></i>Day</a>{% endif %}
{% if t != "week" %}<a class="dropdown-item" href="?sort={{sort}}&t=week"><i class="fas fa-calendar-week mr-2"></i>Week</a>{% endif %}
{% if t != "month" %}<a class="dropdown-item" href="?sort={{sort}}&t=month"><i class="fas fa-calendar-alt mr-2"></i>Month</a>{% endif %}
{% if t != "year" %}<a class="dropdown-item" href="?sort={{sort}}&t=year"><i class="fas fa-calendar mr-2"></i>Year</a>{% endif %}
{% if t != "all" %}<a class="dropdown-item" href="?sort={{sort}}&t=all"><i class="fas fa-infinity mr-2"></i>All</a>{% endif %}
</div>
</div>
<div class="text-small font-weight-bold ml-3 mr-2"></div>
<div class="dropdown dropdown-actions">
<button class="btn btn-secondary dropdown-toggle" role="button" id="dropdownMenuButton2" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if sort=="hot" %}<i class="fas fa-fire mr-1"></i>{% endif %}
{% if sort=="top" %}<i class="fas fa-arrow-alt-circle-up mr-1"></i>{% endif %}
{% if sort=="bottom" %}<i class="fas fa-arrow-alt-circle-down mr-1"></i>{% endif %}
{% if sort=="new" %}<i class="fas fa-sparkles mr-1"></i>{% endif %}
{% if sort=="old" %}<i class="fas fa-book mr-1"></i>{% endif %}
{% if sort=="controversial" %}<i class="fas fa-bullhorn mr-1"></i>{% endif %}
{% if sort=="comments" %}<i class="fas fa-comments mr-1"></i>{% endif %}
{{sort | capitalize}}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton2" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
{% if sort != "hot" %}<a class="dropdown-item" href="?sort=hot&t={{t}}"><i class="fas fa-fire mr-2"></i>Hot</a>{% endif %}
{% if sort != "top" %}<a class="dropdown-item" href="?sort=top&t={{t}}"><i class="fas fa-arrow-alt-circle-up mr-2"></i>Top</a>{% endif %}
{% if sort != "bottom" %}<a class="dropdown-item" href="?sort=bottom&t={{t}}"><i class="fas fa-arrow-alt-circle-down mr-2"></i>Bottom</a>{% endif %}
{% if sort != "new" %}<a class="dropdown-item" href="?sort=new&t={{t}}"><i class="fas fa-sparkles mr-2"></i>New</a>{% endif %}
{% if sort != "old" %}<a class="dropdown-item" href="?sort=old&t={{t}}"><i class="fas fa-book mr-2"></i>Old</a>{% endif %}
{% if sort != "controversial" %}<a class="dropdown-item" href="?sort=controversial&t={{t}}"><i class="fas fa-bullhorn mr-2"></i>Controversial</a>{% endif %}
{% if sort != "comments" %}<a class="dropdown-item" href="?sort=comments&t={{t}}"><i class="fas fa-comments mr-2"></i>Comments</a>{% endif %}
</div>
</div>
</div>
{% endblock %}
</div>
</div>
</div>
{% endblock %}
{% block content %}
{% if v %}
<a id="subscribe" class="{% if v.changelogsub %}d-none{% endif %} btn btn-primary followbutton" role="button" onclick="post_toast2(this, '/changelogsub','subscribe','unsubscribe')">Subscribe</a>
<a id="unsubscribe" class="{% if not v.changelogsub %}d-none{% endif %} btn btn-primary followbutton" role="button" onclick="post_toast2(this, '/changelogsub','subscribe','unsubscribe')">Unsubscribe</a>
{% endif %}
<div class="row no-gutters {% if listing %}mt-md-3{% elif not listing %}my-md-3{% endif %}">
<div class="col-12">
<div class="posts" id="posts">
{% include "submission_listing.html" %}
</div>
</div>
</div>
{% if listing %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm mb-0">
{% if page>1 %}
<li class="page-item">
<small><a class="page-link" href="?sort={{sort}}&page={{page-1}}&t={{t}}{% if only %}&only={{only}}{% endif %}" tabindex="-1">Prev</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Prev</span></li>
{% endif %}
{% if next_exists %}
<li class="page-item">
<small><a class="page-link" href="?sort={{sort}}&page={{page+1}}&t={{t}}{% if only %}&only={{only}}{% endif %}">Next</a></small>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% endif %}
<script src="/assets/js/post_toast2.js?v=243"></script>
{% endblock %}

View File

@ -14,8 +14,8 @@
<title>Chat</title>
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=57">
{% if v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
@ -188,12 +188,12 @@
<input id="site_name" type="hidden" value="{{SITE_NAME}}">
<input id="slurreplacer" type="hidden" value="{{v.slurreplacer}}">
<script src="/chat.js?v=16"></script>
<script src="/chat.js?v=20"></script>
{% include "emoji_modal.html" %}
{% include "expanded_image_modal.html" %}
<script src="/assets/js/lozad.js?v=242"></script>
<script src="/assets/js/lite-youtube.js?v=240"></script>
<script src="/assets/js/lite-youtube.js?v=241"></script>
</body>

View File

@ -207,7 +207,7 @@
{% if not c.author %}
{{c.print()}}
{% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{c.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color:#{{c.author.namecolor}}; font-size:12px; font-weight:bold;"><img loading="lazy" src="{{c.author.profile_url}}" class="profile-pic-25 mr-2"><img class="party-hat" src="/assets/images/party-hat.png"><span {% if c.author.patron and not c.distinguish_level %}class="patron" style="background-color:#{{c.author.namecolor}};"{% elif c.distinguish_level %}class="mod"{% endif %}>{{c.author_name}}</span></a>
<a class="user-name text-decoration-none" onclick='popclick({{c.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color:#{{c.author.namecolor}}; font-size:12px; font-weight:bold;"><img loading="lazy" src="{{c.author.profile_url}}" class="profile-pic-25 mr-2">{% if c.author.is_cakeday or True %}<img class="party-hat" src="/assets/images/party-hat.webp" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Ive spent another year rotting my brain with dramaposting, please ridicule me 🤓">{% endif %}<span {% if c.author.patron and not c.distinguish_level %}class="patron" style="background-color:#{{c.author.namecolor}};"{% elif c.distinguish_level %}class="mod"{% endif %}>{{c.author_name}}</span></a>
{% if c.author.customtitle %}&nbsp;<bdi style="color: #{{c.author.titlecolor}}">&nbsp;{{c.author.customtitle | safe}}</bdi>{% endif %}
{% endif %}
@ -599,9 +599,7 @@
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea required autocomplete="off" minlength="1" maxlength="10000" name="body" form="reply-to-t3_{{c.id}}" data-id="{{c.id}}" class="comment-box form-control rounded" id="reply-form-body-{{c.id}}" aria-label="With textarea" rows="3" oninput="markdown('reply-form-body-{{c.id}}', 'message-reply-{{c.id}}')"></textarea>
<div class="comment-format" id="comment-format-bar-{{c.id}}">
<label class="btn btn-secondary m-0 mt-3 mr-1" onclick="loadEmojis('reply-form-body-{{c.id}}')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji">
<i class="fas fa-smile-beam"></i>
</label>
<div onclick="loadEmojis('reply-form-body-{{c.id}}')" class="btn btn-secondary m-0 mt-3 mr-1" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"><i class="fas fa-smile-beam"></i></div>
{% if c.sentto == 2 %}
<label class="btn btn-secondary m-0 mt-3" for="file-upload">
@ -847,8 +845,8 @@
{% endif %}
{% if v %}
<script src="/assets/js/marked.js?v=251"></script>
<script src="/assets/js/comments_v.js?v=266"></script>
<script src="/assets/js/marked.js?v=253"></script>
<script src="/assets/js/comments_v.js?v=269"></script>
{% endif %}
<script src="/assets/js/clipboard.js?v=250"></script>
@ -860,7 +858,7 @@
{% include "expanded_image_modal.html" %}
<script src="/assets/js/comments+submission_listing.js?v=257"></script>
<script src="/assets/js/comments.js?v=256"></script>
<script src="/assets/js/comments.js?v=257"></script>
<script>
{% if p and (not v or v.highlightcomments) %}
@ -924,4 +922,4 @@
<div id="viewmore-{{offset}}"><button id="viewbtn" class="btn btn-primary" onclick="viewmore({{pid}},'{{sort}}',{{offset}},{{ids}})">VIEW MORE COMMENTS</a></div>
{% endif %}
</body>
</body>

View File

@ -1,58 +1,58 @@
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}} - Contact</title>
{% endblock %}
{% block content %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
<span>
{{msg}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
<h1 class="article-title">Contact {{SITE_NAME}} Admins</h1>
<p>Use this form to contact {{SITE_NAME}} Admins.</p>
<label class="mt-3">Your Email</label>
<input autocomplete="off" class="form-control" value="{{v.email}}" readonly="readonly" disabled>
<form id="contactform" action="/send_admin" method="post" enctype="multipart/form-data">
<label for="input-message" class="mt-3">Your message</label>
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea autocomplete="off" maxlength="10000" id="input-message" form="contactform" name="message" class="form-control" required></textarea>
<label class="btn btn-secondary format m-0 mt-3" onclick="loadEmojis('input-message')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji">
<i class="fas fa-smile-beam"></i>
</label>
<label class="btn btn-secondary m-0 mt-3" for="file-upload">
<div id="filename"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload" type="file" name="file" accept="image/*, video/*" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename','file-upload')" hidden>
</label>
<input type="submit" value="Submit" class="btn btn-primary mt-3">
</form>
<pre>
</pre>
<p>If you can see this line, we haven't been contacted by any law enforcement or governmental organizations in 2022 yet.</p>
<pre>
</pre>
{% include "emoji_modal.html" %}
{% endblock %}
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}} - Contact</title>
{% endblock %}
{% block content %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
<span>
{{msg}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
<h1 class="article-title">Contact {{SITE_NAME}} Admins</h1>
<p>Use this form to contact {{SITE_NAME}} Admins.</p>
<label class="mt-3">Your Email</label>
<input autocomplete="off" class="form-control" value="{{v.email}}" readonly="readonly" disabled>
<form id="contactform" action="/send_admin" method="post" enctype="multipart/form-data">
<label for="input-message" class="mt-3">Your message</label>
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea autocomplete="off" maxlength="10000" id="input-message" form="contactform" name="message" class="form-control" required></textarea>
<label class="btn btn-secondary format m-0 mt-3" onclick="loadEmojis('input-message')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji">
<i class="fas fa-smile-beam"></i>
</label>
<label class="btn btn-secondary m-0 mt-3" for="file-upload">
<div id="filename"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload" type="file" name="file" accept="image/*, video/*" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename','file-upload')" hidden>
</label>
<input type="submit" value="Submit" class="btn btn-primary mt-3">
</form>
<pre>
</pre>
<p>If you can see this line, we haven't been contacted by any law enforcement or governmental organizations in 2022 yet.</p>
<pre>
</pre>
{% include "emoji_modal.html" %}
{% endblock %}

View File

@ -1,358 +1,359 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="description" content="{{config('DESCRIPTION')}}">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval' ajax.cloudflare.com; connect-src 'self' tls-use1.fpapi.io api.fpjs.io {% if PUSHER_ID != 'blahblahblah' %}{{PUSHER_ID}}.pushnotifications.pusher.com{% endif %}; object-src 'none';">
<script src="/assets/js/bootstrap.js?v=245"></script>
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
<link rel="stylesheet" href="/assets/css/awards.css">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/awards.css">
{% endif %}
{% if request.path == '/catalog' %}
<link rel="stylesheet" href="/assets/css/catalog.css?v=1">
{% endif %}
{% if sub and sub.css and not request.path.endswith('settings') %}
<link rel="stylesheet" href="/h/{{sub.name}}/css" type="text/css">
{% endif %}
{% if v and v.themecolor == '30409f' %}
<style>
p a {
color: #2a96f3;
}
</style>
{% endif %}
{% if SITE_NAME == 'rDrama' %}
<style>
.mod:before {
content: '(((';
}
.mod:after {
content: ')))';
}
</style>
{% endif %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="thumbnail" content="/assets/images/{{SITE_NAME}}/site_preview.webp?v=1015">
<link rel="icon" type="image/png" href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015">
{% block title %}
<title>{{SITE_NAME}}</title>
<meta property="og:type" content="article">
<meta property="og:title" content="{{SITE_NAME}}">
<meta property="og:site_name" content="{{request.host}}">
<meta property="og:image" content="/assets/images/{{SITE_NAME}}/site_preview.webp?v=1015">
<meta property="og:url" content="{{SITE_FULL}}{{request.full_path}}">
<meta property="og:description" name="description" content="{{SITE_NAME}} - {{config('DESCRIPTION')}}">
<meta property="og:author" name="author" content="{{SITE_FULL}}">
<meta property="og:site_name" content="{{request.host}}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="{{SITE_FULL}}">
<meta name="twitter:title" content="{{SITE_NAME}}">
<meta name="twitter:creator" content="{{SITE_FULL}}">
<meta name="twitter:description" content="{{SITE_NAME}} - {{config('DESCRIPTION')}}">
<meta name="twitter:image" content="/assets/images/{{SITE_NAME}}/site_preview.webp?v=1015">
<meta name="twitter:url" content="{{SITE_FULL}}{{request.full_path}}">
{% endblock %}
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015">
<link rel="manifest" href="/assets/manifest_{{SITE_NAME}}.json?v=1">
<link rel="mask-icon" href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015">
<link rel="shortcut icon" href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015">
<meta name="apple-mobile-web-app-title" content="{{SITE_NAME}}">
<meta name="application-name" content="{{SITE_NAME}}">
<meta name="msapplication-TileColor" content="#{{config('DEFAULT_COLOR')}}">
<meta name="msapplication-config" content="/assets/browserconfig.xml?v=2">
<meta name="theme-color" content="#{{config('DEFAULT_COLOR')}}">
<link
rel="apple-touch-startup-image"
sizes="320x480"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="640x960"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-icon"
sizes="640x1136"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-icon"
sizes="750x1334"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="768x1004"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="768x1024"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="828x1792"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1024x748"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1024x768"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1125x2436"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1242x2208"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1242x2688"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1334x750"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1536x2008"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1536x2048"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1668x2224"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1792x828"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2048x1496"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2048x1536"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2048x2732"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2208x1242"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2224x1668"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2436x1125"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2668x1242"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2737x2048"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
{% block fixedMobileBarJS %}
{% endblock %}
</head>
<body id="{% if request.path != '/comments' %}{% block pagetype %}frontpage{% endblock %}{% endif %}" {% if SITE_NAME == 'rDrama' and v and (v.is_banned or v.agendaposter) %}style="overflow-x: hidden;background:url(/assets/images/backgrounds/anime/1.webp?v=3) center center fixed; background-color: var(--background)"{% elif v and v.background %}style="{% if path != '/formatting' %}overflow-x: hidden; {% endif %} background:url(/assets/images/backgrounds/{{v.background}}?v=3) center center fixed; background-color: var(--background){% if 'anime' not in v.background %};background-size: cover{% endif %}"{% endif %}>
{% block Banner %}
{% if '@' not in request.path %}
{% if sub %}
<img alt="/h/{{sub.name}} banner" role="button" data-bs-toggle="modal" data-bs-target="#expandImageModal" onclick="expandDesktopImage('{{sub.banner_url}}')" loading="lazy" src="{{sub.banner_url}}" width=100% style="object-fit:cover;max-height:25vw">
{% elif SITE_NAME == 'rDrama' %}
{# set path = "assets/images/" + SITE_NAME + "/banners" #}
{% set path = "assets/images/" + SITE_NAME + "/banners-birthgay" %}
{% set image = "/" + path + "/" + listdir('files/' + path)|random() + '?v=25' %}
<a href="https://secure.transequality.org/site/Donation2?df_id=1480">
{% if v and (v.is_banned or v.agendaposter) %}
<img alt="site banner" src="/assets/images/rDrama/banner2.webp?v=1" width="100%">
{% else %}
<img alt="site banner" src="{{image}}" width="100%">
{% endif %}
</a>
{% else %}
<a href="/">
<img alt="site banner" src="/assets/images/{{SITE_NAME}}/banner.webp?v=1046" width="100%">
</a>
{% endif %}
{% endif %}
{% endblock %}
{% include "header.html" %}
{% block mobileUserBanner %}
{% endblock %}
{% block mobileBanner %}
{% endblock %}
{% block postNav %}
{% endblock %}
<div class="container">
<div class="row justify-content-around" id="main-content-row">
<div class="col h-100 {% block customPadding %}{% if request.path.startswith('/@') %}user-gutters{% else %}custom-gutters{% endif %}{% endblock %}" id="main-content-col">
{% block desktopUserBanner %}
{% endblock %}
{% block desktopBanner %}
{% endblock %}
{% block PseudoSubmitForm %}
{% endblock %}
{% block searchText %}
{% endblock %}
{% block content %}
{% endblock %}
{% block pagenav %}
{% endblock %}
</div>
{% block sidebar %}
{% if home or sub and p %}
{% include "sidebar_" + SITE_NAME + ".html" %}
{% endif %}
{% endblock %}
</div>
</div>
{% block mobilenavbar %}
{% include "mobile_navigation_bar.html" %}
{% endblock %}
{% block actionsModal %}
{% endblock %}
{% block reportCommentModal %}
{% endblock %}
{% block GIFtoast %}
{% endblock %}
{% block GIFpicker %}
{% endblock %}
<div class="toast clipboard" id="toast-success" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body text-center">
<i class="fas fa-check-circle text-success mr-2"></i>Link copied to clipboard
</div>
</div>
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text">Action successful!</span>
</div>
</div>
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
</div>
</div>
<script src="/assets/js/lozad.js?v=242"></script>
{% if v %}
<script src="/assets/js/post_toast2.js?v=243"></script>
<script src="/assets/js/formatting.js?v=240"></script>
{% endif %}
<script src="/assets/js/lite-youtube.js?v=240"></script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="description" content="{{config('DESCRIPTION')}}">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval' ajax.cloudflare.com; connect-src 'self' tls-use1.fpapi.io api.fpjs.io {% if PUSHER_ID != 'blahblahblah' %}{{PUSHER_ID}}.pushnotifications.pusher.com{% endif %}; object-src 'none';">
<script src="/assets/js/bootstrap.js?v=245"></script>
<script src="/assets/js/shortcut handler.js?v=2"></script>
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=268">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=58">
<link rel="stylesheet" href="/assets/css/awards.css">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=268">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
<link rel="stylesheet" href="/assets/css/awards.css">
{% endif %}
{% if request.path == '/catalog' %}
<link rel="stylesheet" href="/assets/css/catalog.css?v=1">
{% endif %}
{% if sub and sub.css and not request.path.endswith('settings') %}
<link rel="stylesheet" href="/h/{{sub.name}}/css" type="text/css">
{% endif %}
{% if v and v.themecolor == '30409f' %}
<style>
p a {
color: #2a96f3;
}
</style>
{% endif %}
{% if SITE_NAME == 'rDrama' %}
<style>
.mod:before {
content: '(((';
}
.mod:after {
content: ')))';
}
</style>
{% endif %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="thumbnail" content="/assets/images/{{SITE_NAME}}/site_preview.webp?v=1015">
<link rel="icon" type="image/png" href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015">
{% block title %}
<title>{{SITE_NAME}}</title>
<meta property="og:type" content="article">
<meta property="og:title" content="{{SITE_NAME}}">
<meta property="og:site_name" content="{{request.host}}">
<meta property="og:image" content="/assets/images/{{SITE_NAME}}/site_preview.webp?v=1015">
<meta property="og:url" content="{{SITE_FULL}}{{request.full_path}}">
<meta property="og:description" name="description" content="{{SITE_NAME}} - {{config('DESCRIPTION')}}">
<meta property="og:author" name="author" content="{{SITE_FULL}}">
<meta property="og:site_name" content="{{request.host}}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="{{SITE_FULL}}">
<meta name="twitter:title" content="{{SITE_NAME}}">
<meta name="twitter:creator" content="{{SITE_FULL}}">
<meta name="twitter:description" content="{{SITE_NAME}} - {{config('DESCRIPTION')}}">
<meta name="twitter:image" content="/assets/images/{{SITE_NAME}}/site_preview.webp?v=1015">
<meta name="twitter:url" content="{{SITE_FULL}}{{request.full_path}}">
{% endblock %}
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-touch-fullscreen" content="yes">
<meta name="format-detection" content="telephone=no">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015">
<link rel="manifest" href="/assets/manifest_{{SITE_NAME}}.json?v=1">
<link rel="mask-icon" href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015">
<link rel="shortcut icon" href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015">
<meta name="apple-mobile-web-app-title" content="{{SITE_NAME}}">
<meta name="application-name" content="{{SITE_NAME}}">
<meta name="msapplication-TileColor" content="#{{config('DEFAULT_COLOR')}}">
<meta name="msapplication-config" content="/assets/browserconfig.xml?v=2">
<meta name="theme-color" content="#{{config('DEFAULT_COLOR')}}">
<link
rel="apple-touch-startup-image"
sizes="320x480"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="640x960"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-icon"
sizes="640x1136"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-icon"
sizes="750x1334"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="768x1004"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="768x1024"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="828x1792"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1024x748"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1024x768"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1125x2436"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1242x2208"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1242x2688"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1334x750"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1536x2008"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1536x2048"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1668x2224"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="1792x828"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2048x1496"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2048x1536"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2048x2732"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2208x1242"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2224x1668"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2436x1125"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2668x1242"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
<link
rel="apple-touch-startup-image"
sizes="2737x2048"
href="/assets/images/{{SITE_NAME}}/icon.webp?v=1015"
>
{% block fixedMobileBarJS %}
{% endblock %}
</head>
<body id="{% if request.path != '/comments' %}{% block pagetype %}frontpage{% endblock %}{% endif %}" {% if SITE_NAME == 'rDrama' and v and (v.is_banned or v.agendaposter) %}style="overflow-x: hidden;background:url(/assets/images/backgrounds/anime/1.webp?v=3) center center fixed; background-color: var(--background)"{% elif v and v.background %}style="{% if path != '/formatting' %}overflow-x: hidden; {% endif %} background:url(/assets/images/backgrounds/{{v.background}}?v=3) center center fixed; background-color: var(--background){% if 'anime' not in v.background %};background-size: cover{% endif %}"{% endif %}>
{% block Banner %}
{% if '@' not in request.path %}
{% if sub %}
<img alt="/h/{{sub.name}} banner" role="button" data-bs-toggle="modal" data-bs-target="#expandImageModal" onclick="expandDesktopImage('{{sub.banner_url}}')" loading="lazy" src="{{sub.banner_url}}" width=100% style="object-fit:cover;max-height:25vw">
{% elif SITE_NAME == 'rDrama' %}
{# set path = "assets/images/" + SITE_NAME + "/banners" #}
{% set path = "assets/images/" + SITE_NAME + "/banners-birthgay" %}
{% set image = "/" + path + "/" + listdir('files/' + path)|random() + '?v=25' %}
<a href="https://secure.transequality.org/site/Donation2?df_id=1480">
{% if v and (v.is_banned or v.agendaposter) %}
<img alt="site banner" src="/assets/images/rDrama/banner2.webp?v=1" width="100%">
{% else %}
<img alt="site banner" src="{{image}}" width="100%">
{% endif %}
</a>
{% else %}
<a href="/">
<img alt="site banner" src="/assets/images/{{SITE_NAME}}/banner.webp?v=1046" width="100%">
</a>
{% endif %}
{% endif %}
{% endblock %}
{% include "header.html" %}
{% block mobileUserBanner %}
{% endblock %}
{% block mobileBanner %}
{% endblock %}
{% block postNav %}
{% endblock %}
<div class="container">
<div class="row justify-content-around" id="main-content-row">
<div class="col h-100 {% block customPadding %}{% if request.path.startswith('/@') %}user-gutters{% else %}custom-gutters{% endif %}{% endblock %}" id="main-content-col">
{% block desktopUserBanner %}
{% endblock %}
{% block desktopBanner %}
{% endblock %}
{% block PseudoSubmitForm %}
{% endblock %}
{% block searchText %}
{% endblock %}
{% block content %}
{% endblock %}
{% block pagenav %}
{% endblock %}
</div>
{% block sidebar %}
{% if home or sub and p %}
{% include "sidebar_" + SITE_NAME + ".html" %}
{% endif %}
{% endblock %}
</div>
</div>
{% block mobilenavbar %}
{% include "mobile_navigation_bar.html" %}
{% endblock %}
{% block actionsModal %}
{% endblock %}
{% block reportCommentModal %}
{% endblock %}
{% block GIFtoast %}
{% endblock %}
{% block GIFpicker %}
{% endblock %}
<div class="toast clipboard" id="toast-success" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body text-center">
<i class="fas fa-check-circle text-success mr-2"></i>Link copied to clipboard
</div>
</div>
<div class="toast" id="toast-post-success" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-success text-center text-white">
<i class="fas fa-comment-alt-smile mr-2"></i><span id="toast-post-success-text">Action successful!</span>
</div>
</div>
<div class="toast" id="toast-post-error" style="position: fixed; bottom: 1.5rem; margin: 0 auto; left: 0; right: 0; width: 275px; z-index: 1000" role="alert" aria-live="assertive" aria-atomic="true" data-bs-animation="true" data-bs-autohide="true" data-bs-delay="5000">
<div class="toast-body bg-danger text-center text-white">
<i class="fas fa-exclamation-circle mr-2"></i><span id="toast-post-error-text">Error, please try again later.</span>
</div>
</div>
<script src="/assets/js/lozad.js?v=242"></script>
{% if v %}
<script src="/assets/js/post_toast2.js?v=243"></script>
<script src="/assets/js/formatting.js?v=240"></script>
{% endif %}
<script src="/assets/js/lite-youtube.js?v=241"></script>
</body>
</html>

View File

@ -1,30 +1,30 @@
<script src="/assets/js/delete_post_modal.js?v=240"></script>
<div class="modal fade" id="deletePostModal" tabindex="-1" role="dialog" aria-labelledby="deletePostModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header d-none d-md-flex">
<h5 class="modal-title">Delete post?</h5>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
<div class="modal-body text-center">
<div class="py-4">
<i class="fas fa-trash-alt text-muted d-none d-md-block" style="font-size: 3.5rem;"></i>
</div>
<div class="h4 d-md-none">Delete post?</div>
<p class="d-none d-md-block">Your post will be deleted everywhere on {{SITE_NAME}}.</p>
<p class="text-muted d-md-none">Your post will be deleted everywhere on {{SITE_NAME}}.</p>
<button id="deletePostButton" class="btn btn-danger btn-block mt-5" data-bs-dismiss="modal">Delete post</button>
<button class="btn btn-secondary btn-block" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<script src="/assets/js/delete_post_modal.js?v=240"></script>
<div class="modal fade" id="deletePostModal" tabindex="-1" role="dialog" aria-labelledby="deletePostModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header d-none d-md-flex">
<h5 class="modal-title">Delete post?</h5>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
<div class="modal-body text-center">
<div class="py-4">
<i class="fas fa-trash-alt text-muted d-none d-md-block" style="font-size: 3.5rem;"></i>
</div>
<div class="h4 d-md-none">Delete post?</div>
<p class="d-none d-md-block">Your post will be deleted everywhere on {{SITE_NAME}}.</p>
<p class="text-muted d-md-none">Your post will be deleted everywhere on {{SITE_NAME}}.</p>
<button id="deletePostButton" class="btn btn-danger btn-block mt-5" data-bs-dismiss="modal">Delete post</button>
<button class="btn btn-secondary btn-block" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>

View File

@ -1,32 +1,32 @@
{% extends "email/default.html" %}
{% block title %}Remove Two-Factor Authentication{% endblock %}</h1>
{% block preheader %}Remove Two-Factor Authentication.{% endblock %}
{% block content %}
<p>We received a request to remove two-factor authentication from your account. In 72 hours, click the link below.</p>
<p>If you didn't make this request, change your password and use the Log Out Everywhere feature in your <a href="/settings/security">Security Settings</a> to permanently invalidate the link.</p>
<div class="overflow-x-auto"><table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto><table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{action_url}}" class="f-fallback button" target="_blank">Remove 2FA</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>Please note that {{SITE_NAME}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
<div class="overflow-x-auto"><table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{action_url}}</p>
</td>
</tr>
</table>
{% endblock %}
{% extends "email/default.html" %}
{% block title %}Remove Two-Factor Authentication{% endblock %}</h1>
{% block preheader %}Remove Two-Factor Authentication.{% endblock %}
{% block content %}
<p>We received a request to remove two-factor authentication from your account. In 72 hours, click the link below.</p>
<p>If you didn't make this request, change your password and use the Log Out Everywhere feature in your <a href="/settings/security">Security Settings</a> to permanently invalidate the link.</p>
<div class="overflow-x-auto"><table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto><table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{action_url}}" class="f-fallback button" target="_blank">Remove 2FA</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>Please note that {{SITE_NAME}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
<div class="overflow-x-auto"><table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{action_url}}</p>
</td>
</tr>
</table>
{% endblock %}

View File

@ -1,410 +1,410 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta name="description" content="{{config('DESCRIPTION')}}">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none';">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="x-apple-disable-message-reformatting" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title></title>
<style type="text/css" rel="stylesheet" media="all">
html {
font-size: 14px;
}
body {
width: 100% !important;
height: 100%;
margin: 0;
-webkit-text-size-adjust: none;
}
a {
color: #FF66AC!important;
}
a img {
border: none;
}
td {
word-break: break-word;
}
.preheader {
display: none !important;
visibility: hidden;
mso-hide: all;
font-size: 1px;
line-height: 1px;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
}
body,
td,
th {
font-family: Helvetica, Arial, sans-serif;
}
h1 {
margin-top: 0;
color: #121213;
font-size: 22px;
font-weight: bold;
text-align: left;
}
h2 {
margin-top: 0;
color: #121213;
font-size: 1rem;
font-weight: bold;
text-align: left;
}
h3 {
margin-top: 0;
color: #121213;
font-size: 14px;
font-weight: bold;
text-align: left;
}
td,
th {
font-size: 1rem;
}
p,
ul,
ol,
blockquote {
margin: .4em 0 1.1875em;
font-size: 1rem;
line-height: 1.625;
}
p.sub {
font-size: 13px;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
.button {
background-color: #FF66AC;
border-top: 10px solid #FF66AC;
border-right: 18px solid #FF66AC;
border-bottom: 10px solid #FF66AC;
border-left: 18px solid #FF66AC;
display: inline-block;
color: #FFF!important;
text-decoration: none;
border-radius: .25rem;
-webkit-text-size-adjust: none;
box-sizing: border-box;
}
.button--green {
background-color: #23CE6B;
border-top: 10px solid #23CE6B;
border-right: 18px solid #23CE6B;
border-bottom: 10px solid #23CE6B;
border-left: 18px solid #23CE6B;
}
.button--red {
background-color: #F05D5E;
border-top: 10px solid #F05D5E;
border-right: 18px solid #F05D5E;
border-bottom: 10px solid #F05D5E;
border-left: 18px solid #F05D5E;
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
text-align: center !important;
}
}
.attributes {
margin: 0 0 21px;
}
.attributes_content {
background-color: #EDF2F7;
padding: 1rem;
border-radius: 0.35rem;
}
.attributes_item {
padding: 0;
}
.related {
width: 100%;
margin: 0;
padding: 25px 0 0 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.related_item {
padding: 10px 0;
color: #CBCCCF;
font-size: 15px;
line-height: 18px;
}
.related_item-title {
display: block;
margin: .5em 0 0;
}
.related_item-thumb {
display: block;
padding-bottom: 10px;
}
.related_heading {
border-top: 1px solid #CBCCCF;
text-align: center;
padding: 25px 0 10px;
}
.discount {
width: 100%;
margin: 0;
padding: 24px;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #EDF2F7;
border: 2px dashed #CBCCCF;
}
.discount_heading {
text-align: center;
}
.discount_body {
text-align: center;
font-size: 15px;
}
.social {
width: auto;
}
.social td {
padding: 0;
width: auto;
}
.social_icon {
height: 20px;
margin: 0 8px 10px 8px;
padding: 0;
}
.purchase {
width: 100%;
margin: 0;
padding: 35px 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.purchase_content {
width: 100%;
margin: 0;
padding: 25px 0 0 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.purchase_item {
padding: 10px 0;
color: #121213;
font-size: 15px;
line-height: 18px;
}
.purchase_heading {
padding-bottom: 8px;
border-bottom: 1px solid #E6E6E6;
}
.purchase_heading p {
margin: 0;
color: #85878E;
font-size: 12px;
}
.purchase_footer {
padding-top: 15px;
border-top: 1px solid #E6E6E6;
}
.purchase_total {
margin: 0;
text-align: right;
font-weight: bold;
color: #121213;
}
.purchase_total--label {
padding: 0 15px 0 0;
}
body {
background-color: #EDF2F7;
color: #121213;
}
p {
color: #121213;
}
p.sub {
color: #6B6E76;
}
.email-wrapper {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #EDF2F7;
}
.email-content {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.email-masthead {
display: none;
}
.email-masthead_logo {
width: 94px;
}
.email-masthead_name {
font-size: 1.25rem;
font-weight: bold;
color: #121213;
text-decoration: none;
}
.email-body {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #cfcfcf;
}
.email-body_inner {
width: 570px;
margin: 0 auto;
padding: 0;
-premailer-width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #cfcfcf;
}
.email-footer {
width: 570px;
margin: 0 auto;
padding: 0;
-premailer-width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
text-align: center;
}
.email-footer p {
color: #6B6E76;
}
.body-action {
width: 100%;
margin: 30px auto;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
text-align: center;
}
.body-sub {
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #E6E6E6;
}
.content-cell {
padding: 35px;
}
@media only screen and (max-width: 600px) {
.email-body_inner,
.email-footer {
width: 100% !important;
}
}
@media (prefers-color-scheme: dark) {
body,
.email-body,
.email-body_inner,
.email-content,
.email-wrapper,
.email-masthead,
.email-footer {
background-color: #121213 !important;
color: #FFF !important;
}
p,
ul,
ol,
blockquote,
h1,
h2,
h3 {
color: #FFF !important;
}
.attributes_content,
.discount {
background-color: #222 !important;
}
.email-masthead_name {
text-shadow: none !important;
}
}
</style>
</head>
<body>
<span class="preheader">{% block preheader %}Thanks for joining {{SITE_NAME}}! Please take a sec to verify the email you used to sign up.{% endblock %}</span>
<div class="overflow-x-auto"><table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto"><table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="/" class="f-fallback email-masthead_name">
{{SITE_NAME}}
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<div class="overflow-x-auto"><table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>{% block title %}Title Goes Here{% endblock %}</h1>
{% block content %}
{% for entry in data %}
<h3>{{entry[0]}}</h3>
<p>{{entry[1]}}</p>
{% endfor %}
{% endblock %}
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta name="description" content="{{config('DESCRIPTION')}}">
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; connect-src 'self'; object-src 'none';">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="x-apple-disable-message-reformatting" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title></title>
<style type="text/css" rel="stylesheet" media="all">
html {
font-size: 14px;
}
body {
width: 100% !important;
height: 100%;
margin: 0;
-webkit-text-size-adjust: none;
}
a {
color: #FF66AC!important;
}
a img {
border: none;
}
td {
word-break: break-word;
}
.preheader {
display: none !important;
visibility: hidden;
mso-hide: all;
font-size: 1px;
line-height: 1px;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
}
body,
td,
th {
font-family: Helvetica, Arial, sans-serif;
}
h1 {
margin-top: 0;
color: #121213;
font-size: 22px;
font-weight: bold;
text-align: left;
}
h2 {
margin-top: 0;
color: #121213;
font-size: 1rem;
font-weight: bold;
text-align: left;
}
h3 {
margin-top: 0;
color: #121213;
font-size: 14px;
font-weight: bold;
text-align: left;
}
td,
th {
font-size: 1rem;
}
p,
ul,
ol,
blockquote {
margin: .4em 0 1.1875em;
font-size: 1rem;
line-height: 1.625;
}
p.sub {
font-size: 13px;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
.button {
background-color: #FF66AC;
border-top: 10px solid #FF66AC;
border-right: 18px solid #FF66AC;
border-bottom: 10px solid #FF66AC;
border-left: 18px solid #FF66AC;
display: inline-block;
color: #FFF!important;
text-decoration: none;
border-radius: .25rem;
-webkit-text-size-adjust: none;
box-sizing: border-box;
}
.button--green {
background-color: #23CE6B;
border-top: 10px solid #23CE6B;
border-right: 18px solid #23CE6B;
border-bottom: 10px solid #23CE6B;
border-left: 18px solid #23CE6B;
}
.button--red {
background-color: #F05D5E;
border-top: 10px solid #F05D5E;
border-right: 18px solid #F05D5E;
border-bottom: 10px solid #F05D5E;
border-left: 18px solid #F05D5E;
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
text-align: center !important;
}
}
.attributes {
margin: 0 0 21px;
}
.attributes_content {
background-color: #EDF2F7;
padding: 1rem;
border-radius: 0.35rem;
}
.attributes_item {
padding: 0;
}
.related {
width: 100%;
margin: 0;
padding: 25px 0 0 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.related_item {
padding: 10px 0;
color: #CBCCCF;
font-size: 15px;
line-height: 18px;
}
.related_item-title {
display: block;
margin: .5em 0 0;
}
.related_item-thumb {
display: block;
padding-bottom: 10px;
}
.related_heading {
border-top: 1px solid #CBCCCF;
text-align: center;
padding: 25px 0 10px;
}
.discount {
width: 100%;
margin: 0;
padding: 24px;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #EDF2F7;
border: 2px dashed #CBCCCF;
}
.discount_heading {
text-align: center;
}
.discount_body {
text-align: center;
font-size: 15px;
}
.social {
width: auto;
}
.social td {
padding: 0;
width: auto;
}
.social_icon {
height: 20px;
margin: 0 8px 10px 8px;
padding: 0;
}
.purchase {
width: 100%;
margin: 0;
padding: 35px 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.purchase_content {
width: 100%;
margin: 0;
padding: 25px 0 0 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.purchase_item {
padding: 10px 0;
color: #121213;
font-size: 15px;
line-height: 18px;
}
.purchase_heading {
padding-bottom: 8px;
border-bottom: 1px solid #E6E6E6;
}
.purchase_heading p {
margin: 0;
color: #85878E;
font-size: 12px;
}
.purchase_footer {
padding-top: 15px;
border-top: 1px solid #E6E6E6;
}
.purchase_total {
margin: 0;
text-align: right;
font-weight: bold;
color: #121213;
}
.purchase_total--label {
padding: 0 15px 0 0;
}
body {
background-color: #EDF2F7;
color: #121213;
}
p {
color: #121213;
}
p.sub {
color: #6B6E76;
}
.email-wrapper {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #EDF2F7;
}
.email-content {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.email-masthead {
display: none;
}
.email-masthead_logo {
width: 94px;
}
.email-masthead_name {
font-size: 1.25rem;
font-weight: bold;
color: #121213;
text-decoration: none;
}
.email-body {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #cfcfcf;
}
.email-body_inner {
width: 570px;
margin: 0 auto;
padding: 0;
-premailer-width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #cfcfcf;
}
.email-footer {
width: 570px;
margin: 0 auto;
padding: 0;
-premailer-width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
text-align: center;
}
.email-footer p {
color: #6B6E76;
}
.body-action {
width: 100%;
margin: 30px auto;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
text-align: center;
}
.body-sub {
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #E6E6E6;
}
.content-cell {
padding: 35px;
}
@media only screen and (max-width: 600px) {
.email-body_inner,
.email-footer {
width: 100% !important;
}
}
@media (prefers-color-scheme: dark) {
body,
.email-body,
.email-body_inner,
.email-content,
.email-wrapper,
.email-masthead,
.email-footer {
background-color: #121213 !important;
color: #FFF !important;
}
p,
ul,
ol,
blockquote,
h1,
h2,
h3 {
color: #FFF !important;
}
.attributes_content,
.discount {
background-color: #222 !important;
}
.email-masthead_name {
text-shadow: none !important;
}
}
</style>
</head>
<body>
<span class="preheader">{% block preheader %}Thanks for joining {{SITE_NAME}}! Please take a sec to verify the email you used to sign up.{% endblock %}</span>
<div class="overflow-x-auto"><table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto"><table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="/" class="f-fallback email-masthead_name">
{{SITE_NAME}}
</a>
</td>
</tr>
<tr>
<td class="email-body" width="100%" cellpadding="0" cellspacing="0">
<div class="overflow-x-auto"><table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>{% block title %}Title Goes Here{% endblock %}</h1>
{% block content %}
{% for entry in data %}
<h3>{{entry[0]}}</h3>
<p>{{entry[1]}}</p>
{% endfor %}
{% endblock %}
</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,54 +1,54 @@
{% extends "email/default.html" %}
{% block title %}Verify Your Email{% endblock %}</h1>
{% block preheader %}Verify your new {{SITE_NAME}} email.{% endblock %}
{% block content %}
<p>You told us you wanted to change your {{SITE_NAME}} account email. To finish this process, please verify your new email address:</p>
<div class="overflow-x-auto"><table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto><table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{action_url}}" class="f-fallback button" target="_blank">Verify email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>For reference, here's your current information:</p>
<div class="overflow-x-auto"><table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_content">
<div class="overflow-x-auto><table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Email:</strong> {{v.email}}
</span>
</td>
</tr>
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Username:</strong> {{v.username}}
</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>Please note that {{SITE_NAME}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
<div class="overflow-x-auto"><table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{action_url}}</p>
</td>
</tr>
</table>
{% endblock %}
{% extends "email/default.html" %}
{% block title %}Verify Your Email{% endblock %}</h1>
{% block preheader %}Verify your new {{SITE_NAME}} email.{% endblock %}
{% block content %}
<p>You told us you wanted to change your {{SITE_NAME}} account email. To finish this process, please verify your new email address:</p>
<div class="overflow-x-auto"><table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto><table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{action_url}}" class="f-fallback button" target="_blank">Verify email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>For reference, here's your current information:</p>
<div class="overflow-x-auto"><table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_content">
<div class="overflow-x-auto><table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Email:</strong> {{v.email}}
</span>
</td>
</tr>
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Username:</strong> {{v.username}}
</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>Please note that {{SITE_NAME}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
<div class="overflow-x-auto"><table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{action_url}}</p>
</td>
</tr>
</table>
{% endblock %}

View File

@ -1,52 +1,52 @@
{% extends "email/default.html" %}
{% block title %}Welcome to {{SITE_NAME}}!{% endblock %}</h1>
{% block content %}
<p>Thanks for joining {{SITE_NAME}}. Were happy to have you on board. To get the most out of {{SITE_NAME}}, please verify your account email:</p>
<div class="overflow-x-auto"><table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto><table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{action_url}}" class="f-fallback button" target="_blank">Verify email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>For reference, here's your username.</p>
<div class="overflow-x-auto"><table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_content">
<div class="overflow-x-auto><table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Email:</strong> {{v.email}}
</span>
</td>
</tr>
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Username:</strong> {{v.username}}
</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>Please note that {{SITE_NAME}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
<div class="overflow-x-auto"><table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{action_url}}</p>
</td>
</tr>
</table>
{% endblock %}
{% extends "email/default.html" %}
{% block title %}Welcome to {{SITE_NAME}}!{% endblock %}</h1>
{% block content %}
<p>Thanks for joining {{SITE_NAME}}. Were happy to have you on board. To get the most out of {{SITE_NAME}}, please verify your account email:</p>
<div class="overflow-x-auto"><table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto><table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{action_url}}" class="f-fallback button" target="_blank">Verify email</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>For reference, here's your username.</p>
<div class="overflow-x-auto"><table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_content">
<div class="overflow-x-auto><table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Email:</strong> {{v.email}}
</span>
</td>
</tr>
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Username:</strong> {{v.username}}
</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>Please note that {{SITE_NAME}} will never ask you for your email, password, or two-factor token via email, text, or phone.</p>
<div class="overflow-x-auto"><table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{action_url}}</p>
</td>
</tr>
</table>
{% endblock %}

View File

@ -1,52 +1,52 @@
{% extends "email/default.html" %}
{% block title %}Reset Your Password{% endblock %}
{% block preheader %}Reset your {{SITE_NAME}} password.{% endblock %}
{% block content %}
<p>To reset your password, click the button below:</p>
<div class="overflow-x-auto"><table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto><table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{action_url}}" class="f-fallback button" target="_blank">Reset password</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>For reference, here's your login information:</p>
<div class="overflow-x-auto"><table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_content">
<div class="overflow-x-auto><table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Email:</strong> {{v.email}}
</span>
</td>
</tr>
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Username:</strong> {{v.username}}
</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="overflow-x-auto"><table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{action_url}}</p>
</td>
</tr>
</table>
{% endblock %}
{% extends "email/default.html" %}
{% block title %}Reset Your Password{% endblock %}
{% block preheader %}Reset your {{SITE_NAME}} password.{% endblock %}
{% block content %}
<p>To reset your password, click the button below:</p>
<div class="overflow-x-auto"><table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<div class="overflow-x-auto><table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="{{action_url}}" class="f-fallback button" target="_blank">Reset password</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
<p>For reference, here's your login information:</p>
<div class="overflow-x-auto"><table class="attributes" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_content">
<div class="overflow-x-auto><table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Email:</strong> {{v.email}}
</span>
</td>
</tr>
<tr>
<td class="attributes_item">
<span class="f-fallback">
<strong>Username:</strong> {{v.username}}
</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="overflow-x-auto"><table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">{{action_url}}</p>
</td>
</tr>
</table>
{% endblock %}

View File

@ -1,107 +1,104 @@
<div id="form" class="d-none"></div>
<div class="modal fade" id="emojiModal" tabindex="-1" role="dialog" aria-labelledby="emojiModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered p-2 py-5 emoji-modal" role="document">
<div class="modal-content" id="emojiTabs">
<div class="modal-header">
<div>
<ul class="nav nav-pills py-2">
<li class="nav-item">
<a class="nav-link active emojitab" data-bs-toggle="tab" href="#emoji-tab-favorite">Favorite</a>
</li>
{% if SITE_NAME == 'Cringetopia' %}
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-cringetopia">Cringetopia</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-marsey">Marsey</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-marseyalphabet">Marsey Alphabet</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-platy">Platy</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-wolf">Zombie Wolf</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-tay">Tay</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-classic">Classic</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-rage">Rage</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-wojak">Wojak</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-flags">Flags</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-misc">Misc</a>
</li>
</ul>
</div>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<i class="fal fa-times text-muted"></i>
</button>
</div>
<div class="px-3"><input autocomplete="off" class="form-control px-2" type="text" id="emoji_search" placeholder="Search.."></div>
<div style="overflow-y: scroll;">
<div class="modal-body p-0" id="emoji-modal-body">
<div id="emoji-tab-search"></div>
<div id="no-emojis-found"></div>
<div id="tab-content" class="tab-content">
<div class="tab-pane fade show active" id="emoji-tab-favorite">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_favorite"></div>
</div>
{% if SITE_NAME == 'Cringetopia' %}
<div class="tab-pane fade" id="emoji-tab-cringetopia">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_cringetopia"></div>
</div>
{% endif %}
<div class="tab-pane fade" id="emoji-tab-marsey">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_marsey"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-marseyalphabet">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_marseyalphabet"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-platy">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_platy"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-wolf">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_wolf"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-tay">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_tay"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-classic">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_classic"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-rage">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_rage"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-wojak">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_wojak"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-flags">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_flags"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-misc">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_misc"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/js/emoji_modal.js?v=271"></script>
<div id="form" class="d-none"></div>
<div class="modal fade" id="emojiModal" tabindex="-1" role="dialog" aria-labelledby="emojiModalTitle" aria-hidden="true">
<style>
#emojiTabs {height: 80%;}
@media (max-height: 650px) {
#emojiTabs {height: 100%;}
#emojiModalInternalDivIDK {margin-top: 0 !important; margin-bottom: 0 !important; padding-top: 0 !important; padding-bottom: 0 !important;}
#emoji-modal-tabs-container {
overflow-x: auto;
}
#emoji-modal-tabs {
white-space: nowrap;
flex-wrap: nowrap;
}
#emoji-modal-tabs li {
display: inline-block;
}
}
</style>
<div id="emojiModalInternalDivIDK" class="modal-dialog modal-dialog-scrollable modal-dialog-centered p-2 py-5 emoji-modal" role="document">
<div class="modal-content" id="emojiTabs">
<div class="modal-header">
<div id="emoji-modal-tabs-container">
<ul class="nav nav-pills py-2" id="emoji-modal-tabs">
<li class="nav-item">
<a class="nav-link active emojitab" data-class-name="favorite" data-bs-toggle="tab" href="#" onclick="switchEmojiTab(event)">⭐ Favorite ⭐</a>
</li>
</ul>
</div>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<i class="fal fa-times text-muted"></i>
</button>
</div>
<div class="px-3">
<input disabled autocomplete="off" class="form-control px-2" type="text" id="emoji_search" placeholder="Search..">
</div>
<div class="px-3 d-flex flex-row">
<fieldset class="p-2">
<label style="display: inline">Options:</legend>
<div style="display: inline" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Makes the emoji larger">
<input type="checkbox" id="emoji-sel-0" value="#" class="emoji-suffix">
<label for="emoji-sel-0">Large</label>
</div>
<div style="display: inline" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Mirror the emoji along the Y axis">
<input type="checkbox" id="emoji-sel-1" value="!" class="emoji-suffix">
<label for="emoji-sel-1">Mirror</label>
</div>
<div style="display: inline" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Adds a hand that pats the emoji">
<input type="checkbox" id="emoji-sel-2" value="pat" class="emoji-postfix">
<label for="emoji-sel-2">Pat</label>
</div>
</fieldset>
<fieldset class="p-2">
<label style="display: inline">Search:</legend>
<div style="display: inline" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Iterate through all substrings of the query. YIKES">
<input type="checkbox" id="emoji-complete-search">
<label for="emoji-complete-search">complete</label>
</div>
</fieldset>
</div>
<div style="overflow-y: scroll;">
<div class="modal-body p-0" id="emoji-modal-body">
<div id="no-emojis-found" class="tab-content py-3 pl-2" hidden>
No results... Next time be better with your query. 💅
</div>
<div id="emojis-work" class="tab-content py-3 pl-2">
I am working as hard as I can, sweety... 🚴
</div>
<div id="emoji-new-user" class="tab-content py-3 pl-2" hidden>
👋 Hello! This is the first time you're using the emoji system on this device 📱.<br>
I've took the liberty to populate this tab with a selection of Anton's emojis 😽. Next time you'll find the ones you used the most in there 📚
</div>
<div id="tab-content" class="tab-content d-flex flex-wrap py-3 pl-2" hidden>
<style>
.emoji2 {
/*background: None!important;*/
width:60px;
height: 85px;
overflow: hidden;
border: none
}
</style>
<template id="emoji-button-template">
<button class="btn m-1 px-0 emoji2" data-bs-toggle="tooltip" delay:="0">
<img loading="lazy" width=50>
</button>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/js/emoji_modal.js?v=281"></script>

View File

@ -1,20 +1,20 @@
{% extends "default.html" %}
{% block title %}
<title>400 Bad Request</title>
{% endblock %}
{% block pagetype %}error-400{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseybrainlet:" loading="lazy" src="/e/marseybrainlet.webp">
<pre></pre>
<h1 class="h5">400 Bad Request</h1>
<p class="text-muted mb-5">That request was bad and you should feel bad.</p>
</div>
</div>
</div>
{% endblock %}
{% extends "default.html" %}
{% block title %}
<title>400 Bad Request</title>
{% endblock %}
{% block pagetype %}error-400{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseybrainlet:" loading="lazy" src="/e/marseybrainlet.webp">
<pre></pre>
<h1 class="h5">400 Bad Request</h1>
<p class="text-muted mb-5">That request was bad and you should feel bad.</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,24 +1,24 @@
{% extends "default.html" %}
{% block title %}
<title>401 Not Authorized</title>
{% endblock %}
{% block pagetype %}error-401{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseydead:" loading="lazy" src="/e/marseydead.webp">
<pre></pre>
<h1 class="h5">401 Not Authorized</h1>
<p class="text-muted mb-5">What you're trying to do requires an account. I think. The original error message said something about a castle and I hated that.</p>
<div><a href="/signup" class="btn btn-primary mb-2">Create an account</a></div>
<div><a href="/login" class="text-muted text-small">Or sign in</a></div>
</div>
</div>
</div>
{% extends "default.html" %}
{% block title %}
<title>401 Not Authorized</title>
{% endblock %}
{% block pagetype %}error-401{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseydead:" loading="lazy" src="/e/marseydead.webp">
<pre></pre>
<h1 class="h5">401 Not Authorized</h1>
<p class="text-muted mb-5">What you're trying to do requires an account. I think. The original error message said something about a castle and I hated that.</p>
<div><a href="/signup" class="btn btn-primary mb-2">Create an account</a></div>
<div><a href="/login" class="text-muted text-small">Or sign in</a></div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,21 +1,21 @@
{% extends "default.html" %}
{% block title %}
<title>403 Forbidden</title>
{% endblock %}
{% block pagetype %}error-403{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseytroll:" loading="lazy" src="/e/marseytroll.webp">
<pre></pre>
<h1 class="h5">403 Forbidden</h1>
<p class="text-muted mb-5">{{description}}</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>
{% endblock %}
{% extends "default.html" %}
{% block title %}
<title>403 Forbidden</title>
{% endblock %}
{% block pagetype %}error-403{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseytroll:" loading="lazy" src="/e/marseytroll.webp">
<pre></pre>
<h1 class="h5">403 Forbidden</h1>
<p class="text-muted mb-5">{{description}}</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,21 +1,21 @@
{% extends "default.html" %}
{% block title %}
<title>404 Page Not Found</title>
{% endblock %}
{% block pagetype %}error-404{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyconfused" loading="lazy" src="/e/marseyconfused.webp">
<pre></pre>
<h1 class="h5">404 Page Not Found</h1>
<p class="text-muted mb-5">Someone typed something wrong and it was probably you, please do better.</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>
{% endblock %}
{% extends "default.html" %}
{% block title %}
<title>404 Page Not Found</title>
{% endblock %}
{% block pagetype %}error-404{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyconfused" loading="lazy" src="/e/marseyconfused.webp">
<pre></pre>
<h1 class="h5">404 Page Not Found</h1>
<p class="text-muted mb-5">Someone typed something wrong and it was probably you, please do better.</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,21 +1,21 @@
{% extends "default.html" %}
{% block title %}
<title>405 Method Not Allowed</title>
{% endblock %}
{% block pagetype %}error-405{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyretard:" loading="lazy" src="/e/marseyretard.webp">
<pre></pre>
<h1 class="h5">405 Method Not Allowed</h1>
<p class="text-muted mb-5">idk how anyone gets this error but if you see this, remember to follow @carpathianflorist<BR>the original error text here talked about internet gremlins and wtf</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>
{% endblock %}
{% extends "default.html" %}
{% block title %}
<title>405 Method Not Allowed</title>
{% endblock %}
{% block pagetype %}error-405{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyretard:" loading="lazy" src="/e/marseyretard.webp">
<pre></pre>
<h1 class="h5">405 Method Not Allowed</h1>
<p class="text-muted mb-5">idk how anyone gets this error but if you see this, remember to follow @carpathianflorist<BR>the original error text here talked about internet gremlins and wtf</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,20 +1,20 @@
{% extends "default.html" %}
{% block title %}
<title>429 Too Many Requests</title>
{% endblock %}
{% block pagetype %}error-429{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyrentfree:" loading="lazy" src="/e/marseyrentfree.webp">
<pre></pre>
<h1 class="h5">429 Too Many Requests</h1>
<p class="text-muted mb-5">go spam somewhere else nerd</p>
</div>
</div>
</div>
{% endblock %}
{% extends "default.html" %}
{% block title %}
<title>429 Too Many Requests</title>
{% endblock %}
{% block pagetype %}error-429{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyrentfree:" loading="lazy" src="/e/marseyrentfree.webp">
<pre></pre>
<h1 class="h5">429 Too Many Requests</h1>
<p class="text-muted mb-5">go spam somewhere else nerd</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,22 +1,22 @@
{% extends "default.html" %}
{% block title %}
<title>500 Internal Server Error</title>
{% endblock %}
{% block pagetype %}error-500{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseydead:" loading="lazy" src="/e/marseydead.webp">
<pre></pre>
<h1 class="h5">500 Internal Server Error</h1>
<p class="text-muted mb-5">Hiiiii it's carp! I think this error means that there's a timeout error. And I think that means something took too long to load so it decided not to work at all. If you keep seeing this on the same page <I>but not other pages</I>, then something is probably wrong with that specific function. It may not be called a function, but that sounds right to me. Anyway, ping me and I'll whine to someone smarter to fix it. Don't bother them. Thanks ily <3</p>
<div><a href="/" class="btn btn-primary">Go to the frontpage</a></div>
</div>
</div>
</div>
{% endblock %}
{% extends "default.html" %}
{% block title %}
<title>500 Internal Server Error</title>
{% endblock %}
{% block pagetype %}error-500{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseydead:" loading="lazy" src="/e/marseydead.webp">
<pre></pre>
<h1 class="h5">500 Internal Server Error</h1>
<p class="text-muted mb-5">Hiiiii it's carp! I think this error means that there's a timeout error. And I think that means something took too long to load so it decided not to work at all. If you keep seeing this on the same page <I>but not other pages</I>, then something is probably wrong with that specific function. It may not be called a function, but that sounds right to me. Anyway, ping me and I'll whine to someone smarter to fix it. Don't bother them. Thanks ily <3</p>
<div><a href="/" class="btn btn-primary">Go to the frontpage</a></div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,28 +1,28 @@
{% extends "default.html" %}
{% block title %}
<title>+18</title>
{% endblock %}
{% block pagetype %}error-451{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col col-md-5">
<div class="text-center px-3 mt-5">
<img alt=":#marseytwerking:" loading="lazy" src="/e/marseytwerking.webp">
<h1 class="h5">Are you over 18?</h1>
<p class="mb-5">This post is rated +18 (Adult-Only). You must be 18 or older to continue. Are you sure you want to proceed?</p>
<div class="btn-toolbar justify-content-center mb-4">
<form action="/allow_nsfw" method="post">
<input type="hidden" name="redir" value="{{request.path}}">
<input type="submit" class="btn btn-danger mr-2" value="Yes, I am +18">
</form>
<div><a href="/" class="btn btn-secondary">No</a></div>
</div>
</div>
</div>
</div>
{% extends "default.html" %}
{% block title %}
<title>+18</title>
{% endblock %}
{% block pagetype %}error-451{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col col-md-5">
<div class="text-center px-3 mt-5">
<img alt=":#marseytwerking:" loading="lazy" src="/e/marseytwerking.webp">
<h1 class="h5">Are you over 18?</h1>
<p class="mb-5">This post is rated +18 (Adult-Only). You must be 18 or older to continue. Are you sure you want to proceed?</p>
<div class="btn-toolbar justify-content-center mb-4">
<form action="/allow_nsfw" method="post">
<input type="hidden" name="redir" value="{{request.full_path}}">
<input type="submit" class="btn btn-danger mr-2" value="Yes, I am +18">
</form>
<div><a href="/" class="btn btn-secondary">No</a></div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,20 +1,20 @@
{% extends "default.html" %}
{% block title %}
<title>401 Not Authorized</title>
{% endblock %}
{% block pagetype %}error-401{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseymerchant:" loading="lazy" class="mb-2" src="/e/marseymerchant.webp">
<h1 class="h5">401 Not Authorized</h1>
<p class="text-muted">This page is only available to {% if SITE_NAME == 'rDrama' %}paypigs{% else %}patrons{% endif %}:</p>
<a rel="nofollow noopener noreferrer" href="{{config('GUMROAD_LINK')}}">{{config('GUMROAD_LINK')}}</a>
</div>
</div>
</div>
{% extends "default.html" %}
{% block title %}
<title>401 Not Authorized</title>
{% endblock %}
{% block pagetype %}error-401{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseymerchant:" loading="lazy" class="mb-2" src="/e/marseymerchant.webp">
<h1 class="h5">401 Not Authorized</h1>
<p class="text-muted">This page is only available to {% if SITE_NAME == 'rDrama' %}paypigs{% else %}patrons{% endif %}:</p>
<a rel="nofollow noopener noreferrer" href="{{config('GUMROAD_LINK')}}">{{config('GUMROAD_LINK')}}</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,17 +1,17 @@
<div class="modal desktop-expanded-image-modal" id="expandImageModal" tabindex="-1" role="dialog" aria-labelledby="expandImageModalTitle" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered mx-auto expandedimage" style="max-width:80%!important" role="document">
<div class="modal-content bg-transparent shadow-none">
<div class="modal-body text-center p-0">
<div class="d-inline-block position-relative">
<a href="/" rel="nofollow noopener noreferrer" target="_blank" id="desktop-expanded-image-wrap-link">
<img loading="lazy" alt="expanded image" referrerpolicy="no-referrer" src="" class="img-fluid rounded" id="desktop-expanded-image" style="min-width: 250px;">
</a>
<div class="position-absolute d-flex justify-content-between align-items-center w-100 mt-1">
<a href="/" rel="nofollow noopener noreferrer" target="_blank" class="text-gray-500 font-weight-bold text-left" id="desktop-expanded-image-link">View original</a>
</div>
</div>
</div>
</div>
</div>
<div class="modal desktop-expanded-image-modal" id="expandImageModal" tabindex="-1" role="dialog" aria-labelledby="expandImageModalTitle" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered mx-auto expandedimage" style="max-width:80%!important" role="document">
<div class="modal-content bg-transparent shadow-none">
<div class="modal-body text-center p-0">
<div class="d-inline-block position-relative">
<a href="/" rel="nofollow noopener noreferrer" target="_blank" id="desktop-expanded-image-wrap-link">
<img loading="lazy" alt="expanded image" referrerpolicy="no-referrer" src="" class="img-fluid rounded" id="desktop-expanded-image" style="min-width: 250px;">
</a>
<div class="position-absolute d-flex justify-content-between align-items-center w-100 mt-1">
<a href="/" rel="nofollow noopener noreferrer" target="_blank" class="text-gray-500 font-weight-bold text-left" id="desktop-expanded-image-link">View original</a>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,33 +1,33 @@
{% extends "default.html" %}
{% block content %}
<script src="/assets/js/followers.js?v=241"></script>
<pre>
</pre>
<h5>@{{u.username}}'s followers</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
{% if v.id == u.id %}
<th></th>
{% endif %}
</tr>
</thead>
<tbody id="followers-table">
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
{% if v.id == u.id %}
<td><div class="btn btn-danger pr-2" onclick="removeFollower(event, '{{user.username}}')">Remove follow</div></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% extends "default.html" %}
{% block content %}
<script src="/assets/js/followers.js?v=241"></script>
<pre>
</pre>
<h5>@{{u.username}}'s followers</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
{% if v.id == u.id %}
<th></th>
{% endif %}
</tr>
</thead>
<tbody id="followers-table">
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
{% if v.id == u.id %}
<td><div class="btn btn-danger pr-2" onclick="removeFollower(event, '{{user.username}}')">Remove follow</div></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -1,33 +1,33 @@
{% extends "default.html" %}
{% block content %}
<script src="/assets/js/following.js?v=241"></script>
<pre>
</pre>
<h5>Users followed by @{{u.username}}</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
{% if v.id == u.id %}
<th></th>
{% endif %}
</tr>
</thead>
<tbody id="followers-table">
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
{% if v.id == u.id %}
<td><div class="btn btn-danger" onclick="removeFollowing(event, '{{user.username}}')">Unfollow</div></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% extends "default.html" %}
{% block content %}
<script src="/assets/js/following.js?v=241"></script>
<pre>
</pre>
<h5>Users followed by @{{u.username}}</h5>
<pre></pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
{% if v.id == u.id %}
<th></th>
{% endif %}
</tr>
</thead>
<tbody id="followers-table">
{% for user in users %}
<tr>
<td>{{loop.index}}</td>
<td><a style="color:#{{user.namecolor}}" href="/@{{user.username}}"><img loading="lazy" src="{{user.profile_url}}" class="pp20"><span {% if user.patron %}class="patron" style="background-color:#{{user.namecolor}}"{% endif %}>{{user.username}}</span></a></td>
{% if v.id == u.id %}
<td><div class="btn btn-danger" onclick="removeFollowing(event, '{{user.username}}')">Unfollow</div></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

Some files were not shown because too many files have changed in this diff Show More