forked from MarseyWorld/MarseyWorld
Merge remote-tracking branch 'upstream/frost' into birthgay-staging
commit
c789f6923e
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 }}
|
|
@ -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
|
|
@ -1,6 +1,6 @@
|
|||
image.*
|
||||
video.mp4
|
||||
video.webm
|
||||
unsanitized.mp4
|
||||
cache/
|
||||
__pycache__/
|
||||
.idea/
|
||||
|
|
34
Dockerfile
34
Dockerfile
|
@ -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" ]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
|
@ -2,6 +2,7 @@ version: '2.3'
|
|||
|
||||
services:
|
||||
files:
|
||||
container_name: "rDrama"
|
||||
build:
|
||||
context: .
|
||||
volumes:
|
||||
|
|
|
@ -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 *
|
|
@ -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 *
|
|
@ -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})>"
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)))
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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")
|
|
@ -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
|
||||
|
|
|
@ -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})>"
|
|
@ -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
|
||||
|
|
|
@ -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})>"
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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]
|
|
@ -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
|
|
@ -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}
|
|
@ -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
|
||||
|
|
|
@ -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('&','&')).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('&','&')
|
||||
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("&", "&").replace('<','<').replace('>','>').replace('"', '"').replace("'", "'").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('&','&')).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('&','&')
|
||||
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("&", "&").replace('<','<').replace('>','>').replace('"', '"').replace("'", "'").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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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.")
|
||||
|
|
|
@ -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
|
@ -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
|
@ -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}")
|
|
@ -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('/')
|
|
@ -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
|
@ -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
|
@ -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
|
@ -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!"}
|
|
@ -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
|
@ -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
|
@ -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
|
|
@ -1,91 +1,96 @@
|
|||
{% extends "default.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{SITE_NAME}}</title>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<pre></pre>
|
||||
<pre></pre>
|
||||
<h3> 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> 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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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"> </div>
|
||||
<div class="text-muted"> </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>
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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="I’ve 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 %} <bdi style="color: #{{c.author.titlecolor}}"> {{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>
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 you’re 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 you’re 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 %}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 you’re 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 you’re 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 %}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
{% extends "email/default.html" %}
|
||||
|
||||
{% block title %}Welcome to {{SITE_NAME}}!{% endblock %}</h1>
|
||||
|
||||
{% block content %}
|
||||
<p>Thanks for joining {{SITE_NAME}}. We’re 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 you’re 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}}. We’re 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 you’re 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 %}
|
||||
|
|
|
@ -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 you’re 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 you’re 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 %}
|
||||
|
|
|
@ -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>
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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 %}
|
|
@ -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
Loading…
Reference in New Issue