spooky-22
Aevann1 1 year ago
parent 8fb6067701
commit 6b10d097a2
  1. 8
      .gitattributes
  2. 34
      Dockerfile
  3. 1322
      LICENSE
  4. 10
      dependabot.yml
  5. 250
      files/__main__.py
  6. 40
      files/classes/__init__.py
  7. 28
      files/classes/alts.py
  8. 72
      files/classes/award.py
  9. 146
      files/classes/badges.py
  10. 166
      files/classes/clients.py
  11. 14
      files/classes/domains.py
  12. 140
      files/classes/flags.py
  13. 846
      files/classes/mod_logs.py
  14. 30
      files/classes/subscriptions.py
  15. 1285
      files/classes/user.py
  16. 28
      files/classes/userblock.py
  17. 172
      files/classes/votes.py
  18. 198
      files/helpers/alerts.py
  19. 130
      files/helpers/discord.py
  20. 576
      files/helpers/get.py
  21. 54
      files/helpers/images.py
  22. 36
      files/helpers/lazy.py
  23. 654
      files/helpers/sanitize.py
  24. 46
      files/helpers/security.py
  25. 234
      files/helpers/wrappers.py
  26. 188
      files/mail/__init__.py
  27. 34
      files/routes/__init__.py
  28. 3394
      files/routes/admin.py
  29. 1334
      files/routes/awards.py
  30. 2154
      files/routes/comments.py
  31. 280
      files/routes/discord.py
  32. 150
      files/routes/errors.py
  33. 136
      files/routes/feeds.py
  34. 1196
      files/routes/front.py
  35. 48
      files/routes/giphy.py
  36. 1144
      files/routes/login.py
  37. 572
      files/routes/oauth.py
  38. 3096
      files/routes/posts.py
  39. 278
      files/routes/reporting.py
  40. 578
      files/routes/search.py
  41. 1818
      files/routes/settings.py
  42. 1130
      files/routes/static.py
  43. 2448
      files/routes/users.py
  44. 556
      files/routes/votes.py
  45. 180
      files/templates/admin/admin_home.html
  46. 174
      files/templates/admin/alt_votes.html
  47. 142
      files/templates/admin/app.html
  48. 142
      files/templates/admin/apps.html
  49. 178
      files/templates/admin/badge_grant.html
  50. 72
      files/templates/admin/banned_domains.html
  51. 48
      files/templates/admin/content_stats.html
  52. 110
      files/templates/admin/image_posts.html
  53. 12
      files/templates/admin/new_users.html
  54. 130
      files/templates/admin/removed_posts.html
  55. 52
      files/templates/admin/reported_comments.html
  56. 130
      files/templates/admin/reported_posts.html
  57. 62
      files/templates/admins.html
  58. 222
      files/templates/api.html
  59. 248
      files/templates/authforms.html
  60. 92
      files/templates/award_modal.html
  61. 76
      files/templates/badges.html
  62. 74
      files/templates/ban_modal.html
  63. 46
      files/templates/banned.html
  64. 48
      files/templates/blocks.html
  65. 216
      files/templates/changelog.html
  66. 1852
      files/templates/comments.html
  67. 116
      files/templates/contact.html
  68. 710
      files/templates/default.html
  69. 60
      files/templates/delete_post_modal.html
  70. 64
      files/templates/email/2fa_remove.html
  71. 820
      files/templates/email/default.html
  72. 108
      files/templates/email/email_change.html
  73. 104
      files/templates/email/email_verify.html
  74. 104
      files/templates/email/password_reset.html
  75. 212
      files/templates/emoji_modal.html
  76. 40
      files/templates/errors/400.html
  77. 46
      files/templates/errors/401.html
  78. 42
      files/templates/errors/403.html
  79. 42
      files/templates/errors/404.html
  80. 42
      files/templates/errors/405.html
  81. 40
      files/templates/errors/429.html
  82. 44
      files/templates/errors/500.html
  83. 54
      files/templates/errors/nsfw.html
  84. 38
      files/templates/errors/patron.html
  85. 32
      files/templates/expanded_image_modal.html
  86. 64
      files/templates/followers.html
  87. 64
      files/templates/following.html
  88. 58
      files/templates/forgot_password.html
  89. 1194
      files/templates/formatting.html
  90. 56
      files/templates/gif_modal.html
  91. 564
      files/templates/header.html
  92. 464
      files/templates/home.html
  93. 174
      files/templates/home_comments.html
  94. 966
      files/templates/leaderboard.html
  95. 296
      files/templates/log.html
  96. 244
      files/templates/login.html
  97. 212
      files/templates/login_2fa.html
  98. 68
      files/templates/lost_2fa.html
  99. 54
      files/templates/message.html
  100. 34
      files/templates/message_success.html
  101. Some files were not shown because too many files have changed in this diff Show More

8
.gitattributes vendored

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

1322
LICENSE

File diff suppressed because it is too large Load Diff

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

@ -1,126 +1,126 @@
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 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:
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 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)))

@ -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,424 @@
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': {