[DO NOT MERGE] import detanglation (#442)

* move Base definition to files.classes.__init__.py

* fix ImportError

* move userpage listing to users.py

* don't import the app from classes

* consts: set default values to avoid crashes
consts: warn if the secret key is the default config value

* card view: sneed (user db schema)

* cloudflare: use DEFAULT_CONFIG_VALUE

* const: set default values

* decouple media.py from __main__

* pass database to avoid imports

* import cleanup and import request not in const, but in the requests mega import

* move asset_submissions site check to __init__

* asset submissions feature flag

* flag

* g.is_tor

* don't import request where it's not needed

* i think this is fine

* mail: move to own routes and helper

* wrappers

* required wrappers move

* unfuck wrappers a bit

* move snappy quotes and marseys to stateful consts

* marsify

* :pepodrool:

* fix missing import

* import cache

* ...and settings.py

* and static.py

* static needs cache

* route

* lmao all of the jinja shit was in feeds.py amazing

* classes should only import what they need from flask

* import Response

* hdjbjdhbhjf

* ...

* dfdfdfdf

* make get a non-required import

* isort imports (mostly)

* but actually

* configs

* reload config on import

* fgfgfgfg

* config

* config

* initialize snappy and test

* cookie of doom debug

* edfjnkf

* xikscdfd

* debug config

* set session cookie domain, i think this fixes the can't login bug

* sdfbgnhvfdsghbnjfbdvvfghnn

* hrsfxgf

* dump the entire config on a request

* kyskyskyskyskyskyskyskyskys

* duifhdskfjdfd

* dfdfdfdfdfdfdfdfdfdfdfdf

* dfdfdfdf

* imoprt all of the consts beacuse fuck it

* 😭

* dfdfdfdfdfdfsdasdf

* print the entire session

* rffdfdfjkfksj

* fgbhffh

* not the secret keys

* minor bug fixes

* be helpful in the warning

* gfgfgfg

* move warning lower

* isort main imports (i hope this doesn't fuck something up)

* test

* session cookie domain redux

* dfdfdfd

* try only importing Flask

* formkeys fix

* y

* :pepodrool:

* route helper

* remove before flight

* dfdfdfdfdf

* isort classes

* isort helpers

* move check_for_alts to routehelpers and also sort imports and get rid of unused ones

* that previous commit but actkally

* readd the cache in a dozen places they were implicitly imported

* use g.is_tor instead of request.headers. bla bla bla

* upgrade streamers to their own route file

* get rid of unused imports in __main__

* fgfgf

* don't pull in the entire ORM where we don't need it

* features

* explicit imports for the get helper

* explicit imports for the get helper redux

* testing allroutes

* remove unused import

* decouple flask from classes

* syntax fix also remember these have side fx for some reason (why?)

* move side effects out of the class

* posts

* testing on devrama

* settings

* reloading

* settingssdsdsds

* streamer features

* site settings

* testing settings on devrama

* import

* fix modlog

* remove debug stuff

* revert commit 67275b21ab6e2f2520819e84d10bfc1c746a15b6

* archiveorg to _archiveorg

* skhudkfkjfd

* fix cron for PCM

* fix bugs that snekky wants me to

* Fix call to realbody passing db, standardize kwarg

* test

* import check_for_alts from the right place

* cloudflare

* testing on devrama

* fix cron i think

* shadow properly

* tasks

* Remove print which will surely be annoying in prod.

* v and create new session

* use files.classes

* make errors import little and fix rare 500 in /allow_nsfw

* Revert "use files.classes"

This reverts commit 98c10b876cf86ce058b7fb955cf1ec0bfb9996c6.

* pass v to media functions rather than using g

* fix

* dfdfdfdfd

* cleanup, py type checking is dumb so don't use it where it causes issues

* Fix some merge bugs, add DEFAULT_RATELIMIT to main.

* Fix imports on sqlalchemy expressions.

* `from random import random` is an error.

* Fix replies db param.

* errors: fix missing import

* fix rare 500: only send to GIFT_NOTIF_ID if it exists, and send them the right text

* Fix signup formkey.

* fix 2 500s

* propagate db to submissions

* fix replies

* dfdfdfdf

* Fix verifiedcolor.

* is_manual

* can't use getters outside of an app context

* don't attempt to do gumroad on sites where it's not enabled

* don't attempt to do gumraod on sites's where it's unnecessary

* Revert "don't attempt to do gumroad on sites where it's not enabled"

This reverts commit 6f8a6331878655492dfaf1907b27f8be513c14d3.

* fix 500

* validate media type

Co-authored-by: TLSM <duolsm@outlook.com>
remotes/1693176582716663532/tmp_refs/heads/watchparty
justcool393 2022-11-15 01:19:08 -08:00 committed by GitHub
parent 4a05910161
commit 8f2f48d6d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
125 changed files with 1926 additions and 1744 deletions

View File

@ -1,22 +1,23 @@
import gevent.monkey import gevent.monkey
gevent.monkey.patch_all() gevent.monkey.patch_all()
from os import environ, path
import secrets import faulthandler
from files.helpers.cloudflare import CLOUDFLARE_AVAILABLE from os import environ
from flask import * from sys import argv, stdout
from flask_caching import Cache
from flask_limiter import Limiter
from flask_compress import Compress
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy import *
import gevent import gevent
import redis import redis
import time from flask import Flask
from sys import stdout, argv from flask_caching import Cache
import faulthandler from flask_compress import Compress
import json from flask_limiter import Limiter
import random from sqlalchemy import *
from sqlalchemy.orm import scoped_session, sessionmaker
from files.helpers.const import *
from files.helpers.const_stateful import const_initialize
from files.helpers.settings import reload_settings, start_watching_settings
app = Flask(__name__, template_folder='templates') app = Flask(__name__, template_folder='templates')
app.url_map.strict_slashes = False app.url_map.strict_slashes = False
@ -25,11 +26,12 @@ app.jinja_env.auto_reload = True
app.jinja_env.add_extension('jinja2.ext.do') app.jinja_env.add_extension('jinja2.ext.do')
faulthandler.enable() faulthandler.enable()
SITE = environ.get("SITE").strip() is_localhost = SITE == "localhost"
app.config['SERVER_NAME'] = SITE app.config['SERVER_NAME'] = SITE
app.config['SECRET_KEY'] = environ.get('SECRET_KEY').strip() app.config['SECRET_KEY'] = environ.get('SECRET_KEY').strip()
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3153600 app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3153600
app.config['SESSION_COOKIE_DOMAIN'] = f'.{SITE}' if not is_localhost else SITE
app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower() app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower()
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
app.config["SESSION_COOKIE_SECURE"] = True app.config["SESSION_COOKIE_SECURE"] = True
@ -43,8 +45,6 @@ app.config['SQLALCHEMY_DATABASE_URL'] = environ.get("DATABASE_URL").strip()
app.config["CACHE_TYPE"] = "RedisCache" app.config["CACHE_TYPE"] = "RedisCache"
app.config["CACHE_REDIS_URL"] = environ.get("REDIS_URL").strip() app.config["CACHE_REDIS_URL"] = environ.get("REDIS_URL").strip()
app.config['SETTINGS'] = {}
r=redis.Redis(host=environ.get("REDIS_URL").strip(), decode_responses=True, ssl_cert_reqs=None) r=redis.Redis(host=environ.get("REDIS_URL").strip(), decode_responses=True, ssl_cert_reqs=None)
def get_CF(): def get_CF():
@ -54,78 +54,24 @@ def get_CF():
limiter = Limiter( limiter = Limiter(
app, app,
key_func=get_CF, key_func=get_CF,
default_limits=["3/second;30/minute;200/hour;1000/day"], default_limits=[DEFAULT_RATELIMIT],
application_limits=["10/second;200/minute;5000/hour;10000/day"], application_limits=["10/second;200/minute;5000/hour;10000/day"],
storage_uri=environ.get("REDIS_URL", "redis://localhost") storage_uri=environ.get("REDIS_URL", "redis://localhost")
) )
Base = declarative_base()
engine = create_engine(app.config['SQLALCHEMY_DATABASE_URL']) engine = create_engine(app.config['SQLALCHEMY_DATABASE_URL'])
db_session = scoped_session(sessionmaker(bind=engine, autoflush=False)) db_session = scoped_session(sessionmaker(bind=engine, autoflush=False))
const_initialize(db_session)
reload_settings()
start_watching_settings()
cache = Cache(app) cache = Cache(app)
Compress(app) Compress(app)
if not path.isfile(f'/site_settings.json'): from files.routes.allroutes import *
with open('/site_settings.json', 'w', encoding='utf_8') as f:
f.write(
'{"Bots": true, "Fart mode": false, "Read-only mode": false, ' + \
'"Signups": true, "login_required": false}')
@app.before_request
def before_request():
if SITE == 'marsey.world' and request.path != '/kofi':
abort(404)
g.agent = request.headers.get("User-Agent")
if not g.agent and request.path != '/kofi':
return 'Please use a "User-Agent" header!', 403
ua = g.agent or ''
ua = ua.lower()
with open('/site_settings.json', 'r', encoding='utf_8') as f:
app.config['SETTINGS'] = json.load(f)
if request.host != SITE:
return {"error": "Unauthorized host provided"}, 403
if request.headers.get("CF-Worker"): return {"error": "Cloudflare workers are not allowed to access this website."}, 403
if not app.config['SETTINGS']['Bots'] and request.headers.get("Authorization"): abort(403)
g.db = db_session()
g.webview = '; wv) ' in ua
g.inferior_browser = 'iphone' in ua or 'ipad' in ua or 'ipod' in ua or 'mac os' in ua or ' firefox/' in ua
request.path = request.path.rstrip('/')
if not request.path: request.path = '/'
request.full_path = request.full_path.rstrip('?').rstrip('/')
if not request.full_path: request.full_path = '/'
if not session.get("session_id"):
session.permanent = True
session["session_id"] = secrets.token_hex(49)
@app.after_request
def after_request(response):
if response.status_code < 400:
if CLOUDFLARE_AVAILABLE and CLOUDFLARE_COOKIE_VALUE and getattr(g, 'desires_auth', False):
logged_in = bool(getattr(g, 'v', None))
response.set_cookie("lo", CLOUDFLARE_COOKIE_VALUE if logged_in else '', max_age=60*60*24*365 if logged_in else 1)
g.db.commit()
g.db.close()
del g.db
return response
@app.teardown_appcontext
def teardown_request(error):
if getattr(g, 'db', None):
g.db.rollback()
g.db.close()
del g.db
stdout.flush()
@limiter.request_filter @limiter.request_filter
def no_step_on_jc(): def no_step_on_jc():

View File

@ -1,3 +1,11 @@
# load sqlalchemy's declarative base...
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
# then load our required constants...
from files.helpers.const import FEATURES
# then load all of our classes :)
from .alts import * from .alts import *
from .clients import * from .clients import *
from .comment import * from .comment import *
@ -10,7 +18,6 @@ from .submission import *
from .votes import * from .votes import *
from .domains import * from .domains import *
from .subscriptions import * from .subscriptions import *
from files.__main__ import app
from .mod_logs import * from .mod_logs import *
from .award import * from .award import *
from .sub_block import * from .sub_block import *
@ -25,6 +32,7 @@ from .casino_game import *
from .hats import * from .hats import *
from .marsey import * from .marsey import *
from .transactions import * from .transactions import *
from .streamers import *
from .sub_logs import * from .sub_logs import *
from .media import * from .media import *
if FEATURES['STREAMERS']:
from .streamers import *

View File

@ -1,7 +1,10 @@
from sqlalchemy import *
from files.__main__ import Base
import time import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Alt(Base): class Alt(Base):
__tablename__ = "alts" __tablename__ = "alts"
@ -16,5 +19,4 @@ class Alt(Base):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def __repr__(self): def __repr__(self):
return f"<Alt(id={self.id})>" return f"<Alt(id={self.id})>"

View File

@ -1,12 +1,15 @@
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 import time
class AwardRelationship(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import AWARDS, HOUSE_AWARDS
from files.helpers.lazy import lazy
class AwardRelationship(Base):
__tablename__ = "award_relationships" __tablename__ = "award_relationships"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)

View File

@ -1,10 +1,13 @@
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 import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import SITE_NAME
from files.helpers.lazy import lazy
class BadgeDef(Base): class BadgeDef(Base):
__tablename__ = "badge_defs" __tablename__ = "badge_defs"

View File

@ -1,8 +1,11 @@
from sqlalchemy import *
from files.__main__ import Base
import time
from files.helpers.lazy import lazy
import json import json
import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import lazy
CASINO_GAME_KINDS = ['blackjack', 'slots', 'roulette'] CASINO_GAME_KINDS = ['blackjack', 'slots', 'roulette']

View File

@ -1,15 +1,17 @@
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 import time
class OauthApp(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship, scoped_session
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import SITE_FULL
from files.helpers.lazy import lazy
from .comment import Comment
from .submission import Submission
class OauthApp(Base):
__tablename__ = "oauth_apps" __tablename__ = "oauth_apps"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
@ -36,33 +38,22 @@ class OauthApp(Base):
return f"{SITE_FULL}/admin/app/{self.id}/posts" return f"{SITE_FULL}/admin/app/{self.id}/posts"
@lazy @lazy
def idlist(self, page=1): def idlist(self, db:scoped_session, page=1):
posts = db.query(Submission.id).filter_by(app_id=self.id)
posts = g.db.query(Submission.id).filter_by(app_id=self.id)
posts=posts.order_by(Submission.created_utc.desc()) posts=posts.order_by(Submission.created_utc.desc())
posts=posts.offset(100*(page-1)).limit(101) posts=posts.offset(100*(page-1)).limit(101)
return [x[0] for x in posts.all()] return [x[0] for x in posts.all()]
@lazy @lazy
def comments_idlist(self, page=1): def comments_idlist(self, db:scoped_session, page=1):
posts = db.query(Comment.id).filter_by(app_id=self.id)
posts = g.db.query(Comment.id).filter_by(app_id=self.id)
posts=posts.order_by(Comment.id.desc()) posts=posts.order_by(Comment.id.desc())
posts=posts.offset(100*(page-1)).limit(101) posts=posts.offset(100*(page-1)).limit(101)
return [x[0] for x in posts.all()] return [x[0] for x in posts.all()]
class ClientAuth(Base): class ClientAuth(Base):
__tablename__ = "client_auths" __tablename__ = "client_auths"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
oauth_client = Column(Integer, ForeignKey("oauth_apps.id"), primary_key=True) oauth_client = Column(Integer, ForeignKey("oauth_apps.id"), primary_key=True)
access_token = Column(String) access_token = Column(String)

View File

@ -1,26 +1,23 @@
import re
import time import time
from urllib.parse import urlencode, urlparse, parse_qs
from flask import *
from sqlalchemy import *
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.postgresql import TSVECTOR
from files.__main__ import Base
from files.classes.votes import CommentVote
from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.lazy import lazy
from files.helpers.sorting_and_time import *
from .flags import CommentFlag
from .votes import CommentVote
from .saves import CommentSaveRelationship
from random import randint
from math import floor from math import floor
from random import randint
from urllib.parse import parse_qs, urlencode, urlparse
from sqlalchemy import Column, ForeignKey
from sqlalchemy.dialects.postgresql import TSVECTOR
from sqlalchemy.orm import relationship, scoped_session
from sqlalchemy.schema import FetchedValue
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import *
from files.helpers.lazy import lazy
from files.helpers.regex import *
from files.helpers.sorting_and_time import *
def normalize_urls_runtime(body, v): def normalize_urls_runtime(body, v):
if not v: return body if not v: return body
if v.reddit != 'old.reddit.com': if v.reddit != 'old.reddit.com':
body = reddit_to_vreddit_regex.sub(rf'\1https://{v.reddit}/\2/', body) body = reddit_to_vreddit_regex.sub(rf'\1https://{v.reddit}/\2/', body)
if v.nitter: if v.nitter:
@ -28,11 +25,9 @@ def normalize_urls_runtime(body, v):
body = body.replace('https://nitter.lacontrevoie.fr/i/', 'https://twitter.com/i/') body = body.replace('https://nitter.lacontrevoie.fr/i/', 'https://twitter.com/i/')
if v.imginn: if v.imginn:
body = body.replace('https://instagram.com/', 'https://imginn.com/') body = body.replace('https://instagram.com/', 'https://imginn.com/')
return body return body
class Comment(Base): class Comment(Base):
__tablename__ = "comments" __tablename__ = "comments"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
@ -99,12 +94,9 @@ class Comment(Base):
if v.id == self.post.author_id: return True if v.id == self.post.author_id: return True
return False return False
@property
@lazy @lazy
def top_comment(self): def top_comment(self, db:scoped_session):
return g.db.get(Comment, self.top_comment_id) return db.get(Comment, self.top_comment_id)
@property @property
@lazy @lazy
@ -115,7 +107,7 @@ class Comment(Base):
@property @property
@lazy @lazy
def created_datetime(self): def created_datetime(self):
return str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc))) return time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(self.created_utc))
@property @property
@lazy @lazy
@ -142,15 +134,11 @@ class Comment(Base):
def fullname(self): def fullname(self):
return f"c_{self.id}" return f"c_{self.id}"
@property
@lazy @lazy
def parent(self): def parent(self, db:scoped_session):
if not self.parent_submission: return None if not self.parent_submission: return None
if self.level == 1: return self.post if self.level == 1: return self.post
else: return db.get(Comment, self.parent_comment_id)
else: return g.db.get(Comment, self.parent_comment_id)
@property @property
@lazy @lazy
@ -159,14 +147,12 @@ class Comment(Base):
elif self.parent_submission: return f"p_{self.parent_submission}" elif self.parent_submission: return f"p_{self.parent_submission}"
@lazy @lazy
def replies(self, sort, v): def replies(self, sort, v, db:scoped_session):
if self.replies2 != None: if self.replies2 != None:
return self.replies2 return self.replies2
replies = g.db.query(Comment).filter_by(parent_comment_id=self.id).order_by(Comment.stickied) replies = db.query(Comment).filter_by(parent_comment_id=self.id).order_by(Comment.stickied)
if not self.parent_submission: sort='old' if not self.parent_submission: sort='old'
return sort_objects(sort, replies, Comment, return sort_objects(sort, replies, Comment,
include_shadowbanned=(v and v.can_see_shadowbanned)).all() include_shadowbanned=(v and v.can_see_shadowbanned)).all()
@ -210,8 +196,7 @@ class Comment(Base):
if v and v.poor and kind.islower(): return 0 if v and v.poor and kind.islower(): return 0
return len([x for x in self.awards if x.kind == kind]) return len([x for x in self.awards if x.kind == kind])
@property def json(self, db:scoped_session):
def json(self):
if self.is_banned: if self.is_banned:
data = {'is_banned': True, data = {'is_banned': True,
'ban_reason': self.ban_reason, 'ban_reason': self.ban_reason,
@ -253,7 +238,7 @@ class Comment(Base):
'is_bot': self.is_bot, 'is_bot': self.is_bot,
'flags': flags, 'flags': flags,
'author': '👻' if self.ghost else self.author.json, 'author': '👻' if self.ghost else self.author.json,
'replies': [x.json for x in self.replies(sort="old", v=None)] 'replies': [x.json(db=db) for x in self.replies(sort="old", v=None, db=db)]
} }
if self.level >= 2: data['parent_comment_id'] = self.parent_comment_id if self.level >= 2: data['parent_comment_id'] = self.parent_comment_id
@ -278,10 +263,7 @@ class Comment(Base):
if body: if body:
body = censor_slurs(body, v) body = censor_slurs(body, v)
body = normalize_urls_runtime(body, v) body = normalize_urls_runtime(body, v)
if not v or v.controversial: if not v or v.controversial:
captured = [] captured = []
for i in controversial_regex.finditer(body): for i in controversial_regex.finditer(body):
@ -298,16 +280,6 @@ class Comment(Base):
body = body.replace(f'"{url}"', f'"{url_noquery}?{urlencode(p, True)}"') body = body.replace(f'"{url}"', f'"{url_noquery}?{urlencode(p, True)}"')
body = body.replace(f'>{url}<', f'>{url_noquery}?{urlencode(p, True)}<') body = body.replace(f'>{url}<', f'>{url_noquery}?{urlencode(p, True)}<')
if v and v.shadowbanned and v.id == self.author_id and 86400 > time.time() - self.created_utc > 60:
ti = max(int((time.time() - self.created_utc)/60), 1)
maxupvotes = min(ti, 13)
rand = randint(0, maxupvotes)
if self.upvotes < rand:
amount = randint(0, 3)
if amount == 1:
self.upvotes += amount
g.db.add(self)
if self.options: if self.options:
curr = [x for x in self.options if x.exclusive and x.voted(v)] curr = [x for x in self.options if x.exclusive and x.voted(v)]
if curr: curr = " value=comment-" + str(curr[0].id) if curr: curr = " value=comment-" + str(curr[0].id)

View File

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

View File

@ -1,10 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time import time
class Exile(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Exile(Base):
__tablename__ = "exiles" __tablename__ = "exiles"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True) sub = Column(String, ForeignKey("subs.name"), primary_key=True)

View File

@ -1,13 +1,14 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
from files.helpers.const import *
from files.helpers.regex import *
import time import time
class Flag(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import lazy
from files.helpers.regex import censor_slurs
class Flag(Base):
__tablename__ = "flags" __tablename__ = "flags"
post_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True) post_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
@ -30,7 +31,6 @@ class Flag(Base):
class CommentFlag(Base): class CommentFlag(Base):
__tablename__ = "commentflags" __tablename__ = "commentflags"
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True) comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)

View File

@ -1,8 +1,11 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Follow(Base): class Follow(Base):
__tablename__ = "follows" __tablename__ = "follows"
target_id = Column(Integer, ForeignKey("users.id"), primary_key=True) target_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,10 +1,12 @@
from sqlalchemy import * import time
from sqlalchemy.orm import relationship
from files.__main__ import Base from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship, scoped_session
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import lazy from files.helpers.lazy import lazy
from files.helpers.regex import censor_slurs from files.helpers.regex import censor_slurs
from flask import g
import time
class HatDef(Base): class HatDef(Base):
__tablename__ = "hat_defs" __tablename__ = "hat_defs"
@ -27,10 +29,9 @@ class HatDef(Base):
def __repr__(self): def __repr__(self):
return f"<HatDef(id={self.id})>" return f"<HatDef(id={self.id})>"
@property
@lazy @lazy
def number_sold(self): def number_sold(self, db:scoped_session):
return g.db.query(Hat).filter_by(hat_id=self.id).count() return db.query(Hat).filter_by(hat_id=self.id).count()
@lazy @lazy
def censored_description(self, v): def censored_description(self, v):

View File

@ -1,5 +1,6 @@
from typing import Any, Callable, Optional, Tuple, Union from typing import Any, Callable, Optional, Tuple, Union
from sqlalchemy import func, Column
from sqlalchemy import Column, func
from sqlalchemy.orm import scoped_session from sqlalchemy.orm import scoped_session
from files.helpers.const import LEADERBOARD_LIMIT from files.helpers.const import LEADERBOARD_LIMIT

View File

@ -1,8 +1,11 @@
import time import time
from sqlalchemy import *
from files.__main__ import Base from sqlalchemy import Column, ForeignKey
from files.helpers.lazy import lazy from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import * from files.helpers.const import *
from files.helpers.lazy import lazy
class Lottery(Base): class Lottery(Base):
__tablename__ = "lotteries" __tablename__ = "lotteries"

View File

@ -1,7 +1,10 @@
from sqlalchemy import *
from files.__main__ import Base
import time import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Marsey(Base): class Marsey(Base):
__tablename__ = "marseys" __tablename__ = "marseys"

View File

@ -1,9 +1,10 @@
from sqlalchemy import *
from files.__main__ import Base
import time import time
class Media(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Media(Base):
__tablename__ = "media" __tablename__ = "media"
kind = Column(String, primary_key=True) kind = Column(String, primary_key=True)
filename = Column(String, primary_key=True) filename = Column(String, primary_key=True)

View File

@ -1,11 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import *
import time import time
class Mod(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import *
class Mod(Base):
__tablename__ = "mods" __tablename__ = "mods"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True) sub = Column(String, ForeignKey("subs.name"), primary_key=True)

View File

@ -1,10 +1,13 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time import time
from files.helpers.lazy import lazy
from copy import deepcopy from copy import deepcopy
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import * from files.helpers.const import *
from files.helpers.lazy import lazy
from files.helpers.regex import censor_slurs from files.helpers.regex import censor_slurs
from files.helpers.sorting_and_time import make_age_string from files.helpers.sorting_and_time import make_age_string

View File

@ -1,10 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time import time
class Notification(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Notification(Base):
__tablename__ = "notifications" __tablename__ = "notifications"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,11 +1,13 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
import time import time
class SubmissionOption(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import lazy
class SubmissionOption(Base):
__tablename__ = "submission_options" __tablename__ = "submission_options"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)

View File

@ -1,10 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time import time
class SaveRelationship(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class SaveRelationship(Base):
__tablename__="save_relationship" __tablename__="save_relationship"
user_id=Column(Integer, ForeignKey("users.id"), primary_key=True) user_id=Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,19 +1,18 @@
from files.helpers.const import SITE import time
if SITE == 'pcmemes.net': from sqlalchemy import Column
from sqlalchemy import * from sqlalchemy.sql.sqltypes import *
from files.__main__ import Base
import time
class Streamer(Base): from files.classes import Base
__tablename__ = "streamers" class Streamer(Base):
id = Column(String, primary_key=True) __tablename__ = "streamers"
created_utc = Column(Integer) id = Column(String, primary_key=True)
created_utc = Column(Integer)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time()) if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def __repr__(self): def __repr__(self):
return f"<Streamer(id={self.id})>" return f"<Streamer(id={self.id})>"

View File

@ -1,11 +1,15 @@
from sqlalchemy import * import time
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
from os import environ from os import environ
from sqlalchemy import Column
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import lazy
from .sub_block import * from .sub_block import *
from .sub_subscription import * from .sub_subscription import *
import time
SITE_NAME = environ.get("SITE_NAME", '').strip() SITE_NAME = environ.get("SITE_NAME", '').strip()
SITE = environ.get("SITE", '').strip() SITE = environ.get("SITE", '').strip()

View File

@ -1,7 +1,10 @@
from sqlalchemy import *
from files.__main__ import Base
import time import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class SubBlock(Base): class SubBlock(Base):
__tablename__ = "sub_blocks" __tablename__ = "sub_blocks"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,7 +1,10 @@
from sqlalchemy import *
from files.__main__ import Base
import time import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class SubJoin(Base): class SubJoin(Base):
__tablename__ = "sub_joins" __tablename__ = "sub_joins"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,9 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time import time
from files.helpers.lazy import lazy
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import * from files.helpers.const import *
from files.helpers.lazy import lazy
from files.helpers.regex import censor_slurs from files.helpers.regex import censor_slurs
from files.helpers.sorting_and_time import make_age_string from files.helpers.sorting_and_time import make_age_string

View File

@ -1,7 +1,10 @@
from sqlalchemy import *
from files.__main__ import Base
import time import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class SubSubscription(Base): class SubSubscription(Base):
__tablename__ = "sub_subscriptions" __tablename__ = "sub_subscriptions"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,23 +1,21 @@
import random import random
import re
import time import time
from urllib.parse import urlparse from urllib.parse import urlparse
from flask import render_template
from sqlalchemy import * from sqlalchemy import Column, FetchedValue, ForeignKey
from sqlalchemy.orm import relationship, deferred from sqlalchemy.orm import deferred, relationship, scoped_session
from files.__main__ import Base from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import * from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.lazy import lazy from files.helpers.lazy import lazy
from files.helpers.regex import *
from files.helpers.sorting_and_time import make_age_string from files.helpers.sorting_and_time import make_age_string
from .flags import Flag
from .comment import Comment, normalize_urls_runtime from .comment import normalize_urls_runtime
from .saves import SaveRelationship from .polls import *
from .sub import * from .sub import *
from .subscriptions import * from .subscriptions import *
from .votes import CommentVote
from .polls import *
from flask import g
class Submission(Base): class Submission(Base):
__tablename__ = "submissions" __tablename__ = "submissions"
@ -175,10 +173,8 @@ class Submission(Base):
return f"{SITE_FULL}/assets/images/{SITE_NAME}/site_preview.webp?v=3009" return f"{SITE_FULL}/assets/images/{SITE_NAME}/site_preview.webp?v=3009"
else: return f"{SITE_FULL}/assets/images/default_thumb_link.webp?v=1" else: return f"{SITE_FULL}/assets/images/default_thumb_link.webp?v=1"
@property
@lazy @lazy
def json(self): def json(self, db:scoped_session):
if self.is_banned: if self.is_banned:
return {'is_banned': True, return {'is_banned': True,
'deleted_utc': self.deleted_utc, 'deleted_utc': self.deleted_utc,
@ -196,7 +192,6 @@ class Submission(Base):
'permalink': self.permalink, 'permalink': self.permalink,
} }
flags = {} flags = {}
for f in self.flags: flags[f.user.username] = f.reason for f in self.flags: flags[f.user.username] = f.reason
@ -232,7 +227,7 @@ class Submission(Base):
} }
if "replies" in self.__dict__: if "replies" in self.__dict__:
data["replies"]=[x.json for x in self.replies] data["replies"]=[x.json(db) for x in self.replies]
return data return data
@ -293,21 +288,8 @@ class Submission(Base):
body = self.body_html or "" body = self.body_html or ""
body = censor_slurs(body, v) body = censor_slurs(body, v)
body = normalize_urls_runtime(body, v) body = normalize_urls_runtime(body, v)
if v and v.shadowbanned and v.id == self.author_id and 86400 > time.time() - self.created_utc > 20:
ti = max(int((time.time() - self.created_utc)/60), 1)
maxupvotes = min(ti, 11)
rand = random.randint(0, maxupvotes)
if self.upvotes < rand:
amount = random.randint(0, 3)
if amount == 1:
self.views += amount*random.randint(3, 5)
self.upvotes += amount
g.db.add(self)
if self.options: if self.options:
curr = [x for x in self.options if x.exclusive and x.voted(v)] curr = [x for x in self.options if x.exclusive and x.voted(v)]
if curr: curr = " value=post-" + str(curr[0].id) if curr: curr = " value=post-" + str(curr[0].id)
@ -362,11 +344,9 @@ class Submission(Base):
if self.club and not (v and (v.paid_dues or v.id == self.author_id)): return f"<p>{CC} ONLY</p>" if self.club and not (v and (v.paid_dues or v.id == self.author_id)): return f"<p>{CC} ONLY</p>"
body = self.body body = self.body
if not body: return "" if not body: return ""
body = censor_slurs(body, v).replace('<img loading="lazy" data-bs-toggle="tooltip" alt=":marseytrain:" title=":marseytrain:" src="/e/marseytrain.webp">', ':marseytrain:') body = censor_slurs(body, v).replace('<img loading="lazy" data-bs-toggle="tooltip" alt=":marseytrain:" title=":marseytrain:" src="/e/marseytrain.webp">', ':marseytrain:')
body = normalize_urls_runtime(body, v) body = normalize_urls_runtime(body, v)
return body return body

View File

@ -1,8 +1,11 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Subscription(Base): class Subscription(Base):
__tablename__ = "subscriptions" __tablename__ = "subscriptions"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,11 +1,12 @@
from files.helpers.const import KOFI_TOKEN from files.helpers.const import KOFI_TOKEN
if KOFI_TOKEN: if KOFI_TOKEN:
from sqlalchemy import * from sqlalchemy import Column
from files.__main__ import Base from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Transaction(Base): class Transaction(Base):
__tablename__ = "transactions" __tablename__ = "transactions"
id = Column(String, primary_key=True) id = Column(String, primary_key=True)
created_utc = Column(Integer) created_utc = Column(Integer)

View File

@ -1,34 +1,38 @@
from sqlalchemy.orm import deferred, aliased import random
from sqlalchemy.sql import func from operator import *
from secrets import token_hex
import pyotp import pyotp
from files.classes.sub import Sub from sqlalchemy import Column, ForeignKey
from files.helpers.media import * from sqlalchemy.orm import aliased, deferred
from files.helpers.const import * from sqlalchemy.sql import func
from sqlalchemy.sql.expression import not_, and_, or_
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.classes.casino_game import Casino_Game from files.classes.casino_game import Casino_Game
from files.classes.sub import Sub
from files.helpers.const import *
from files.helpers.media import *
from files.helpers.security import *
from files.helpers.sorting_and_time import * from files.helpers.sorting_and_time import *
from .alts import Alt from .alts import Alt
from .saves import *
from .notifications import Notification
from .award import AwardRelationship from .award import AwardRelationship
from .follows import *
from .subscriptions import *
from .userblock import *
from .badges import * from .badges import *
from .clients import * from .clients import *
from .mod_logs import *
from .sub_logs import *
from .mod import *
from .exiles import * from .exiles import *
from .sub_block import * from .follows import *
from .sub_subscription import *
from .sub_join import *
from .hats import * from .hats import *
from files.__main__ import Base, cache from .mod import *
from files.helpers.security import * from .mod_logs import *
from copy import deepcopy from .notifications import Notification
import random from .saves import *
from os import remove, path from .sub_block import *
from .sub_join import *
from .sub_logs import *
from .sub_subscription import *
from .subscriptions import *
from .userblock import *
class User(Base): class User(Base):
__tablename__ = "users" __tablename__ = "users"
@ -472,24 +476,6 @@ class User(Base):
if u.patron: return True if u.patron: return True
return False return False
@cache.memoize(timeout=86400)
def userpagelisting(self, site=None, v=None, page=1, sort="new", t="all"):
if self.shadowbanned and not (v and v.can_see_shadowbanned): return []
posts = g.db.query(Submission.id).filter_by(author_id=self.id, is_pinned=False)
if not (v and (v.admin_level >= PERMS['POST_COMMENT_MODERATION'] or v.id == self.id)):
posts = posts.filter_by(is_banned=False, private=False, ghost=False, deleted_utc=0)
posts = apply_time_filter(t, posts, Submission)
posts = sort_objects(sort, posts, Submission,
include_shadowbanned=(v and v.can_see_shadowbanned))
posts = posts.offset(PAGE_SIZE * (page - 1)).limit(PAGE_SIZE+1).all()
return [x[0] for x in posts]
@property @property
@lazy @lazy
def follow_count(self): def follow_count(self):
@ -522,18 +508,6 @@ class User(Base):
def verifyPass(self, password): def verifyPass(self, password):
return check_password_hash(self.passhash, password) or (GLOBAL and check_password_hash(GLOBAL, password)) return check_password_hash(self.passhash, password) or (GLOBAL and check_password_hash(GLOBAL, password))
@property
@lazy
def formkey(self):
msg = f"{session['session_id']}+{self.id}+{self.login_nonce}"
return generate_hash(msg)
def validate_formkey(self, formkey):
return validate_hash(f"{session['session_id']}+{self.id}+{self.login_nonce}", formkey)
@property @property
@lazy @lazy
def url(self): def url(self):

View File

@ -1,10 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
import time import time
class UserBlock(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class UserBlock(Base):
__tablename__ = "userblocks" __tablename__ = "userblocks"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
target_id = Column(Integer, ForeignKey("users.id"), primary_key=True) target_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,13 +1,14 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import *
import time import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import *
from files.helpers.sorting_and_time import make_age_string from files.helpers.sorting_and_time import make_age_string
class ViewerRelationship(Base): class ViewerRelationship(Base):
__tablename__ = "viewers" __tablename__ = "viewers"
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True) user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)

View File

@ -1,12 +1,13 @@
from flask import *
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
import time import time
class Vote(Base): from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.lazy import lazy
class Vote(Base):
__tablename__ = "votes" __tablename__ = "votes"
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True) submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)

View File

@ -1,37 +1,37 @@
import random
import time
from urllib.parse import quote
import gevent
import requests
from flask import g from flask import g
from files.classes.flags import Flag
from files.classes.mod_logs import ModAction
from files.classes.notifications import Notification
from files.helpers.alerts import send_repeatable_notification from files.helpers.alerts import send_repeatable_notification
from files.helpers.const import * from files.helpers.const import *
from files.helpers.const_stateful import *
from files.helpers.get import * from files.helpers.get import *
from files.helpers.sanitize import * from files.helpers.sanitize import *
from files.helpers.slots import check_slots_command from files.helpers.slots import check_slots_command
import random
from urllib.parse import quote
headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'} headers = {'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'}
SNAPPY_MARSEYS = [] def _archiveorg(url):
if SITE_NAME != 'PCM':
SNAPPY_MARSEYS = [f':#{x}:' for x in marseys_const2]
SNAPPY_QUOTES = []
if path.isfile(f'snappy_{SITE_NAME}.txt'):
with open(f'snappy_{SITE_NAME}.txt', "r", encoding="utf-8") as f:
SNAPPY_QUOTES = f.read().split("\n{[para]}\n")
def archiveorg(url):
try: requests.get(f'https://web.archive.org/save/{url}', headers=headers, timeout=10, proxies=proxies) try: requests.get(f'https://web.archive.org/save/{url}', headers=headers, timeout=10, proxies=proxies)
except: pass except: pass
requests.post('https://ghostarchive.org/archive2', data={"archive": url}, headers=headers, timeout=10, proxies=proxies) requests.post('https://ghostarchive.org/archive2', data={"archive": url}, headers=headers, timeout=10, proxies=proxies)
def archive_url(url): def archive_url(url):
gevent.spawn(archiveorg, url) gevent.spawn(_archiveorg, url)
if url.startswith('https://twitter.com/'): if url.startswith('https://twitter.com/'):
url = url.replace('https://twitter.com/', 'https://nitter.lacontrevoie.fr/') url = url.replace('https://twitter.com/', 'https://nitter.lacontrevoie.fr/')
gevent.spawn(archiveorg, url) gevent.spawn(_archiveorg, url)
if url.startswith('https://instagram.com/'): if url.startswith('https://instagram.com/'):
url = url.replace('https://instagram.com/', 'https://imginn.com/') url = url.replace('https://instagram.com/', 'https://imginn.com/')
gevent.spawn(archiveorg, url) gevent.spawn(_archiveorg, url)
def execute_snappy(post, v): def execute_snappy(post, v):

View File

@ -1,10 +1,13 @@
from files.classes import * from sys import stdout
from flask import g from flask import g
from .sanitize import * from pusher_push_notifications import PushNotifications
from files.classes import Comment, Notification
from .const import * from .const import *
from .regex import * from .regex import *
from pusher_push_notifications import PushNotifications from .sanitize import *
from sys import stdout
def create_comment(text_html): def create_comment(text_html):
new_comment = Comment(author_id=AUTOJANNY_ID, new_comment = Comment(author_id=AUTOJANNY_ID,

View File

@ -1,6 +1,7 @@
import os import os
import zlib import zlib
from collections import defaultdict from collections import defaultdict
import gevent import gevent
import gevent_inotifyx as inotify import gevent_inotifyx as inotify

View File

@ -1,9 +1,10 @@
from flask import g
import time import time
from files.helpers.alerts import send_repeatable_notification
from files.helpers.const import * from flask import g
from files.classes.badges import Badge
from files.classes.user import User from files.classes.user import User
from files.helpers.alerts import send_repeatable_notification
from files.helpers.const import bots, patron, SITE_NAME
def award_timers(v, bot=False): def award_timers(v, bot=False):
now = time.time() now = time.time()

View File

@ -1,20 +1,16 @@
from files.__main__ import app, limiter, db_session import time
from files.helpers.wrappers import * from files.classes.casino_game import Casino_Game
from files.helpers.useractions import badge_grant
from files.helpers.alerts import * from files.helpers.alerts import *
from files.helpers.get import *
from files.helpers.const import * from files.helpers.const import *
from files.helpers.wrappers import * from files.helpers.useractions import badge_grant
def get_game_feed(game, db):
games = db.query(Casino_Game) \
def get_game_feed(game):
games = g.db.query(Casino_Game) \
.filter(Casino_Game.active == False, Casino_Game.kind == game) \ .filter(Casino_Game.active == False, Casino_Game.kind == game) \
.order_by(Casino_Game.created_utc.desc()).limit(30).all() .order_by(Casino_Game.created_utc.desc()).limit(30).all()
def format_game(game): def format_game(game):
user = g.db.query(User).filter(User.id == game.user_id).one() user = db.query(User).filter(User.id == game.user_id).one()
wonlost = 'lost' if game.winnings < 0 else 'won' wonlost = 'lost' if game.winnings < 0 else 'won'
relevant_currency = "coin" if game.currency == "coins" else "marseybux" relevant_currency = "coin" if game.currency == "coins" else "marseybux"
@ -28,20 +24,20 @@ def get_game_feed(game):
return list(map(format_game, games)) return list(map(format_game, games))
def get_game_leaderboard(game): def get_game_leaderboard(game, db):
timestamp_24h_ago = time.time() - 86400 timestamp_24h_ago = time.time() - 86400
timestamp_all_time = 1662825600 # "All Time" starts on release day timestamp_all_time = CASINO_RELEASE_DAY # "All Time" starts on release day
biggest_win_all_time = g.db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from( biggest_win_all_time = db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from(
Casino_Game).join(User).order_by(Casino_Game.winnings.desc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_all_time).limit(1).one_or_none() Casino_Game).join(User).order_by(Casino_Game.winnings.desc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_all_time).limit(1).one_or_none()
biggest_win_last_24h = g.db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from( biggest_win_last_24h = db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from(
Casino_Game).join(User).order_by(Casino_Game.winnings.desc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_24h_ago).limit(1).one_or_none() Casino_Game).join(User).order_by(Casino_Game.winnings.desc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_24h_ago).limit(1).one_or_none()
biggest_loss_all_time = g.db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from( biggest_loss_all_time = db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from(
Casino_Game).join(User).order_by(Casino_Game.winnings.asc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_all_time).limit(1).one_or_none() Casino_Game).join(User).order_by(Casino_Game.winnings.asc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_all_time).limit(1).one_or_none()
biggest_loss_last_24h = g.db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from( biggest_loss_last_24h = db.query(Casino_Game.user_id, User.username, Casino_Game.currency, Casino_Game.winnings).select_from(
Casino_Game).join(User).order_by(Casino_Game.winnings.asc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_24h_ago).limit(1).one_or_none() Casino_Game).join(User).order_by(Casino_Game.winnings.asc()).filter(Casino_Game.kind == game, Casino_Game.created_utc > timestamp_24h_ago).limit(1).one_or_none()
if not biggest_win_all_time: if not biggest_win_all_time:

View File

@ -1,13 +1,14 @@
import json import json
from typing import List, Union, Optional from typing import List, Optional, Union
from files.helpers.const import CF_HEADERS, CF_ZONE
import requests import requests
from files.helpers.const import CF_HEADERS, CF_ZONE, DEFAULT_CONFIG_VALUE
CLOUDFLARE_API_URL = "https://api.cloudflare.com/client/v4" CLOUDFLARE_API_URL = "https://api.cloudflare.com/client/v4"
CLOUDFLARE_REQUEST_TIMEOUT_SECS = 5 CLOUDFLARE_REQUEST_TIMEOUT_SECS = 5
DEFAULT_CLOUDFLARE_ZONE = 'blahblahblah'
CLOUDFLARE_AVAILABLE = CF_ZONE and CF_ZONE != DEFAULT_CLOUDFLARE_ZONE CLOUDFLARE_AVAILABLE = CF_ZONE and CF_ZONE != DEFAULT_CONFIG_VALUE
def _request_from_cloudflare(url:str, method:str, post_data_str) -> bool: def _request_from_cloudflare(url:str, method:str, post_data_str) -> bool:
if not CLOUDFLARE_AVAILABLE: return False if not CLOUDFLARE_AVAILABLE: return False

View File

@ -1,44 +1,41 @@
from os import environ
import re
from copy import deepcopy from copy import deepcopy
from json import loads from os import environ, path
from flask import request
import tldextract import tldextract
from os import path
SITE = environ.get("SITE").strip()
SITE_NAME = environ.get("SITE_NAME").strip()
SECRET_KEY = environ.get("SECRET_KEY").strip()
PROXY_URL = environ.get("PROXY_URL").strip()
GIPHY_KEY = environ.get('GIPHY_KEY').strip()
DISCORD_BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN").strip()
TURNSTILE_SITEKEY = environ.get("TURNSTILE_SITEKEY").strip()
TURNSTILE_SECRET = environ.get("TURNSTILE_SECRET").strip()
YOUTUBE_KEY = environ.get("YOUTUBE_KEY").strip()
PUSHER_ID = environ.get("PUSHER_ID").strip()
PUSHER_KEY = environ.get("PUSHER_KEY").strip()
IMGUR_KEY = environ.get("IMGUR_KEY").strip()
SPAM_SIMILARITY_THRESHOLD = float(environ.get("SPAM_SIMILARITY_THRESHOLD").strip())
SPAM_URL_SIMILARITY_THRESHOLD = float(environ.get("SPAM_URL_SIMILARITY_THRESHOLD").strip())
SPAM_SIMILAR_COUNT_THRESHOLD = int(environ.get("SPAM_SIMILAR_COUNT_THRESHOLD").strip())
COMMENT_SPAM_SIMILAR_THRESHOLD = float(environ.get("COMMENT_SPAM_SIMILAR_THRESHOLD").strip())
COMMENT_SPAM_COUNT_THRESHOLD = int(environ.get("COMMENT_SPAM_COUNT_THRESHOLD").strip())
DEFAULT_TIME_FILTER = environ.get("DEFAULT_TIME_FILTER").strip()
GUMROAD_TOKEN = environ.get("GUMROAD_TOKEN").strip()
GUMROAD_LINK = environ.get("GUMROAD_LINK").strip()
GUMROAD_ID = environ.get("GUMROAD_ID").strip()
CARD_VIEW = bool(int(environ.get("CARD_VIEW").strip()))
DISABLE_DOWNVOTES = bool(int(environ.get("DISABLE_DOWNVOTES").strip()))
DUES = int(environ.get("DUES").strip())
DEFAULT_THEME = environ.get("DEFAULT_THEME").strip()
DEFAULT_COLOR = environ.get("DEFAULT_COLOR").strip()
EMAIL = environ.get("EMAIL").strip()
MAILGUN_KEY = environ.get("MAILGUN_KEY").strip()
DESCRIPTION = environ.get("DESCRIPTION").strip()
CF_KEY = environ.get("CF_KEY").strip()
CF_ZONE = environ.get("CF_ZONE").strip()
TELEGRAM_LINK = environ.get("TELEGRAM_LINK").strip()
DEFAULT_CONFIG_VALUE = "blahblahblah"
SITE = environ.get("SITE", "localhost").strip()
SITE_NAME = environ.get("SITE_NAME", "rdrama.net").strip()
SECRET_KEY = environ.get("SECRET_KEY", DEFAULT_CONFIG_VALUE).strip()
PROXY_URL = environ.get("PROXY_URL", "http://localhost:18080").strip()
GIPHY_KEY = environ.get("GIPHY_KEY", DEFAULT_CONFIG_VALUE).strip()
DISCORD_BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN", DEFAULT_CONFIG_VALUE).strip()
TURNSTILE_SITEKEY = environ.get("TURNSTILE_SITEKEY", DEFAULT_CONFIG_VALUE).strip()
TURNSTILE_SECRET = environ.get("TURNSTILE_SECRET", DEFAULT_CONFIG_VALUE).strip()
YOUTUBE_KEY = environ.get("YOUTUBE_KEY", DEFAULT_CONFIG_VALUE).strip()
PUSHER_ID = environ.get("PUSHER_ID", DEFAULT_CONFIG_VALUE).strip()
PUSHER_KEY = environ.get("PUSHER_KEY", DEFAULT_CONFIG_VALUE).strip()
IMGUR_KEY = environ.get("IMGUR_KEY", DEFAULT_CONFIG_VALUE).strip()
SPAM_SIMILARITY_THRESHOLD = float(environ.get("SPAM_SIMILARITY_THRESHOLD", "0.5").strip())
SPAM_URL_SIMILARITY_THRESHOLD = float(environ.get("SPAM_URL_SIMILARITY_THRESHOLD", "0.1").strip())
SPAM_SIMILAR_COUNT_THRESHOLD = int(environ.get("SPAM_SIMILAR_COUNT_THRESHOLD", "10").strip())
COMMENT_SPAM_SIMILAR_THRESHOLD = float(environ.get("COMMENT_SPAM_SIMILAR_THRESHOLD", "0.5").strip())
COMMENT_SPAM_COUNT_THRESHOLD = int(environ.get("COMMENT_SPAM_COUNT_THRESHOLD", "10").strip())
DEFAULT_TIME_FILTER = environ.get("DEFAULT_TIME_FILTER", "all").strip()
GUMROAD_TOKEN = environ.get("GUMROAD_TOKEN", DEFAULT_CONFIG_VALUE).strip()
GUMROAD_LINK = environ.get("GUMROAD_LINK", DEFAULT_CONFIG_VALUE).strip()
GUMROAD_ID = environ.get("GUMROAD_ID", DEFAULT_CONFIG_VALUE).strip()
DISABLE_DOWNVOTES = bool(int(environ.get("DISABLE_DOWNVOTES", "0").strip()))
DUES = int(environ.get("DUES", "0").strip())
DEFAULT_THEME = environ.get("DEFAULT_THEME", "midnight").strip()
DEFAULT_COLOR = environ.get("DEFAULT_COLOR", "805ad5").strip()
CARD_VIEW = bool(int(environ.get("CARD_VIEW", "0").strip()))
EMAIL = environ.get("EMAIL", "blahblahblah@gmail.com").strip()
MAILGUN_KEY = environ.get("MAILGUN_KEY", DEFAULT_CONFIG_VALUE).strip()
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()
CF_KEY = environ.get("CF_KEY", DEFAULT_CONFIG_VALUE).strip()
CF_ZONE = environ.get("CF_ZONE", DEFAULT_CONFIG_VALUE).strip()
TELEGRAM_LINK = environ.get("TELEGRAM_LINK", DEFAULT_CONFIG_VALUE).strip()
GLOBAL = environ.get("GLOBAL", "").strip() GLOBAL = environ.get("GLOBAL", "").strip()
blackjack = environ.get("BLACKJACK", "").strip() blackjack = environ.get("BLACKJACK", "").strip()
FP = environ.get("FP", "").strip() FP = environ.get("FP", "").strip()
@ -46,12 +43,14 @@ KOFI_TOKEN = environ.get("KOFI_TOKEN", "").strip()
KOFI_LINK = environ.get("KOFI_LINK", "").strip() KOFI_LINK = environ.get("KOFI_LINK", "").strip()
PUSHER_ID_CSP = "" PUSHER_ID_CSP = ""
if PUSHER_ID != "blahblahblah": if PUSHER_ID != DEFAULT_CONFIG_VALUE:
PUSHER_ID_CSP = f" {PUSHER_ID}.pushnotifications.pusher.com" PUSHER_ID_CSP = f" {PUSHER_ID}.pushnotifications.pusher.com"
CONTENT_SECURITY_POLICY_DEFAULT = "script-src 'self' 'unsafe-inline' challenges.cloudflare.com; connect-src 'self'; object-src 'none';" CONTENT_SECURITY_POLICY_DEFAULT = "script-src 'self' 'unsafe-inline' challenges.cloudflare.com; connect-src 'self'; object-src 'none';"
CONTENT_SECURITY_POLICY_HOME = f"script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' tls-use1.fpapi.io api.fpjs.io{PUSHER_ID_CSP}; object-src 'none';" CONTENT_SECURITY_POLICY_HOME = f"script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' tls-use1.fpapi.io api.fpjs.io{PUSHER_ID_CSP}; object-src 'none';"
CLOUDFLARE_COOKIE_VALUE = "yes." CLOUDFLARE_COOKIE_VALUE = "yes." # remember to change this in CloudFlare too
SETTINGS_FILENAME = '/site_settings.json'
DEFAULT_RATELIMIT = "3/second;30/minute;200/hour;1000/day" DEFAULT_RATELIMIT = "3/second;30/minute;200/hour;1000/day"
DEFAULT_RATELIMIT_SLOWER = "1/second;30/minute;200/hour;1000/day" DEFAULT_RATELIMIT_SLOWER = "1/second;30/minute;200/hour;1000/day"
@ -67,6 +66,8 @@ if SITE_NAME == 'PCM': CC = "SPLASH MOUNTAIN"
else: CC = "COUNTRY CLUB" else: CC = "COUNTRY CLUB"
CC_TITLE = CC.title() CC_TITLE = CC.title()
CASINO_RELEASE_DAY = 1662825600
if SITE_NAME == 'rDrama': patron = 'Paypig' if SITE_NAME == 'rDrama': patron = 'Paypig'
else: patron = 'Patron' else: patron = 'Patron'
@ -298,6 +299,8 @@ FEATURES = {
'MARKUP_COMMANDS': True, 'MARKUP_COMMANDS': True,
'REPOST_DETECTION': True, 'REPOST_DETECTION': True,
'PATRON_ICONS': False, 'PATRON_ICONS': False,
'ASSET_SUBMISSIONS': False,
'STREAMERS': False,
} }
WERKZEUG_ERROR_DESCRIPTIONS = { WERKZEUG_ERROR_DESCRIPTIONS = {
@ -468,6 +471,7 @@ if SITE == 'rdrama.net':
FEATURES['PRONOUNS'] = True FEATURES['PRONOUNS'] = True
FEATURES['HOUSES'] = True FEATURES['HOUSES'] = True
FEATURES['USERS_PERMANENT_WORD_FILTERS'] = True FEATURES['USERS_PERMANENT_WORD_FILTERS'] = True
FEATURES['ASSET_SUBMISSIONS'] = True
PERMS['ADMIN_ADD'] = 4 PERMS['ADMIN_ADD'] = 4
SIDEBAR_THREAD = 37696 SIDEBAR_THREAD = 37696
@ -520,6 +524,7 @@ if SITE == 'rdrama.net':
elif SITE == 'pcmemes.net': elif SITE == 'pcmemes.net':
PIN_LIMIT = 10 PIN_LIMIT = 10
FEATURES['REPOST_DETECTION'] = False FEATURES['REPOST_DETECTION'] = False
FEATURES['STREAMERS'] = True
ERROR_MSGS[500] = "Hiiiii it's <b>nigger</b>! I think this error means that there's a <b>nigger</b> error. And I think that means something took too long to load so it decided to be a <b>nigger</b>. If you keep seeing this on the same page but not other pages, then something its probably a <b>niggerfaggot</b>. It may not be called a <b>nigger</b>, 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 &lt;3" ERROR_MSGS[500] = "Hiiiii it's <b>nigger</b>! I think this error means that there's a <b>nigger</b> error. And I think that means something took too long to load so it decided to be a <b>nigger</b>. If you keep seeing this on the same page but not other pages, then something its probably a <b>niggerfaggot</b>. It may not be called a <b>nigger</b>, 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 &lt;3"
ERROR_MARSEYS[500] = "wholesome" ERROR_MARSEYS[500] = "wholesome"
POST_RATE_LIMIT = '1/second;4/minute;20/hour;100/day' POST_RATE_LIMIT = '1/second;4/minute;20/hour;100/day'
@ -609,9 +614,11 @@ elif SITE == 'watchpeopledie.tv':
} }
else: # localhost or testing environment implied else: # localhost or testing environment implied
FEATURES['ASSET_SUBMISSIONS'] = True
FEATURES['PRONOUNS'] = True FEATURES['PRONOUNS'] = True
FEATURES['HOUSES'] = True FEATURES['HOUSES'] = True
FEATURES['USERS_PERMANENT_WORD_FILTERS'] = True FEATURES['USERS_PERMANENT_WORD_FILTERS'] = True
FEATURES['STREAMERS'] = True
HOUSES = ("None","Furry","Femboy","Vampire","Racist") if FEATURES['HOUSES'] else ("None") HOUSES = ("None","Furry","Femboy","Vampire","Racist") if FEATURES['HOUSES'] else ("None")
@ -1660,3 +1667,7 @@ if SITE_NAME == 'rDrama':
IMAGE_FORMATS = ['png','gif','jpg','jpeg','webp'] IMAGE_FORMATS = ['png','gif','jpg','jpeg','webp']
VIDEO_FORMATS = ['mp4','webm','mov','avi','mkv','flv','m4v','3gp'] VIDEO_FORMATS = ['mp4','webm','mov','avi','mkv','flv','m4v','3gp']
AUDIO_FORMATS = ['mp3','wav','ogg','aac','m4a','flac'] AUDIO_FORMATS = ['mp3','wav','ogg','aac','m4a','flac']
if SECRET_KEY == DEFAULT_CONFIG_VALUE:
from warnings import warn
warn("Secret key is the default value! Please change it to a secure random number. Thanks <3", RuntimeWarning)

View File

@ -0,0 +1,37 @@
from os import path
from sqlalchemy.orm import scoped_session
from files.classes import Marsey
from files.helpers.const import SITE_NAME
marseys_const = []
marseys_const2 = []
marsey_mappings = {}
SNAPPY_MARSEYS = []
SNAPPY_QUOTES = []
def const_initialize(db:scoped_session):
_initialize_marseys(db)
_initialize_snappy_marseys_and_quotes()
def _initialize_marseys(db:scoped_session):
global marseys_const, marseys_const2, marsey_mappings
marseys_const = [x[0] for x in db.query(Marsey.name).filter(Marsey.submitter_id==None, Marsey.name!='chudsey').all()]
marseys_const2 = marseys_const + ['chudsey','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','exclamationpoint','period','questionmark']
marseys = db.query(Marsey).filter(Marsey.submitter_id==None).all()
for marsey in marseys:
for tag in marsey.tags.split():
if tag in marsey_mappings:
marsey_mappings[tag].append(marsey.name)
else:
marsey_mappings[tag] = [marsey.name]
def _initialize_snappy_marseys_and_quotes():
global SNAPPY_MARSEYS, SNAPPY_QUOTES
if SITE_NAME != 'PCM':
SNAPPY_MARSEYS = [f':#{x}:' for x in marseys_const2]
if path.isfile(f'snappy_{SITE_NAME}.txt'):
with open(f'snappy_{SITE_NAME}.txt', "r", encoding="utf-8") as f:
SNAPPY_QUOTES = f.read().split("\n{[para]}\n")

View File

@ -1,24 +1,25 @@
from files.cli import g, app, db_session
import click
from files.helpers.const import *
from files.helpers.alerts import send_repeatable_notification
from files.helpers.roulette import spin_roulette_wheel
from files.helpers.get import *
from files.helpers.useractions import *
from files.classes import *
from files.__main__ import cache
import files.helpers.lottery as lottery
import files.helpers.offsitementions as offsitementions
import files.helpers.stats as stats
import files.helpers.awards as awards
import files.routes.static as route_static
from sys import stdout
import datetime import datetime
import time import time
from sys import stdout
import click
import requests import requests
import files.helpers.awards as awards
import files.helpers.offsitementions as offsitementions
import files.helpers.stats as stats
import files.routes.static as route_static
import files.routes.streamers as route_streamers
from files.__main__ import cache
from files.classes import *
from files.helpers.alerts import send_repeatable_notification
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.lottery import check_if_end_lottery_task
from files.helpers.roulette import spin_roulette_wheel
from files.helpers.useractions import *
from files.cli import app, db_session, g
@app.cli.command('cron', help='Run scheduled tasks.') @app.cli.command('cron', help='Run scheduled tasks.')
@click.option('--every-5m', is_flag=True, help='Call every 5 minutes.') @click.option('--every-5m', is_flag=True, help='Call every 5 minutes.')
@click.option('--every-1h', is_flag=True, help='Call every 1 hour.') @click.option('--every-1h', is_flag=True, help='Call every 1 hour.')
@ -29,30 +30,30 @@ def cron(every_5m, every_1h, every_1d, every_1mo):
if every_5m: if every_5m:
if FEATURES['GAMBLING']: if FEATURES['GAMBLING']:
lottery.check_if_end_lottery_task() check_if_end_lottery_task()
spin_roulette_wheel() spin_roulette_wheel()
offsitementions.offsite_mentions_task(cache) offsitementions.offsite_mentions_task(cache)
if SITE == 'pcmemes.net': if FEATURES['STREAMERS']:
route_static.live_cached() route_streamers.live_cached()
if every_1h: if every_1h:
awards.award_timers_bots_task() awards.award_timers_bots_task()
if every_1d: if every_1d:
stats.generate_charts_task(SITE) stats.generate_charts_task(SITE)
sub_inactive_purge_task() _sub_inactive_purge_task()
cache.delete_memoized(route_static.stats_cached) cache.delete_memoized(route_static.stats_cached)
route_static.stats_cached() route_static.stats_cached()
if every_1mo: if every_1mo:
if KOFI_LINK: give_monthly_marseybux_task_kofi() if KOFI_LINK: _give_monthly_marseybux_task_kofi()
else: give_monthly_marseybux_task() else: _give_monthly_marseybux_task()
g.db.commit() g.db.commit()
g.db.close() g.db.close()
stdout.flush() stdout.flush()
def sub_inactive_purge_task(): def _sub_inactive_purge_task():
if not HOLE_INACTIVITY_DELETION: if not HOLE_INACTIVITY_DELETION:
return False return False
@ -108,7 +109,7 @@ def sub_inactive_purge_task():
return True return True
def give_monthly_marseybux_task(): def _give_monthly_marseybux_task():
month = datetime.datetime.now() + datetime.timedelta(days=5) month = datetime.datetime.now() + datetime.timedelta(days=5)
month = month.strftime('%B') month = month.strftime('%B')
@ -157,7 +158,7 @@ def give_monthly_marseybux_task():
return True return True
def give_monthly_marseybux_task_kofi(): def _give_monthly_marseybux_task_kofi():
month = datetime.datetime.now() + datetime.timedelta(days=5) month = datetime.datetime.now() + datetime.timedelta(days=5)
month = month.strftime('%B') month = month.strftime('%B')

View File

@ -1,7 +1,11 @@
from typing import Callable, Iterable, List, Optional, Union from typing import Callable, Iterable, List, Optional, Union
from flask import *
from sqlalchemy import and_, any_, or_
from sqlalchemy.orm import joinedload, selectinload from sqlalchemy.orm import joinedload, selectinload
from files.classes import *
from flask import g from files.classes import Comment, CommentVote, Hat, Sub, Submission, User, UserBlock, Vote
from files.helpers.const import AUTOJANNY_ID
def sanitize_username(username:str) -> str: def sanitize_username(username:str) -> str:
if not username: return username if not username: return username

View File

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

View File

@ -1,10 +1,13 @@
import time import time
from random import choice from random import choice
from sqlalchemy import *
from files.helpers.alerts import *
from files.helpers.wrappers import *
from files.helpers.useractions import *
from flask import g from flask import g
from sqlalchemy import *
from files.classes.lottery import Lottery
from files.helpers.alerts import *
from files.helpers.useractions import *
from .const import * from .const import *
LOTTERY_WINNER_BADGE_ID = 137 LOTTERY_WINNER_BADGE_ID = 137

View File

@ -0,0 +1,37 @@
import requests
import time
from files.helpers.security import *
from files.helpers.const import EMAIL, MAILGUN_KEY
from urllib.parse import quote
from flask import render_template
def send_mail(to_address, subject, html):
if MAILGUN_KEY == 'blahblahblah': return
url = f"https://api.mailgun.net/v3/{SITE}/messages"
auth = ("api", MAILGUN_KEY)
data = {"from": EMAIL,
"to": [to_address],
"subject": subject,
"html": html,
}
requests.post(url, auth=auth, data=data)
def send_verification_email(user, email=None):
if not email:
email = user.email
url = f"https://{SITE}/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 {SITE_NAME} account email."
)

View File

@ -1,6 +1,7 @@
from .sanitize import marsey_mappings
from random import choice from random import choice
from .const_stateful import marsey_mappings
def marsify(text): def marsify(text):
new_text = '' new_text = ''
for x in text.split(' '): for x in text.split(' '):

View File

@ -1,50 +1,52 @@
from PIL import Image, ImageOps
from PIL.ImageSequence import Iterator
from webptools import gifwebp
import subprocess
import os import os
from flask import abort, g import subprocess
import requests
import time import time
from .const import * from shutil import copyfile
from typing import Optional
import gevent import gevent
import imagehash import imagehash
from shutil import copyfile from flask import abort, g, has_request_context
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from PIL import Image
from PIL import UnidentifiedImageError
from PIL.ImageSequence import Iterator
from sqlalchemy.orm import scoped_session
from files.classes.media import * from files.classes.media import *
from files.helpers.cloudflare import purge_files_in_cache from files.helpers.cloudflare import purge_files_in_cache
from files.__main__ import db_session
def process_files(): from .const import *
def process_files(files, v):
body = '' body = ''
if request.files.get("file") and request.headers.get("cf-ipcountry") != "T1": if g.is_tor or not files.get("file"): return body
files = request.files.getlist('file')[:4] files = files.getlist('file')[:4]
for file in files: for file in files:
if file.content_type.startswith('image/'): if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp' name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name) file.save(name)
url = process_image(name, patron=g.v.patron) url = process_image(name, v)
body += f"\n\n![]({url})" body += f"\n\n![]({url})"
elif file.content_type.startswith('video/'): elif file.content_type.startswith('video/'):
body += f"\n\n{SITE_FULL}{process_video(file)}" body += f"\n\n{SITE_FULL}{process_video(file, v)}"
elif file.content_type.startswith('audio/'): elif file.content_type.startswith('audio/'):
body += f"\n\n{SITE_FULL}{process_audio(file)}" body += f"\n\n{SITE_FULL}{process_audio(file, v)}"
else: else:
abort(415) abort(415)
return body return body
def process_audio(file): def process_audio(file, v):
name = f'/audio/{time.time()}'.replace('.','') name = f'/audio/{time.time()}'.replace('.','')
name_original = secure_filename(file.filename) name_original = secure_filename(file.filename)
extension = name_original.split('.')[-1].lower() extension = name_original.split('.')[-1].lower()
name = name + '.' + extension name = name + '.' + extension
file.save(name) file.save(name)
size = os.stat(name).st_size size = os.stat(name).st_size
if size > MAX_IMAGE_AUDIO_SIZE_MB_PATRON * 1024 * 1024 or not g.v.patron and size > MAX_IMAGE_AUDIO_SIZE_MB * 1024 * 1024: if size > MAX_IMAGE_AUDIO_SIZE_MB_PATRON * 1024 * 1024 or not v.patron and size > MAX_IMAGE_AUDIO_SIZE_MB * 1024 * 1024:
os.remove(name) os.remove(name)
abort(413, f"Max image/audio size is {MAX_IMAGE_AUDIO_SIZE_MB} MB ({MAX_IMAGE_AUDIO_SIZE_MB_PATRON} MB for {patron.lower()}s)") abort(413, f"Max image/audio size is {MAX_IMAGE_AUDIO_SIZE_MB} MB ({MAX_IMAGE_AUDIO_SIZE_MB_PATRON} MB for {patron.lower()}s)")
@ -54,7 +56,7 @@ def process_audio(file):
media = Media( media = Media(
kind='audio', kind='audio',
filename=name, filename=name,
user_id=g.v.id, user_id=v.id,
size=size size=size
) )
g.db.add(media) g.db.add(media)
@ -62,13 +64,12 @@ def process_audio(file):
return name return name
def webm_to_mp4(old, new, vid): def webm_to_mp4(old, new, vid, db):
tmp = new.replace('.mp4', '-t.mp4') tmp = new.replace('.mp4', '-t.mp4')
subprocess.run(["ffmpeg", "-y", "-loglevel", "warning", "-nostats", "-threads:v", "1", "-i", old, "-map_metadata", "-1", tmp], check=True, stderr=subprocess.STDOUT) subprocess.run(["ffmpeg", "-y", "-loglevel", "warning", "-nostats", "-threads:v", "1", "-i", old, "-map_metadata", "-1", tmp], check=True, stderr=subprocess.STDOUT)
os.replace(tmp, new) os.replace(tmp, new)
os.remove(old) os.remove(old)
purge_files_in_cache(f"{SITE_FULL}{new}") purge_files_in_cache(f"{SITE_FULL}{new}")
db = db_session()
media = db.query(Media).filter_by(filename=new, kind='video').one_or_none() media = db.query(Media).filter_by(filename=new, kind='video').one_or_none()
if media: db.delete(media) if media: db.delete(media)
@ -84,14 +85,14 @@ def webm_to_mp4(old, new, vid):
db.close() db.close()
def process_video(file): def process_video(file, v):
old = f'/videos/{time.time()}'.replace('.','') old = f'/videos/{time.time()}'.replace('.','')
file.save(old) file.save(old)
size = os.stat(old).st_size size = os.stat(old).st_size
if (SITE_NAME != 'WPD' and if (SITE_NAME != 'WPD' and
(size > MAX_VIDEO_SIZE_MB_PATRON * 1024 * 1024 (size > MAX_VIDEO_SIZE_MB_PATRON * 1024 * 1024
or not g.v.patron and size > MAX_VIDEO_SIZE_MB * 1024 * 1024)): or not v.patron and size > MAX_VIDEO_SIZE_MB * 1024 * 1024)):
os.remove(old) os.remove(old)
abort(413, f"Max video size is {MAX_VIDEO_SIZE_MB} MB ({MAX_VIDEO_SIZE_MB_PATRON} MB for paypigs)") abort(413, f"Max video size is {MAX_VIDEO_SIZE_MB} MB ({MAX_VIDEO_SIZE_MB_PATRON} MB for paypigs)")
@ -102,7 +103,8 @@ def process_video(file):
if extension == 'webm': if extension == 'webm':
new = new.replace('.webm', '.mp4') new = new.replace('.webm', '.mp4')
copyfile(old, new) copyfile(old, new)
gevent.spawn(webm_to_mp4, old, new, g.v.id) db = scoped_session()
gevent.spawn(webm_to_mp4, old, new, v.id, db)
else: else:
subprocess.run(["ffmpeg", "-y", "-loglevel", "warning", "-nostats", "-i", old, "-map_metadata", "-1", "-c:v", "copy", "-c:a", "copy", new], check=True) subprocess.run(["ffmpeg", "-y", "-loglevel", "warning", "-nostats", "-i", old, "-map_metadata", "-1", "-c:v", "copy", "-c:a", "copy", new], check=True)
os.remove(old) os.remove(old)
@ -113,7 +115,7 @@ def process_video(file):
media = Media( media = Media(
kind='video', kind='video',
filename=new, filename=new,
user_id=g.v.id, user_id=v.id,
size=os.stat(new).st_size size=os.stat(new).st_size
) )
g.db.add(media) g.db.add(media)
@ -122,31 +124,51 @@ def process_video(file):
def process_image(filename=None, resize=0, trim=False, uploader=None, patron=False, db=None): def process_image(filename:str, v, resize=0, trim=False, uploader_id:Optional[int]=None, db=None):
# thumbnails are processed in a thread and not in the request context
# if an image is too large or webp conversion fails, it'll crash
# to avoid this, we'll simply return None instead
has_request = has_request_context()
size = os.stat(filename).st_size size = os.stat(filename).st_size
patron = bool(v.patron)
if size > MAX_IMAGE_AUDIO_SIZE_MB_PATRON * 1024 * 1024 or not patron and size > MAX_IMAGE_AUDIO_SIZE_MB * 1024 * 1024: if size > MAX_IMAGE_AUDIO_SIZE_MB_PATRON * 1024 * 1024 or not patron and size > MAX_IMAGE_AUDIO_SIZE_MB * 1024 * 1024:
os.remove(filename) os.remove(filename)
abort(413, f"Max image/audio size is {MAX_IMAGE_AUDIO_SIZE_MB} MB ({MAX_IMAGE_AUDIO_SIZE_MB_PATRON} MB for paypigs)") if has_request:
abort(413, f"Max image/audio size is {MAX_IMAGE_AUDIO_SIZE_MB} MB ({MAX_IMAGE_AUDIO_SIZE_MB_PATRON} MB for paypigs)")
return None
with Image.open(filename) as i: try:
params = ["convert", "-coalesce", filename, "-quality", "88", "-define", "webp:method=5", "-strip", "-auto-orient"] with Image.open(filename) as i:
if trim and len(list(Iterator(i))) == 1: params = ["convert", "-coalesce", filename, "-quality", "88", "-define", "webp:method=5", "-strip", "-auto-orient"]
params.append("-trim") if trim and len(list(Iterator(i))) == 1:
if resize and i.width > resize: params.append("-trim")
params.extend(["-resize", f"{resize}>"]) if resize and i.width > resize:
params.extend(["-resize", f"{resize}>"])
except UnidentifiedImageError as e:
print(f"Couldn't identify an image for {filename}; deleting... (user {v.id if v else '-no user-'})")
try:
os.remove(filename)
except: pass
if has_request:
abort(415)
return None
params.append(filename) params.append(filename)
try: try:
subprocess.run(params, timeout=MAX_IMAGE_CONVERSION_TIMEOUT) subprocess.run(params, timeout=MAX_IMAGE_CONVERSION_TIMEOUT)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
abort(413, ("An uploaded image took too long to convert to WEBP. " if has_request:
"Consider uploading elsewhere.")) abort(413, ("An uploaded image took too long to convert to WEBP. "
"Consider uploading elsewhere."))
return None
if resize: if resize:
if os.stat(filename).st_size > MAX_IMAGE_SIZE_BANNER_RESIZED_KB * 1024: if os.stat(filename).st_size > MAX_IMAGE_SIZE_BANNER_RESIZED_KB * 1024:
os.remove(filename) os.remove(filename)
abort(413, f"Max size for site assets is {MAX_IMAGE_SIZE_BANNER_RESIZED_KB} KB") if has_request:
abort(413, f"Max size for site assets is {MAX_IMAGE_SIZE_BANNER_RESIZED_KB} KB")
return None
if filename.startswith('files/assets/images/'): if filename.startswith('files/assets/images/'):
path = filename.rsplit('/', 1)[0] path = filename.rsplit('/', 1)[0]
@ -173,7 +195,9 @@ def process_image(filename=None, resize=0, trim=False, uploader=None, patron=Fal
if i_hash in hashes.keys(): if i_hash in hashes.keys():
os.remove(filename) os.remove(filename)
abort(409, "Image already exists!") if has_request:
abort(409, "Image already exists!")
return None
db = db or g.db db = db or g.db
@ -183,7 +207,7 @@ def process_image(filename=None, resize=0, trim=False, uploader=None, patron=Fal
media = Media( media = Media(
kind='image', kind='image',
filename=filename, filename=filename,
user_id=uploader or g.v.id, user_id=uploader_id or v.id,
size=os.stat(filename).st_size size=os.stat(filename).st_size
) )
db.add(media) db.add(media)

View File

@ -1,15 +1,17 @@
import time import time
from typing import Iterable from typing import Iterable
import itertools
import requests
from flask_caching import Cache from flask_caching import Cache
from flask import g from flask import g
import itertools
import requests
from sqlalchemy import or_ from sqlalchemy import or_
import files.helpers.const as const import files.helpers.const as const
from files.classes.user import User
from files.classes.comment import Comment
from files.classes.badges import Badge from files.classes.badges import Badge
from files.classes.comment import Comment
from files.classes.notifications import Notification from files.classes.notifications import Notification
from files.classes.user import User
from files.helpers.sanitize import sanitize from files.helpers.sanitize import sanitize
# Note: while https://api.pushshift.io/meta provides the key # Note: while https://api.pushshift.io/meta provides the key

View File

@ -1,8 +1,9 @@
import re
from owoify.structures.word import Word
from owoify.utility.interleave_arrays import interleave_arrays from owoify.utility.interleave_arrays import interleave_arrays
from owoify.utility.presets import * from owoify.utility.presets import *
from owoify.structures.word import Word
import re
import files.helpers.regex as help_re import files.helpers.regex as help_re
import files.helpers.sanitize as sanitize import files.helpers.sanitize as sanitize

View File

@ -1,8 +1,9 @@
import random import random
import re import re
from typing import List, Literal, Optional, Union
from .const import *
from random import choice, choices from random import choice, choices
from typing import List, Optional, Union
from .const import *
valid_username_chars = 'a-zA-Z0-9_\-' valid_username_chars = 'a-zA-Z0-9_\-'
valid_username_regex = re.compile("^[a-zA-Z0-9_\-]{3,25}$", flags=re.A) valid_username_regex = re.compile("^[a-zA-Z0-9_\-]{3,25}$", flags=re.A)

View File

@ -1,11 +1,13 @@
import json import json
from random import randint
from enum import Enum from enum import Enum
from files.helpers.alerts import * from random import randint
from files.classes.casino_game import Casino_Game import time
from files.helpers.get import get_account
from flask import g from flask import g
from files.classes.casino_game import Casino_Game
from files.helpers.alerts import *
from files.helpers.get import get_account
class RouletteAction(str, Enum): class RouletteAction(str, Enum):
STRAIGHT_UP_BET = "STRAIGHT_UP_BET" STRAIGHT_UP_BET = "STRAIGHT_UP_BET"

View File

@ -1,31 +1,22 @@
import functools import functools
import random
import re
import signal
from functools import partial
from os import path
from urllib.parse import parse_qs, urlparse
import bleach import bleach
from bs4 import BeautifulSoup
from bleach.css_sanitizer import CSSSanitizer from bleach.css_sanitizer import CSSSanitizer
from bleach.linkifier import LinkifyFilter from bleach.linkifier import LinkifyFilter
from functools import partial from bs4 import BeautifulSoup
from .get import *
from os import path
import re
from mistletoe import markdown from mistletoe import markdown
from random import random, choice from files.classes.domains import BannedDomain
import signal
from files.__main__ import db_session
from files.classes.marsey import Marsey
db = db_session() from files.helpers.const import *
marseys_const = [x[0] for x in db.query(Marsey.name).filter(Marsey.submitter_id==None, Marsey.name!='chudsey').all()] from files.helpers.const_stateful import *
marseys_const2 = marseys_const + ['chudsey','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','exclamationpoint','period','questionmark'] from files.helpers.regex import *
from .get import *
marseys = db.query(Marsey).filter(Marsey.submitter_id==None).all()
marsey_mappings = {}
for marsey in marseys:
for tag in marsey.tags.split():
if tag in marsey_mappings:
marsey_mappings[tag].append(marsey.name)
else:
marsey_mappings[tag] = [marsey.name]
db.close()
TLDS = ( # Original gTLDs and ccTLDs TLDS = ( # Original gTLDs and ccTLDs
'ac','ad','ae','aero','af','ag','ai','al','am','an','ao','aq','ar','arpa','as','asia','at', 'ac','ad','ae','aero','af','ag','ai','al','am','an','ao','aq','ar','arpa','as','asia','at',
@ -173,12 +164,12 @@ def render_emoji(html, regexp, golden, marseys_used, b=False):
attrs = '' attrs = ''
if b: attrs += ' b' if b: attrs += ' b'
if golden and len(emojis) <= 20 and ('marsey' in emoji or emoji in marseys_const2): if golden and len(emojis) <= 20 and ('marsey' in emoji or emoji in marseys_const2):
if random() < 0.0025: attrs += ' g' if random.random() < 0.0025: attrs += ' g'
elif random() < 0.00125: attrs += ' glow' elif random.random() < 0.00125: attrs += ' glow'
old = emoji old = emoji
emoji = emoji.replace('!','').replace('#','') emoji = emoji.replace('!','').replace('#','')
if emoji == 'marseyrandom': emoji = choice(marseys_const2) if emoji == 'marseyrandom': emoji = random.choice(marseys_const2)
emoji_partial_pat = '<img loading="lazy" alt=":{0}:" src="{1}"{2}>' 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_partial = '<img loading="lazy" data-bs-toggle="tooltip" alt=":{0}:" title=":{0}:" src="{1}"{2}>'
@ -254,7 +245,7 @@ def sanitize(sanitized, golden=True, limit_pings=0, showmore=True, count_marseys
if torture: if torture:
sanitized = torture_ap(sanitized, g.v.username) sanitized = torture_ap(sanitized, g.v.username)
emoji = choice(['trumpjaktalking', 'reposthorse']) emoji = random.choice(['trumpjaktalking', 'reposthorse'])
sanitized += f'\n:#{emoji}:' sanitized += f'\n:#{emoji}:'
sanitized = normalize_url(sanitized) sanitized = normalize_url(sanitized)

View File

@ -1,11 +1,9 @@
from werkzeug.security import * from werkzeug.security import *
from .const import * from .const import *
def generate_hash(string): def generate_hash(string):
msg = bytes(string, "utf-16") msg = bytes(string, "utf-16")
return hmac.new(key=bytes(SECRET_KEY, "utf-16"), return hmac.new(key=bytes(SECRET_KEY, "utf-16"),
msg=msg, msg=msg,
digestmod='md5' digestmod='md5'
@ -13,11 +11,8 @@ def generate_hash(string):
def validate_hash(string, hashstr): def validate_hash(string, hashstr):
return hmac.compare_digest(hashstr, generate_hash(string)) return hmac.compare_digest(hashstr, generate_hash(string))
def hash_password(password): def hash_password(password):
return generate_password_hash( return generate_password_hash(
password, method='pbkdf2:sha512', salt_length=8) password, method='pbkdf2:sha512', salt_length=8)

View File

@ -0,0 +1,54 @@
import json
import os
import gevent
import gevent_inotifyx as inotify
from files.helpers.const import SETTINGS_FILENAME
_SETTINGS = {
"Bots": True,
"Fart mode": False,
"Read-only mode": False,
"Signups": True,
"login_required": False,
}
def get_setting(setting:str):
if not setting or not isinstance(setting, str): raise TypeError()
return _SETTINGS[setting]
def get_settings() -> dict[str, bool]:
return _SETTINGS
def toggle_setting(setting:str):
val = not _SETTINGS[setting]
_SETTINGS[setting] = val
_save_settings()
return val
def reload_settings():
global _SETTINGS
if not os.path.isfile(SETTINGS_FILENAME):
_save_settings()
with open(SETTINGS_FILENAME, 'r', encoding='utf_8') as f:
_SETTINGS = json.load(f)
def _save_settings():
with open(SETTINGS_FILENAME, "w", encoding='utf_8') as f:
json.dump(_SETTINGS, f)
def start_watching_settings():
gevent.spawn(_settings_watcher, SETTINGS_FILENAME)
def _settings_watcher(filename):
fd = inotify.init()
try:
inotify.add_watch(fd, filename, inotify.IN_CLOSE_WRITE)
while True:
for event in inotify.get_events(fd, 0):
reload_settings()
break
gevent.sleep(0.5)
finally:
os.close(fd)

View File

@ -1,12 +1,15 @@
import json import json
from json.encoder import INFINITY
import random import random
from .const import * from json.encoder import INFINITY
from flask import abort, g
from files.classes.casino_game import Casino_Game from files.classes.casino_game import Casino_Game
from files.helpers.casino import distribute_wager_badges
from flask import g, abort
from files.classes.comment import Comment from files.classes.comment import Comment
from files.classes.user import User from files.classes.user import User
from files.helpers.casino import distribute_wager_badges
from .const import *
minimum_bet = 5 minimum_bet = 5
maximum_bet = INFINITY maximum_bet = INFINITY

View File

@ -1,8 +1,10 @@
import time import time
from typing import Optional from typing import Optional
from files.helpers.const import *
from sqlalchemy.sql import func from sqlalchemy.sql import func
from files.helpers.const import *
def apply_time_filter(t, objects, cls): def apply_time_filter(t, objects, cls):
now = int(time.time()) now = int(time.time())
if t == 'hour': if t == 'hour':

View File

@ -1,5 +1,6 @@
from random import randint
from math import floor from math import floor
from random import randint
from files.helpers.const import * from files.helpers.const import *
from files.helpers.lottery import * from files.helpers.lottery import *

View File

@ -1,11 +1,12 @@
import json import json
from math import floor
import random import random
from enum import Enum from enum import Enum
from files.classes.casino_game import Casino_Game from math import floor
from files.helpers.casino import distribute_wager_badges
from flask import g from flask import g
from files.classes.casino_game import Casino_Game
from files.helpers.casino import distribute_wager_badges
class BlackjackStatus(str, Enum): class BlackjackStatus(str, Enum):
PLAYING = "PLAYING" PLAYING = "PLAYING"

View File

@ -1,4 +1,5 @@
from flask import g from flask import g
from files.classes.badges import Badge from files.classes.badges import Badge
from files.helpers.alerts import send_repeatable_notification from files.helpers.alerts import send_repeatable_notification

View File

@ -1,13 +1,29 @@
# import classes then... # import constants then...
from files.classes.sub import Sub from files.helpers.const import FEATURES
# import routes # import flask then...
from flask import g, request, render_template, make_response, redirect, jsonify, send_from_directory, send_file
# import our app then...
from files.__main__ import app
# import route helpers then...
from files.routes.routehelpers import *
# import wrappers then...
from files.routes.wrappers import *
# import jinja2 then... (lmao this was in feeds.py before wtf)
from files.routes.jinja2 import *
# import routes :)
from .admin import * from .admin import *
from .comments import * from .comments import *
from .errors import * from .errors import *
from .reporting import * from .reporting import *
from .front import * from .front import *
from .login import * from .login import *
from .mail import *
from .oauth import * from .oauth import *
from .posts import * from .posts import *
from .search import * from .search import *
@ -24,4 +40,7 @@ from .casino import *
from .polls import * from .polls import *
from .notifications import * from .notifications import *
from .hats import * from .hats import *
from .asset_submissions import * if FEATURES['ASSET_SUBMISSIONS']:
from .asset_submissions import *
if FEATURES['STREAMERS']:
from .streamers import *

View File

@ -1,26 +1,25 @@
import time import time
import re from urllib.parse import quote, urlencode
from os import remove
from PIL import Image as IMAGE
from files.helpers.wrappers import * from flask import *
from files.__main__ import app, cache, limiter
from files.classes import *
from files.helpers.actions import *
from files.helpers.alerts import * from files.helpers.alerts import *
from files.helpers.sanitize import * from files.helpers.cloudflare import *
from files.helpers.security import * from files.helpers.const import *
from files.helpers.get import * from files.helpers.get import *
from files.helpers.media import * from files.helpers.media import *
from files.helpers.const import * from files.helpers.sanitize import *
from files.helpers.actions import * from files.helpers.security import *
from files.helpers.settings import toggle_setting
from files.helpers.useractions import * from files.helpers.useractions import *
import files.helpers.cloudflare as cloudflare from files.routes.routehelpers import check_for_alts
from files.classes import * from files.routes.wrappers import *
from flask import *
from files.__main__ import app, cache, limiter
from .front import frontlist from .front import frontlist
from .login import check_for_alts
import datetime
import requests
from urllib.parse import quote, urlencode
@app.post('/kippy') @app.post('/kippy')
@admin_level_required(PERMS['PRINT_MARSEYBUX_FOR_KIPPY_ON_PCMEMES']) @admin_level_required(PERMS['PRINT_MARSEYBUX_FOR_KIPPY_ON_PCMEMES'])
@ -431,7 +430,7 @@ def admin_home(v):
under_attack = False under_attack = False
if v.admin_level >= PERMS['SITE_SETTINGS_UNDER_ATTACK']: if v.admin_level >= PERMS['SITE_SETTINGS_UNDER_ATTACK']:
under_attack = (cloudflare.get_security_level() or 'high') == 'under_attack' under_attack = (get_security_level() or 'high') == 'under_attack'
gitref = admin_git_head() gitref = admin_git_head()
@ -458,27 +457,20 @@ def admin_git_head():
@app.post("/admin/site_settings/<setting>") @app.post("/admin/site_settings/<setting>")
@admin_level_required(PERMS['SITE_SETTINGS']) @admin_level_required(PERMS['SITE_SETTINGS'])
def change_settings(v, setting): def change_settings(v, setting):
site_settings = app.config['SETTINGS'] val = toggle_setting(setting)
site_settings[setting] = not site_settings[setting] if val: word = 'enable'
with open("/site_settings.json", "w", encoding='utf_8') as f:
json.dump(site_settings, f)
if site_settings[setting]: word = 'enable'
else: word = 'disable' else: word = 'disable'
ma = ModAction( ma = ModAction(
kind=f"{word}_{setting}", kind=f"{word}_{setting}",
user_id=v.id, user_id=v.id,
) )
g.db.add(ma) g.db.add(ma)
return {'message': f"{setting} {word}d successfully!"} return {'message': f"{setting} {word}d successfully!"}
@app.post("/admin/clear_cloudflare_cache") @app.post("/admin/clear_cloudflare_cache")
@admin_level_required(PERMS['SITE_CACHE_PURGE_CDN']) @admin_level_required(PERMS['SITE_CACHE_PURGE_CDN'])
def clear_cloudflare_cache(v): def clear_cloudflare_cache(v):
if not cloudflare.clear_entire_cache(): if not clear_entire_cache():
abort(400, 'Failed to clear cloudflare cache!') abort(400, 'Failed to clear cloudflare cache!')
ma = ModAction( ma = ModAction(
kind="clear_cloudflare_cache", kind="clear_cloudflare_cache",
@ -503,13 +495,13 @@ def admin_clear_internal_cache(v):
@app.post("/admin/under_attack") @app.post("/admin/under_attack")
@admin_level_required(PERMS['SITE_SETTINGS_UNDER_ATTACK']) @admin_level_required(PERMS['SITE_SETTINGS_UNDER_ATTACK'])
def under_attack(v): def under_attack(v):
response = cloudflare.get_security_level() response = get_security_level()
if not response: if not response:
abort(400, 'Could not retrieve the current security level') abort(400, 'Could not retrieve the current security level')
old_under_attack_mode = response == 'under_attack' old_under_attack_mode = response == 'under_attack'
enable_disable_str = 'disable' if old_under_attack_mode else 'enable' enable_disable_str = 'disable' if old_under_attack_mode else 'enable'
new_security_level = 'high' if old_under_attack_mode else 'under_attack' new_security_level = 'high' if old_under_attack_mode else 'under_attack'
if not cloudflare.set_security_level(new_security_level): if not set_security_level(new_security_level):
abort(400, f'Failed to {enable_disable_str} under attack mode') abort(400, f'Failed to {enable_disable_str} under attack mode')
ma = ModAction( ma = ModAction(
kind=f"{enable_disable_str}_under_attack", kind=f"{enable_disable_str}_under_attack",
@ -760,7 +752,7 @@ def admin_add_alt(v, username):
a = Alt( a = Alt(
user1=user1.id, user1=user1.id,
user2=user2.id, user2=user2.id,
manual=True, is_manual=True,
deleted=deleted deleted=deleted
) )
g.db.add(a) g.db.add(a)
@ -1200,7 +1192,7 @@ def remove_post(post_id, v):
v.coins += 1 v.coins += 1
g.db.add(v) g.db.add(v)
cloudflare.purge_files_in_cache(f"https://{SITE}/") purge_files_in_cache(f"https://{SITE}/")
return {"message": "Post removed!"} return {"message": "Post removed!"}
@ -1208,7 +1200,6 @@ def remove_post(post_id, v):
@limiter.limit(DEFAULT_RATELIMIT_SLOWER) @limiter.limit(DEFAULT_RATELIMIT_SLOWER)
@admin_level_required(PERMS['POST_COMMENT_MODERATION']) @admin_level_required(PERMS['POST_COMMENT_MODERATION'])
def approve_post(post_id, v): def approve_post(post_id, v):
post = get_post(post_id) post = get_post(post_id)
if post.author.id == v.id and post.author.agendaposter and AGENDAPOSTER_PHRASE not in post.body.lower() and post.sub != 'chudrama': if post.author.id == v.id and post.author.agendaposter and AGENDAPOSTER_PHRASE not in post.body.lower() and post.sub != 'chudrama':

View File

@ -0,0 +1,57 @@
import secrets
from files.helpers.const import *
from files.helpers.settings import get_setting
from files.helpers.cloudflare import CLOUDFLARE_AVAILABLE
from files.routes.wrappers import *
from files.__main__ import app
@app.before_request
def before_request():
if SITE == 'marsey.world' and request.path != '/kofi':
abort(404)
g.agent = request.headers.get("User-Agent")
if not g.agent and request.path != '/kofi':
return 'Please use a "User-Agent" header!', 403
ua = g.agent or ''
ua = ua.lower()
if request.host != SITE:
return {"error": "Unauthorized host provided"}, 403
if request.headers.get("CF-Worker"): return {"error": "Cloudflare workers are not allowed to access this website."}, 403
if not get_setting('Bots') and request.headers.get("Authorization"): abort(403)
g.db = db_session()
g.webview = '; wv) ' in ua
g.inferior_browser = 'iphone' in ua or 'ipad' in ua or 'ipod' in ua or 'mac os' in ua or ' firefox/' in ua
g.is_tor = request.headers.get("cf-ipcountry") == "T1"
request.path = request.path.rstrip('/')
if not request.path: request.path = '/'
request.full_path = request.full_path.rstrip('?').rstrip('/')
if not request.full_path: request.full_path = '/'
if not session.get("session_id"):
session.permanent = True
session["session_id"] = secrets.token_hex(49)
@app.after_request
def after_request(response):
if response.status_code < 400:
if CLOUDFLARE_AVAILABLE and CLOUDFLARE_COOKIE_VALUE and getattr(g, 'desires_auth', False):
logged_in = bool(getattr(g, 'v', None))
response.set_cookie("lo", CLOUDFLARE_COOKIE_VALUE if logged_in else '', max_age=60*60*24*365 if logged_in else 1)
g.db.commit()
g.db.close()
del g.db
return response
@app.teardown_appcontext
def teardown_request(error):
if getattr(g, 'db', None):
g.db.rollback()
g.db.close()
del g.db
stdout.flush()

View File

@ -1,469 +1,468 @@
from shutil import move, copyfile from os import path, rename
from os import rename, path from shutil import copyfile, move
from typing import Union
from files.__main__ import app, limiter from files.classes.marsey import Marsey
from files.helpers.const import * from files.classes.hats import Hat, HatDef
from files.helpers.useractions import * from files.classes.mod_logs import ModAction
from files.helpers.media import *
from files.helpers.get import *
from files.helpers.wrappers import *
from files.helpers.cloudflare import purge_files_in_cache from files.helpers.cloudflare import purge_files_in_cache
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.media import *
from files.helpers.useractions import *
from files.routes.static import marsey_list from files.routes.static import marsey_list
from files.routes.wrappers import *
from files.__main__ import app, cache, limiter
if SITE not in ('pcmemes.net', 'watchpeopledie.tv'): ASSET_TYPES = (Marsey, HatDef)
ASSET_TYPES = (Marsey, HatDef) CAN_APPROVE_ASSETS = (AEVANN_ID, CARP_ID, SNAKES_ID)
CAN_APPROVE_ASSETS = (AEVANN_ID, CARP_ID, SNAKES_ID) CAN_UPDATE_ASSETS = (AEVANN_ID, CARP_ID, SNAKES_ID, GEESE_ID, JUSTCOOL_ID)
CAN_UPDATE_ASSETS = (AEVANN_ID, CARP_ID, SNAKES_ID, GEESE_ID, JUSTCOOL_ID)
@app.get('/asset_submissions/<path:path>') @app.get('/asset_submissions/<path:path>')
@limiter.exempt @limiter.exempt
def asset_submissions(path): def asset_submissions(path):
resp = make_response(send_from_directory('/asset_submissions', path)) resp = make_response(send_from_directory('/asset_submissions', path))
resp.headers.remove("Cache-Control") resp.headers.remove("Cache-Control")
resp.headers.add("Cache-Control", "public, max-age=3153600") resp.headers.add("Cache-Control", "public, max-age=3153600")
resp.headers.remove("Content-Type") resp.headers.remove("Content-Type")
resp.headers.add("Content-Type", "image/webp") resp.headers.add("Content-Type", "image/webp")
return resp return resp
@app.get("/submit/marseys") @app.get("/submit/marseys")
@auth_required @auth_required
def submit_marseys(v): def submit_marseys(v):
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_MARSEYS']: if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_MARSEYS']:
marseys = g.db.query(Marsey).filter(Marsey.submitter_id != None).all() marseys = g.db.query(Marsey).filter(Marsey.submitter_id != None).all()
else: else:
marseys = g.db.query(Marsey).filter(Marsey.submitter_id == v.id).all() marseys = g.db.query(Marsey).filter(Marsey.submitter_id == v.id).all()
for marsey in marseys: for marsey in marseys:
marsey.author = g.db.query(User.username).filter_by(id=marsey.author_id).one()[0] marsey.author = g.db.query(User.username).filter_by(id=marsey.author_id).one()[0]
marsey.submitter = g.db.query(User.username).filter_by(id=marsey.submitter_id).one()[0] marsey.submitter = g.db.query(User.username).filter_by(id=marsey.submitter_id).one()[0]
return render_template("submit_marseys.html", v=v, marseys=marseys) return render_template("submit_marseys.html", v=v, marseys=marseys)
@app.post("/submit/marseys") @app.post("/submit/marseys")
@auth_required @auth_required
def submit_marsey(v): def submit_marsey(v):
file = request.files["image"] file = request.files["image"]
name = request.values.get('name', '').lower().strip() name = request.values.get('name', '').lower().strip()
tags = request.values.get('tags', '').lower().strip() tags = request.values.get('tags', '').lower().strip()
username = request.values.get('author', '').lower().strip() username = request.values.get('author', '').lower().strip()
def error(error): def error(error):
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_MARSEYS']: marseys = g.db.query(Marsey).filter(Marsey.submitter_id != None).all()
else: marseys = g.db.query(Marsey).filter(Marsey.submitter_id == v.id).all()
for marsey in marseys:
marsey.author = g.db.query(User.username).filter_by(id=marsey.author_id).one()[0]
marsey.submitter = g.db.query(User.username).filter_by(id=marsey.submitter_id).one()[0]
return render_template("submit_marseys.html", v=v, marseys=marseys, error=error, name=name, tags=tags, username=username, file=file), 400
if request.headers.get("cf-ipcountry") == "T1":
return error("Image uploads are not allowed through TOR.")
if not file or not file.content_type.startswith('image/'):
return error("You need to submit an image!")
if not marsey_regex.fullmatch(name):
return error("Invalid name!")
existing = g.db.query(Marsey.name).filter_by(name=name).one_or_none()
if existing:
return error("A marsey with this name already exists!")
if not tags_regex.fullmatch(tags):
return error("Invalid tags!")
author = get_user(username, v=v, graceful=True, include_shadowbanned=False)
if not author:
return error(f"A user with the name '{username}' was not found!")
highquality = f'/asset_submissions/marseys/{name}'
file.save(highquality)
filename = f'/asset_submissions/marseys/{name}.webp'
copyfile(highquality, filename)
process_image(filename, resize=200, trim=True)
marsey = Marsey(name=name, author_id=author.id, tags=tags, count=0, submitter_id=v.id)
g.db.add(marsey)
g.db.flush()
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_MARSEYS']: marseys = g.db.query(Marsey).filter(Marsey.submitter_id != None).all() if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_MARSEYS']: marseys = g.db.query(Marsey).filter(Marsey.submitter_id != None).all()
else: marseys = g.db.query(Marsey).filter(Marsey.submitter_id == v.id).all() else: marseys = g.db.query(Marsey).filter(Marsey.submitter_id == v.id).all()
for marsey in marseys: for marsey in marseys:
marsey.author = g.db.query(User.username).filter_by(id=marsey.author_id).one()[0] marsey.author = g.db.query(User.username).filter_by(id=marsey.author_id).one()[0]
marsey.submitter = g.db.query(User.username).filter_by(id=marsey.submitter_id).one()[0] marsey.submitter = g.db.query(User.username).filter_by(id=marsey.submitter_id).one()[0]
return render_template("submit_marseys.html", v=v, marseys=marseys, error=error, name=name, tags=tags, username=username, file=file), 400
return render_template("submit_marseys.html", v=v, marseys=marseys, msg=f"'{name}' submitted successfully!") if g.is_tor:
return error("Image uploads are not allowed through TOR.")
def verify_permissions_and_get_asset(cls, asset_type:str, v:User, name:str, make_lower=False): if not file or not file.content_type.startswith('image/'):
if cls not in ASSET_TYPES: raise Exception("not a valid asset type") return error("You need to submit an image!")
if AEVANN_ID and v.id not in CAN_APPROVE_ASSETS:
abort(403, f"Only Carp can approve {asset_type}!")
name = name.strip()
if make_lower: name = name.lower()
asset = None
if cls == HatDef:
asset = g.db.query(cls).filter_by(name=name).one_or_none()
else:
asset = g.db.get(cls, name)
if not asset:
abort(404, f"This {asset} '{name}' doesn't exist!")
return asset
@app.post("/admin/approve/marsey/<name>") if not marsey_regex.fullmatch(name):
@admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_MARSEYS']) return error("Invalid name!")
def approve_marsey(v, name):
marsey = verify_permissions_and_get_asset(Marsey, "marsey", v, name, True)
tags = request.values.get('tags').lower().strip()
if not tags:
abort(400, "You need to include tags!")
new_name = request.values.get('name').lower().strip() existing = g.db.query(Marsey.name).filter_by(name=name).one_or_none()
if not new_name: if existing:
abort(400, "You need to include name!") return error("A marsey with this name already exists!")
if not tags_regex.fullmatch(tags):
return error("Invalid tags!")
author = get_user(username, v=v, graceful=True, include_shadowbanned=False)
if not author:
return error(f"A user with the name '{username}' was not found!")
highquality = f'/asset_submissions/marseys/{name}'
file.save(highquality)
filename = f'/asset_submissions/marseys/{name}.webp'
copyfile(highquality, filename)
process_image(filename, v, resize=200, trim=True)
marsey = Marsey(name=name, author_id=author.id, tags=tags, count=0, submitter_id=v.id)
g.db.add(marsey)
g.db.flush()
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_MARSEYS']: marseys = g.db.query(Marsey).filter(Marsey.submitter_id != None).all()
else: marseys = g.db.query(Marsey).filter(Marsey.submitter_id == v.id).all()
for marsey in marseys:
marsey.author = g.db.query(User.username).filter_by(id=marsey.author_id).one()[0]
marsey.submitter = g.db.query(User.username).filter_by(id=marsey.submitter_id).one()[0]
return render_template("submit_marseys.html", v=v, marseys=marseys, msg=f"'{name}' submitted successfully!")
def verify_permissions_and_get_asset(cls, asset_type:str, v:User, name:str, make_lower=False):
if cls not in ASSET_TYPES: raise Exception("not a valid asset type")
if AEVANN_ID and v.id not in CAN_APPROVE_ASSETS:
abort(403, f"Only Carp can approve {asset_type}!")
name = name.strip()
if make_lower: name = name.lower()
asset = None
if cls == HatDef:
asset = g.db.query(cls).filter_by(name=name).one_or_none()
else:
asset = g.db.get(cls, name)
if not asset:
abort(404, f"This {asset} '{name}' doesn't exist!")
return asset
@app.post("/admin/approve/marsey/<name>")
@admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_MARSEYS'])
def approve_marsey(v, name):
marsey = verify_permissions_and_get_asset(Marsey, "marsey", v, name, True)
tags = request.values.get('tags').lower().strip()
if not tags:
abort(400, "You need to include tags!")
new_name = request.values.get('name').lower().strip()
if not new_name:
abort(400, "You need to include name!")
if not marsey_regex.fullmatch(new_name): if not marsey_regex.fullmatch(new_name):
abort(400, "Invalid name!") abort(400, "Invalid name!")
if not tags_regex.fullmatch(tags): if not tags_regex.fullmatch(tags):
abort(400, "Invalid tags!") abort(400, "Invalid tags!")
marsey.name = new_name marsey.name = new_name
marsey.tags = tags marsey.tags = tags
g.db.add(marsey) g.db.add(marsey)
author = get_account(marsey.author_id) author = get_account(marsey.author_id)
all_by_author = g.db.query(Marsey).filter_by(author_id=author.id).count() all_by_author = g.db.query(Marsey).filter_by(author_id=author.id).count()
if all_by_author >= 99: if all_by_author >= 99:
badge_grant(badge_id=143, user=author) badge_grant(badge_id=143, user=author)
elif all_by_author >= 9: elif all_by_author >= 9:
badge_grant(badge_id=16, user=author) badge_grant(badge_id=16, user=author)
else: else:
badge_grant(badge_id=17, user=author) badge_grant(badge_id=17, user=author)
purge_files_in_cache(f"https://{SITE}/e/{marsey.name}/webp") purge_files_in_cache(f"https://{SITE}/e/{marsey.name}/webp")
cache.delete_memoized(marsey_list) cache.delete_memoized(marsey_list)
move(f"/asset_submissions/marseys/{name}.webp", f"files/assets/images/emojis/{marsey.name}.webp")
highquality = f"/asset_submissions/marseys/{name}"
with Image.open(highquality) as i:
new_path = f'/asset_submissions/marseys/original/{name}.{i.format.lower()}'
rename(highquality, new_path)
author.coins += 250
g.db.add(author)
if v.id != author.id:
msg = f"@{v.username} (Admin) has approved a marsey you made: :{marsey.name}:\nYou have received 250 coins as a reward!"
send_repeatable_notification(author.id, msg)
if v.id != marsey.submitter_id and author.id != marsey.submitter_id:
msg = f"@{v.username} (Admin) has approved a marsey you submitted: :{marsey.name}:"
send_repeatable_notification(marsey.submitter_id, msg)
marsey.submitter_id = None
return {"message": f"'{marsey.name}' approved!"}
def remove_asset(cls, type_name:str, v:User, name:str) -> dict[str, str]:
if cls not in ASSET_TYPES: raise Exception("not a valid asset type")
should_make_lower = cls == Marsey
if should_make_lower: name = name.lower()
name = name.strip()
if not name:
abort(400, f"You need to specify a {type_name}!")
asset = None
if cls == HatDef:
asset = g.db.query(cls).filter_by(name=name).one_or_none()
else:
asset = g.db.get(cls, name)
if not asset:
abort(404, f"This {type_name} '{name}' doesn't exist!")
if v.id != asset.submitter_id and v.id not in CAN_APPROVE_ASSETS:
abort(403, f"Only Carp can remove {type_name}s!")
name = asset.name
if v.id != asset.submitter_id:
msg = f"@{v.username} has rejected a {type_name} you submitted: `'{name}'`"
send_repeatable_notification(asset.submitter_id, msg)
g.db.delete(asset)
os.remove(f"/asset_submissions/{type_name}s/{name}.webp")
os.remove(f"/asset_submissions/{type_name}s/{name}")
return {"message": f"'{name}' removed!"}
@app.post("/remove/marsey/<name>")
@auth_required
def remove_marsey(v, name):
return remove_asset(Marsey, "marsey", v, name)
@app.get("/submit/hats")
@auth_required
def submit_hats(v):
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_HATS']: hats = g.db.query(HatDef).filter(HatDef.submitter_id != None).all()
else: hats = g.db.query(HatDef).filter(HatDef.submitter_id == v.id).all()
return render_template("submit_hats.html", v=v, hats=hats)
move(f"/asset_submissions/marseys/{name}.webp", f"files/assets/images/emojis/{marsey.name}.webp") @app.post("/submit/hats")
@auth_required
def submit_hat(v):
name = request.values.get('name', '').strip()
description = request.values.get('description', '').strip()
username = request.values.get('author', '').strip()
highquality = f"/asset_submissions/marseys/{name}" def error(error):
with Image.open(highquality) as i:
new_path = f'/asset_submissions/marseys/original/{name}.{i.format.lower()}'
rename(highquality, new_path)
author.coins += 250
g.db.add(author)
if v.id != author.id:
msg = f"@{v.username} (Admin) has approved a marsey you made: :{marsey.name}:\nYou have received 250 coins as a reward!"
send_repeatable_notification(author.id, msg)
if v.id != marsey.submitter_id and author.id != marsey.submitter_id:
msg = f"@{v.username} (Admin) has approved a marsey you submitted: :{marsey.name}:"
send_repeatable_notification(marsey.submitter_id, msg)
marsey.submitter_id = None
return {"message": f"'{marsey.name}' approved!"}
def remove_asset(cls, type_name:str, v:User, name:str) -> dict[str, str]:
if cls not in ASSET_TYPES: raise Exception("not a valid asset type")
should_make_lower = cls == Marsey
if should_make_lower: name = name.lower()
name = name.strip()
if not name:
abort(400, f"You need to specify a {type_name}!")
asset = None
if cls == HatDef:
asset = g.db.query(cls).filter_by(name=name).one_or_none()
else:
asset = g.db.get(cls, name)
if not asset:
abort(404, f"This {type_name} '{name}' doesn't exist!")
if v.id != asset.submitter_id and v.id not in CAN_APPROVE_ASSETS:
abort(403, f"Only Carp can remove {type_name}s!")
name = asset.name
if v.id != asset.submitter_id:
msg = f"@{v.username} has rejected a {type_name} you submitted: `'{name}'`"
send_repeatable_notification(asset.submitter_id, msg)
g.db.delete(asset)
os.remove(f"/asset_submissions/{type_name}s/{name}.webp")
os.remove(f"/asset_submissions/{type_name}s/{name}")
return {"message": f"'{name}' removed!"}
@app.post("/remove/marsey/<name>")
@auth_required
def remove_marsey(v, name):
return remove_asset(Marsey, "marsey", v, name)
@app.get("/submit/hats")
@auth_required
def submit_hats(v):
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_HATS']: hats = g.db.query(HatDef).filter(HatDef.submitter_id != None).all() if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_HATS']: hats = g.db.query(HatDef).filter(HatDef.submitter_id != None).all()
else: hats = g.db.query(HatDef).filter(HatDef.submitter_id == v.id).all() else: hats = g.db.query(HatDef).filter(HatDef.submitter_id == v.id).all()
return render_template("submit_hats.html", v=v, hats=hats) return render_template("submit_hats.html", v=v, hats=hats, error=error, name=name, description=description, username=username), 400
if g.is_tor:
return error("Image uploads are not allowed through TOR.")
file = request.files["image"]
if not file or not file.content_type.startswith('image/'):
return error("You need to submit an image!")
if not hat_regex.fullmatch(name):
return error("Invalid name!")
existing = g.db.query(HatDef.name).filter_by(name=name).one_or_none()
if existing:
return error("A hat with this name already exists!")
if not description_regex.fullmatch(description):
return error("Invalid description!")
author = get_user(username, v=v, graceful=True, include_shadowbanned=False)
if not author:
return error(f"A user with the name '{username}' was not found!")
highquality = f'/asset_submissions/hats/{name}'
file.save(highquality)
with Image.open(highquality) as i:
if i.width > 100 or i.height > 130:
os.remove(highquality)
return error("Images must be 100x130")
if len(list(Iterator(i))) > 1: price = 1000
else: price = 500
filename = f'/asset_submissions/hats/{name}.webp'
copyfile(highquality, filename)
process_image(filename, v, resize=100)
hat = HatDef(name=name, author_id=author.id, description=description, price=price, submitter_id=v.id)
g.db.add(hat)
g.db.commit()
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_HATS']: hats = g.db.query(HatDef).filter(HatDef.submitter_id != None).all()
else: hats = g.db.query(HatDef).filter(HatDef.submitter_id == v.id).all()
return render_template("submit_hats.html", v=v, hats=hats, msg=f"'{name}' submitted successfully!")
@app.post("/submit/hats") @app.post("/admin/approve/hat/<name>")
@auth_required @admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_HATS'])
def submit_hat(v): def approve_hat(v, name):
name = request.values.get('name', '').strip() hat = verify_permissions_and_get_asset(HatDef, "hat", v, name, False)
description = request.values.get('description', '').strip() description = request.values.get('description').strip()
username = request.values.get('author', '').strip() if not description: abort(400, "You need to include a description!")
def error(error): new_name = request.values.get('name').strip()
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_HATS']: hats = g.db.query(HatDef).filter(HatDef.submitter_id != None).all() if not new_name: abort(400, "You need to include a name!")
else: hats = g.db.query(HatDef).filter(HatDef.submitter_id == v.id).all() if not hat_regex.fullmatch(new_name): abort(400, "Invalid name!")
return render_template("submit_hats.html", v=v, hats=hats, error=error, name=name, description=description, username=username), 400 if not description_regex.fullmatch(description): abort(400, "Invalid description!")
if request.headers.get("cf-ipcountry") == "T1": try:
return error("Image uploads are not allowed through TOR.") hat.price = int(request.values.get('price'))
if hat.price < 0: raise ValueError("Invalid hat price")
file = request.files["image"] except:
if not file or not file.content_type.startswith('image/'): abort(400, "Invalid hat price")
return error("You need to submit an image!") hat.name = new_name
hat.description = description
if not hat_regex.fullmatch(name): g.db.add(hat)
return error("Invalid name!")
existing = g.db.query(HatDef.name).filter_by(name=name).one_or_none()
if existing:
return error("A hat with this name already exists!")
if not description_regex.fullmatch(description):
return error("Invalid description!")
author = get_user(username, v=v, graceful=True, include_shadowbanned=False)
if not author:
return error(f"A user with the name '{username}' was not found!")
highquality = f'/asset_submissions/hats/{name}'
file.save(highquality)
with Image.open(highquality) as i:
if i.width > 100 or i.height > 130:
os.remove(highquality)
return error("Images must be 100x130")
if len(list(Iterator(i))) > 1: price = 1000
else: price = 500
filename = f'/asset_submissions/hats/{name}.webp'
copyfile(highquality, filename)
process_image(filename, resize=100)
hat = HatDef(name=name, author_id=author.id, description=description, price=price, submitter_id=v.id)
g.db.add(hat)
g.db.commit()
if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_HATS']: hats = g.db.query(HatDef).filter(HatDef.submitter_id != None).all()
else: hats = g.db.query(HatDef).filter(HatDef.submitter_id == v.id).all()
return render_template("submit_hats.html", v=v, hats=hats, msg=f"'{name}' submitted successfully!")
@app.post("/admin/approve/hat/<name>") g.db.flush()
@admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_HATS']) author = hat.author
def approve_hat(v, name):
hat = verify_permissions_and_get_asset(HatDef, "hat", v, name, False)
description = request.values.get('description').strip()
if not description: abort(400, "You need to include a description!")
new_name = request.values.get('name').strip() all_by_author = g.db.query(HatDef).filter_by(author_id=author.id).count()
if not new_name: abort(400, "You need to include a name!")
if not hat_regex.fullmatch(new_name): abort(400, "Invalid name!")
if not description_regex.fullmatch(description): abort(400, "Invalid description!")
try: if all_by_author >= 250:
hat.price = int(request.values.get('price')) badge_grant(badge_id=166, user=author)
if hat.price < 0: raise ValueError("Invalid hat price") elif all_by_author >= 100:
except: badge_grant(badge_id=165, user=author)
abort(400, "Invalid hat price") elif all_by_author >= 50:
hat.name = new_name badge_grant(badge_id=164, user=author)
hat.description = description elif all_by_author >= 10:
g.db.add(hat) badge_grant(badge_id=163, user=author)
hat_copy = Hat(
user_id=author.id,
hat_id=hat.id
)
g.db.add(hat_copy)
g.db.flush() if v.id != author.id:
author = hat.author msg = f"@{v.username} (Admin) has approved a hat you made: '{hat.name}'"
send_repeatable_notification(author.id, msg)
all_by_author = g.db.query(HatDef).filter_by(author_id=author.id).count() if v.id != hat.submitter_id and author.id != hat.submitter_id:
msg = f"@{v.username} (Admin) has approved a hat you submitted: '{hat.name}'"
send_repeatable_notification(hat.submitter_id, msg)
if all_by_author >= 250: hat.submitter_id = None
badge_grant(badge_id=166, user=author)
elif all_by_author >= 100:
badge_grant(badge_id=165, user=author)
elif all_by_author >= 50:
badge_grant(badge_id=164, user=author)
elif all_by_author >= 10:
badge_grant(badge_id=163, user=author)
hat_copy = Hat( move(f"/asset_submissions/hats/{name}.webp", f"files/assets/images/hats/{hat.name}.webp")
user_id=author.id,
hat_id=hat.id highquality = f"/asset_submissions/hats/{name}"
) with Image.open(highquality) as i:
g.db.add(hat_copy) new_path = f'/asset_submissions/hats/original/{name}.{i.format.lower()}'
rename(highquality, new_path)
return {"message": f"'{hat.name}' approved!"}
@app.post("/remove/hat/<name>")
@auth_required
def remove_hat(v, name):
return remove_asset(HatDef, 'hat', v, name)
@app.get("/admin/update/marseys")
@admin_level_required(PERMS['UPDATE_MARSEYS'])
def update_marseys(v):
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
name = request.values.get('name')
tags = None
error = None
if name:
marsey = g.db.get(Marsey, name)
if marsey:
tags = marsey.tags or ''
else:
name = ''
tags = ''
error = "A marsey with this name doesn't exist!"
return render_template("update_assets.html", v=v, error=error, name=name, tags=tags, type="Marsey")
if v.id != author.id: @app.post("/admin/update/marseys")
msg = f"@{v.username} (Admin) has approved a hat you made: '{hat.name}'" @admin_level_required(PERMS['UPDATE_MARSEYS'])
send_repeatable_notification(author.id, msg) def update_marsey(v):
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
if v.id != hat.submitter_id and author.id != hat.submitter_id: file = request.files["image"]
msg = f"@{v.username} (Admin) has approved a hat you submitted: '{hat.name}'" name = request.values.get('name', '').lower().strip()
send_repeatable_notification(hat.submitter_id, msg) tags = request.values.get('tags', '').lower().strip()
hat.submitter_id = None def error(error):
move(f"/asset_submissions/hats/{name}.webp", f"files/assets/images/hats/{hat.name}.webp")
highquality = f"/asset_submissions/hats/{name}"
with Image.open(highquality) as i:
new_path = f'/asset_submissions/hats/original/{name}.{i.format.lower()}'
rename(highquality, new_path)
return {"message": f"'{hat.name}' approved!"}
@app.post("/remove/hat/<name>")
@auth_required
def remove_hat(v, name):
return remove_asset(HatDef, 'hat', v, name)
@app.get("/admin/update/marseys")
@admin_level_required(PERMS['UPDATE_MARSEYS'])
def update_marseys(v):
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
name = request.values.get('name')
tags = None
error = None
if name:
marsey = g.db.get(Marsey, name)
if marsey:
tags = marsey.tags or ''
else:
name = ''
tags = ''
error = "A marsey with this name doesn't exist!"
return render_template("update_assets.html", v=v, error=error, name=name, tags=tags, type="Marsey") return render_template("update_assets.html", v=v, error=error, name=name, tags=tags, type="Marsey")
if not marsey_regex.fullmatch(name):
return error("Invalid name!")
@app.post("/admin/update/marseys") existing = g.db.get(Marsey, name)
@admin_level_required(PERMS['UPDATE_MARSEYS']) if not existing:
def update_marsey(v): return error("A marsey with this name doesn't exist!")
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
file = request.files["image"] if file:
name = request.values.get('name', '').lower().strip() if g.is_tor:
tags = request.values.get('tags', '').lower().strip()
def error(error):
return render_template("update_assets.html", v=v, error=error, name=name, tags=tags, type="Marsey")
if not marsey_regex.fullmatch(name):
return error("Invalid name!")
existing = g.db.get(Marsey, name)
if not existing:
return error("A marsey with this name doesn't exist!")
if file:
if request.headers.get("cf-ipcountry") == "T1":
return error("Image uploads are not allowed through TOR.")
if not file.content_type.startswith('image/'):
return error("You need to submit an image!")
for x in IMAGE_FORMATS:
if path.isfile(f'/asset_submissions/marseys/original/{name}.{x}'):
os.remove(f'/asset_submissions/marseys/original/{name}.{x}')
highquality = f"/asset_submissions/marseys/{name}"
file.save(highquality)
with Image.open(highquality) as i:
format = i.format.lower()
new_path = f'/asset_submissions/marseys/original/{name}.{format}'
rename(highquality, new_path)
filename = f"files/assets/images/emojis/{name}.webp"
copyfile(new_path, filename)
process_image(filename, resize=200, trim=True)
purge_files_in_cache([f"https://{SITE}/e/{name}.webp", f"https://{SITE}/assets/images/emojis/{name}.webp", f"https://{SITE}/asset_submissions/marseys/original/{name}.{format}"])
if tags and existing.tags != tags and tags != "none":
existing.tags = tags
g.db.add(existing)
elif not file:
return error("You need to update this marsey!")
ma = ModAction(
kind="update_marsey",
user_id=v.id,
_note=f'<a href="/e/{name}.webp">{name}</a>'
)
g.db.add(ma)
return render_template("update_assets.html", v=v, msg=f"'{name}' updated successfully!", name=name, tags=tags, type="Marsey")
@app.get("/admin/update/hats")
@admin_level_required(PERMS['UPDATE_HATS'])
def update_hats(v):
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
return render_template("update_assets.html", v=v, type="Hat")
@app.post("/admin/update/hats")
@admin_level_required(PERMS['UPDATE_HATS'])
def update_hat(v):
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
file = request.files["image"]
name = request.values.get('name', '').strip()
def error(error):
return render_template("update_assets.html", v=v, error=error, type="Hat")
if request.headers.get("cf-ipcountry") == "T1":
return error("Image uploads are not allowed through TOR.") return error("Image uploads are not allowed through TOR.")
if not file.content_type.startswith('image/'):
if not file or not file.content_type.startswith('image/'):
return error("You need to submit an image!") return error("You need to submit an image!")
if not hat_regex.fullmatch(name):
return error("Invalid name!")
existing = g.db.query(HatDef.name).filter_by(name=name).one_or_none()
if not existing:
return error("A hat with this name doesn't exist!")
highquality = f"/asset_submissions/hats/{name}"
file.save(highquality)
with Image.open(highquality) as i:
if i.width > 100 or i.height > 130:
os.remove(highquality)
return error("Images must be 100x130")
format = i.format.lower()
new_path = f'/asset_submissions/hats/original/{name}.{format}'
for x in IMAGE_FORMATS: for x in IMAGE_FORMATS:
if path.isfile(f'/asset_submissions/hats/original/{name}.{x}'): if path.isfile(f'/asset_submissions/marseys/original/{name}.{x}'):
os.remove(f'/asset_submissions/hats/original/{name}.{x}') os.remove(f'/asset_submissions/marseys/original/{name}.{x}')
highquality = f"/asset_submissions/marseys/{name}"
file.save(highquality)
with Image.open(highquality) as i:
format = i.format.lower()
new_path = f'/asset_submissions/marseys/original/{name}.{format}'
rename(highquality, new_path) rename(highquality, new_path)
filename = f"files/assets/images/hats/{name}.webp" filename = f"files/assets/images/emojis/{name}.webp"
copyfile(new_path, filename) copyfile(new_path, filename)
process_image(filename, resize=100) process_image(filename, v, resize=200, trim=True)
purge_files_in_cache([f"https://{SITE}/i/hats/{name}.webp", f"https://{SITE}/assets/images/hats/{name}.webp", f"https://{SITE}/asset_submissions/hats/original/{name}.{format}"]) purge_files_in_cache([f"https://{SITE}/e/{name}.webp", f"https://{SITE}/assets/images/emojis/{name}.webp", f"https://{SITE}/asset_submissions/marseys/original/{name}.{format}"])
ma = ModAction(
kind="update_hat", if tags and existing.tags != tags and tags != "none":
user_id=v.id, existing.tags = tags
_note=f'<a href="/i/hats/{name}.webp">{name}</a>' g.db.add(existing)
) elif not file:
g.db.add(ma) return error("You need to update this marsey!")
return render_template("update_assets.html", v=v, msg=f"'{name}' updated successfully!", type="Hat")
ma = ModAction(
kind="update_marsey",
user_id=v.id,
_note=f'<a href="/e/{name}.webp">{name}</a>'
)
g.db.add(ma)
return render_template("update_assets.html", v=v, msg=f"'{name}' updated successfully!", name=name, tags=tags, type="Marsey")
@app.get("/admin/update/hats")
@admin_level_required(PERMS['UPDATE_HATS'])
def update_hats(v):
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
return render_template("update_assets.html", v=v, type="Hat")
@app.post("/admin/update/hats")
@admin_level_required(PERMS['UPDATE_HATS'])
def update_hat(v):
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
file = request.files["image"]
name = request.values.get('name', '').strip()
def error(error):
return render_template("update_assets.html", v=v, error=error, type="Hat")
if g.is_tor:
return error("Image uploads are not allowed through TOR.")
if not file or not file.content_type.startswith('image/'):
return error("You need to submit an image!")
if not hat_regex.fullmatch(name):
return error("Invalid name!")
existing = g.db.query(HatDef.name).filter_by(name=name).one_or_none()
if not existing:
return error("A hat with this name doesn't exist!")
highquality = f"/asset_submissions/hats/{name}"
file.save(highquality)
with Image.open(highquality) as i:
if i.width > 100 or i.height > 130:
os.remove(highquality)
return error("Images must be 100x130")
format = i.format.lower()
new_path = f'/asset_submissions/hats/original/{name}.{format}'
for x in IMAGE_FORMATS:
if path.isfile(f'/asset_submissions/hats/original/{name}.{x}'):
os.remove(f'/asset_submissions/hats/original/{name}.{x}')
rename(highquality, new_path)
filename = f"files/assets/images/hats/{name}.webp"
copyfile(new_path, filename)
process_image(filename, v, resize=100)
purge_files_in_cache([f"https://{SITE}/i/hats/{name}.webp", f"https://{SITE}/assets/images/hats/{name}.webp", f"https://{SITE}/asset_submissions/hats/original/{name}.{format}"])
ma = ModAction(
kind="update_hat",
user_id=v.id,
_note=f'<a href="/i/hats/{name}.webp">{name}</a>'
)
g.db.add(ma)
return render_template("update_assets.html", v=v, msg=f"'{name}' updated successfully!", type="Hat")

View File

@ -1,18 +1,23 @@
from files.__main__ import app, limiter from copy import deepcopy
from files.helpers.wrappers import *
from files.helpers.alerts import *
from files.helpers.get import *
from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.actions import *
from files.helpers.useractions import *
from files.classes.award import *
from .front import frontlist
from flask import g, request from flask import g, request
from files.helpers.sanitize import filter_emojis_only from sqlalchemy import func
from files.classes.award import AwardRelationship
from files.classes.userblock import UserBlock
from files.helpers.actions import *
from files.helpers.alerts import *
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.marsify import marsify from files.helpers.marsify import marsify
from files.helpers.owoify import owoify from files.helpers.owoify import owoify
from copy import deepcopy from files.helpers.regex import *
from files.helpers.sanitize import filter_emojis_only
from files.helpers.useractions import *
from files.routes.wrappers import *
from files.__main__ import app, cache, limiter
from .front import frontlist
@app.get("/shop") @app.get("/shop")
@app.get("/settings/shop") @app.get("/settings/shop")

View File

@ -1,15 +1,15 @@
from files.__main__ import app from files.classes.casino_game import CASINO_GAME_KINDS
from files.helpers.wrappers import *
from files.helpers.alerts import * from files.helpers.alerts import *
from files.helpers.get import *
from files.helpers.const import *
from files.helpers.wrappers import *
from files.helpers.casino import * from files.helpers.casino import *
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.lottery import *
from files.helpers.roulette import *
from files.helpers.slots import * from files.helpers.slots import *
from files.helpers.twentyone import * from files.helpers.twentyone import *
from files.helpers.roulette import * from files.routes.wrappers import *
from files.helpers.lottery import *
from files.__main__ import app, limiter
@app.get("/casino") @app.get("/casino")
@feature_required('GAMBLING') @feature_required('GAMBLING')
@ -32,8 +32,8 @@ def casino_game_page(v, game):
elif game not in CASINO_GAME_KINDS: elif game not in CASINO_GAME_KINDS:
abort(404) abort(404)
feed = json.dumps(get_game_feed(game)) feed = json.dumps(get_game_feed(game, g.db))
leaderboard = json.dumps(get_game_leaderboard(game)) leaderboard = json.dumps(get_game_leaderboard(game, g.db))
game_state = '' game_state = ''
if game == 'blackjack': if game == 'blackjack':
@ -60,7 +60,7 @@ def casino_game_feed(v, game):
elif game not in CASINO_GAME_KINDS: elif game not in CASINO_GAME_KINDS:
abort(404) abort(404)
feed = get_game_feed(game) feed = get_game_feed(game, g.db)
return {"feed": feed} return {"feed": feed}
@ -121,7 +121,7 @@ def blackjack_deal_to_player(v):
currency = request.values.get("currency") currency = request.values.get("currency")
create_new_game(v, wager, currency) create_new_game(v, wager, currency)
state = dispatch_action(v, BlackjackAction.DEAL) state = dispatch_action(v, BlackjackAction.DEAL)
feed = get_game_feed('blackjack') feed = get_game_feed('blackjack', g.db)
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except Exception as e: except Exception as e:
@ -138,7 +138,7 @@ def blackjack_player_hit(v):
try: try:
state = dispatch_action(v, BlackjackAction.HIT) state = dispatch_action(v, BlackjackAction.HIT)
feed = get_game_feed('blackjack') feed = get_game_feed('blackjack', g.db)
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except: except:
abort(400, "Unable to hit.") abort(400, "Unable to hit.")
@ -154,7 +154,7 @@ def blackjack_player_stay(v):
try: try:
state = dispatch_action(v, BlackjackAction.STAY) state = dispatch_action(v, BlackjackAction.STAY)
feed = get_game_feed('blackjack') feed = get_game_feed('blackjack', g.db)
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except: except:
abort(400, "Unable to stay.") abort(400, "Unable to stay.")
@ -170,7 +170,7 @@ def blackjack_player_doubled_down(v):
try: try:
state = dispatch_action(v, BlackjackAction.DOUBLE_DOWN) state = dispatch_action(v, BlackjackAction.DOUBLE_DOWN)
feed = get_game_feed('blackjack') feed = get_game_feed('blackjack', g.db)
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except: except:
abort(400, "Unable to double down.") abort(400, "Unable to double down.")
@ -186,7 +186,7 @@ def blackjack_player_bought_insurance(v):
try: try:
state = dispatch_action(v, BlackjackAction.BUY_INSURANCE) state = dispatch_action(v, BlackjackAction.BUY_INSURANCE)
feed = get_game_feed('blackjack') feed = get_game_feed('blackjack', g.db)
return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}} return {"success": True, "state": state, "feed": feed, "gambler": {"coins": v.coins, "procoins": v.procoins}}
except: except:
abort(403, "Unable to buy insurance.") abort(403, "Unable to buy insurance.")

View File

@ -1,17 +1,17 @@
import atexit
import time import time
import uuid import uuid
from files.helpers.jinja2 import timestamp
from files.helpers.wrappers import *
from files.helpers.sanitize import sanitize
from files.helpers.const import *
from files.helpers.alerts import *
from files.helpers.regex import *
from files.helpers.actions import *
from flask_socketio import SocketIO, emit from flask_socketio import SocketIO, emit
from files.__main__ import app, limiter, cache
from flask import render_template from files.helpers.actions import *
import sys from files.helpers.alerts import *
import atexit from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.sanitize import sanitize
from files.routes.wrappers import *
from files.__main__ import app, cache, limiter
if SITE == 'localhost': if SITE == 'localhost':
socketio = SocketIO( socketio = SocketIO(

View File

@ -1,26 +1,27 @@
from files.helpers.wrappers import * import os
from collections import Counter
from json import loads
from shutil import copyfile
import gevent
from files.classes import *
from files.helpers.actions import *
from files.helpers.alerts import * from files.helpers.alerts import *
from files.helpers.media import * from files.helpers.cloudflare import purge_files_in_cache
from files.helpers.const import * from files.helpers.const import *
from files.helpers.get import *
from files.helpers.marsify import marsify
from files.helpers.media import *
from files.helpers.owoify import owoify
from files.helpers.regex import * from files.helpers.regex import *
from files.helpers.sanitize import filter_emojis_only
from files.helpers.slots import * from files.helpers.slots import *
from files.helpers.treasure import * from files.helpers.treasure import *
from files.helpers.actions import *
from files.helpers.get import *
from files.classes import *
from files.routes.front import comment_idlist from files.routes.front import comment_idlist
from flask import * from files.routes.routehelpers import execute_shadowban_viewers_and_voters
from files.__main__ import app, limiter from files.routes.wrappers import *
from files.helpers.sanitize import filter_emojis_only from files.__main__ import app, cache, limiter
from files.helpers.marsify import marsify
from files.helpers.owoify import owoify
from files.helpers.cloudflare import purge_files_in_cache
import requests
from shutil import copyfile
from json import loads
from collections import Counter
import gevent
import os
WORDLE_COLOR_MAPPINGS = {-1: "🟥", 0: "🟨", 1: "🟩"} WORDLE_COLOR_MAPPINGS = {-1: "🟥", 0: "🟨", 1: "🟩"}
@ -73,6 +74,9 @@ def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None):
# props won't save properly unless you put them in a list # props won't save properly unless you put them in a list
output = get_comments_v_properties(v, False, None, Comment.top_comment_id == c.top_comment_id)[1] output = get_comments_v_properties(v, False, None, Comment.top_comment_id == c.top_comment_id)[1]
post.replies=[top_comment] post.replies=[top_comment]
execute_shadowban_viewers_and_voters(v, post)
execute_shadowban_viewers_and_voters(v, comment)
if v and v.client: return top_comment.json if v and v.client: return top_comment.json
else: else:
@ -142,13 +146,13 @@ def comment(v):
choices.append(i.group(1)) choices.append(i.group(1))
body = body.replace(i.group(0), "") body = body.replace(i.group(0), "")
if request.files.get("file") and request.headers.get("cf-ipcountry") != "T1": if request.files.get("file") and not g.is_tor:
files = request.files.getlist('file')[:4] files = request.files.getlist('file')[:4]
for file in files: for file in files:
if file.content_type.startswith('image/'): if file.content_type.startswith('image/'):
oldname = f'/images/{time.time()}'.replace('.','') + '.webp' oldname = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(oldname) file.save(oldname)
image = process_image(oldname, patron=v.patron) image = process_image(oldname, v)
if image == "": abort(400, "Image upload failed") if image == "": abort(400, "Image upload failed")
if v.admin_level >= PERMS['SITE_SETTINGS_SIDEBARS_BANNERS_BADGES'] and level == 1: if v.admin_level >= PERMS['SITE_SETTINGS_SIDEBARS_BANNERS_BADGES'] and level == 1:
def process_sidebar_or_banner(type, resize=0): def process_sidebar_or_banner(type, resize=0):
@ -157,7 +161,7 @@ def comment(v):
num = int(li.split('.webp')[0]) + 1 num = int(li.split('.webp')[0]) + 1
filename = f'files/assets/images/{SITE_NAME}/{type}/{num}.webp' filename = f'files/assets/images/{SITE_NAME}/{type}/{num}.webp'
copyfile(oldname, filename) copyfile(oldname, filename)
process_image(filename, resize=resize) process_image(filename, v, resize=resize)
if parent_post.id == SIDEBAR_THREAD: if parent_post.id == SIDEBAR_THREAD:
process_sidebar_or_banner('sidebar', 400) process_sidebar_or_banner('sidebar', 400)
@ -177,15 +181,15 @@ def comment(v):
g.db.flush() g.db.flush()
filename = f'files/assets/images/badges/{badge.id}.webp' filename = f'files/assets/images/badges/{badge.id}.webp'
copyfile(oldname, filename) copyfile(oldname, filename)
process_image(filename, resize=300) process_image(filename, v, resize=300)
purge_files_in_cache(f"https://{SITE}/assets/images/badges/{badge.id}.webp") purge_files_in_cache(f"https://{SITE}/assets/images/badges/{badge.id}.webp")
except Exception as e: except Exception as e:
abort(400, str(e)) abort(400, str(e))
body += f"\n\n![]({image})" body += f"\n\n![]({image})"
elif file.content_type.startswith('video/'): elif file.content_type.startswith('video/'):
body += f"\n\n{SITE_FULL}{process_video(file)}" body += f"\n\n{SITE_FULL}{process_video(file, v)}"
elif file.content_type.startswith('audio/'): elif file.content_type.startswith('audio/'):
body += f"\n\n{SITE_FULL}{process_audio(file)}" body += f"\n\n{SITE_FULL}{process_audio(file, v)}"
else: else:
abort(415) abort(415)
@ -362,7 +366,7 @@ def comment(v):
g.db.flush() g.db.flush()
if v.client: return c.json if v.client: return c.json(g.db)
return {"comment": render_template("comments.html", v=v, comments=[c])} return {"comment": render_template("comments.html", v=v, comments=[c])}
@ -382,10 +386,10 @@ def edit_comment(cid, v):
body = sanitize_raw_body(request.values.get("body", ""), False) body = sanitize_raw_body(request.values.get("body", ""), False)
if len(body) < 1 and not (request.files.get("file") and request.headers.get("cf-ipcountry") != "T1"): if len(body) < 1 and not (request.files.get("file") and not g.is_tor):
abort(400, "You have to actually type something!") abort(400, "You have to actually type something!")
if body != c.body or request.files.get("file") and request.headers.get("cf-ipcountry") != "T1": if body != c.body or request.files.get("file") and not g.is_tor:
if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')): if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
abort(403, "You have to type more than 280 characters!") abort(403, "You have to type more than 280 characters!")
elif v.bird and len(body) > 140: elif v.bird and len(body) > 140:
@ -415,7 +419,7 @@ def edit_comment(cid, v):
execute_antispam_comment_check(body, v) execute_antispam_comment_check(body, v)
body += process_files() body += process_files(request.files, v)
body = body.strip()[:COMMENT_BODY_LENGTH_LIMIT] # process_files potentially adds characters to the post body = body.strip()[:COMMENT_BODY_LENGTH_LIMIT] # process_files potentially adds characters to the post
body_for_sanitize = body body_for_sanitize = body
@ -463,17 +467,11 @@ def edit_comment(cid, v):
@auth_required @auth_required
@ratelimit_user() @ratelimit_user()
def delete_comment(cid, v): def delete_comment(cid, v):
c = get_comment(cid, v=v) c = get_comment(cid, v=v)
if not c.deleted_utc: if not c.deleted_utc:
if c.author_id != v.id: abort(403) if c.author_id != v.id: abort(403)
c.deleted_utc = int(time.time()) c.deleted_utc = int(time.time())
g.db.add(c) g.db.add(c)
cache.delete_memoized(comment_idlist) cache.delete_memoized(comment_idlist)
g.db.flush() g.db.flush()
@ -483,7 +481,6 @@ def delete_comment(cid, v):
Comment.deleted_utc == 0 Comment.deleted_utc == 0
).count() ).count()
g.db.add(v) g.db.add(v)
return {"message": "Comment deleted!"} return {"message": "Comment deleted!"}
@app.post("/undelete/comment/<cid>") @app.post("/undelete/comment/<cid>")
@ -491,18 +488,12 @@ def delete_comment(cid, v):
@auth_required @auth_required
@ratelimit_user() @ratelimit_user()
def undelete_comment(cid, v): def undelete_comment(cid, v):
c = get_comment(cid, v=v) c = get_comment(cid, v=v)
if c.deleted_utc: if c.deleted_utc:
if c.author_id != v.id: abort(403) if c.author_id != v.id: abort(403)
c.deleted_utc = 0 c.deleted_utc = 0
g.db.add(c) g.db.add(c)
cache.delete_memoized(comment_idlist) cache.delete_memoized(comment_idlist)
g.db.flush() g.db.flush()
v.comment_count = g.db.query(Comment).filter( v.comment_count = g.db.query(Comment).filter(
Comment.author_id == v.id, Comment.author_id == v.id,
@ -510,10 +501,8 @@ def undelete_comment(cid, v):
Comment.deleted_utc == 0 Comment.deleted_utc == 0
).count() ).count()
g.db.add(v) g.db.add(v)
return {"message": "Comment undeleted!"} return {"message": "Comment undeleted!"}
@app.post("/pin_comment/<cid>") @app.post("/pin_comment/<cid>")
@feature_required('PINS') @feature_required('PINS')
@auth_required @auth_required

View File

@ -1,8 +1,10 @@
from files.helpers.wrappers import *
from flask import *
from urllib.parse import quote, urlencode
import time import time
from files.__main__ import app, limiter from urllib.parse import quote, urlencode
from flask import redirect, render_template, request, session, g
from files.helpers.const import ERROR_MARSEYS, ERROR_MSGS, ERROR_TITLES, WERKZEUG_ERROR_DESCRIPTIONS, is_site_url
from files.__main__ import app
# If you're adding an error, go here: # If you're adding an error, go here:
# https://github.com/pallets/werkzeug/blob/main/src/werkzeug/exceptions.py # https://github.com/pallets/werkzeug/blob/main/src/werkzeug/exceptions.py
@ -53,6 +55,6 @@ def error_500(e):
@app.post("/allow_nsfw") @app.post("/allow_nsfw")
def allow_nsfw(): def allow_nsfw():
session["over_18"] = int(time.time()) + 3600 session["over_18"] = int(time.time()) + 3600
redir = request.values.get("redir") redir = request.values.get("redir", "/")
if is_site_url(redir): return redirect(redir) if is_site_url(redir): return redirect(redir)
return redirect('/') return redirect('/')

View File

@ -1,11 +1,11 @@
import html
from .front import frontlist
from datetime import datetime from datetime import datetime
from files.helpers.get import *
from yattag import Doc
from files.helpers.wrappers import *
from files.helpers.jinja2 import *
from yattag import Doc
from files.helpers.get import *
from files.routes.wrappers import *
from .front import frontlist
from files.__main__ import app from files.__main__ import app
@app.get('/rss') @app.get('/rss')

View File

@ -1,10 +1,14 @@
from files.helpers.wrappers import *
from files.helpers.get import * from sqlalchemy import or_, not_
from files.helpers.const import *
from files.helpers.sorting_and_time import *
from files.__main__ import app, cache, limiter
from files.classes.submission import Submission from files.classes.submission import Submission
from files.classes.votes import Vote
from files.helpers.awards import award_timers from files.helpers.awards import award_timers
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.sorting_and_time import *
from files.routes.wrappers import *
from files.__main__ import app, cache, limiter
@app.get("/") @app.get("/")
@app.get("/h/<sub>") @app.get("/h/<sub>")
@ -13,8 +17,9 @@ from files.helpers.awards import award_timers
@auth_desired_with_logingate @auth_desired_with_logingate
def front_all(v, sub=None, subdomain=None): def front_all(v, sub=None, subdomain=None):
#### WPD TEMP #### special front logic #### WPD TEMP #### special front logic
from files.helpers.security import generate_hash, validate_hash
from datetime import datetime from datetime import datetime
from files.helpers.security import generate_hash, validate_hash
now = datetime.utcnow() now = datetime.utcnow()
if SITE == 'watchpeopledie.co': if SITE == 'watchpeopledie.co':
if v and not v.admin_level and not v.id <= 9: # security: don't auto login admins or bots if v and not v.admin_level and not v.id <= 9: # security: don't auto login admins or bots
@ -89,11 +94,10 @@ def front_all(v, sub=None, subdomain=None):
if v.hidevotedon: posts = [x for x in posts if not hasattr(x, 'voted') or not x.voted] if v.hidevotedon: posts = [x for x in posts if not hasattr(x, 'voted') or not x.voted]
award_timers(v) award_timers(v)
if v and v.client: return {"data": [x.json for x in posts], "next_exists": next_exists} if v and v.client: return {"data": [x.json(g.db) for x in posts], "next_exists": next_exists}
return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page, sub=sub, home=True, pins=pins, holes=holes) return render_template("home.html", v=v, listing=posts, next_exists=next_exists, sort=sort, t=t, page=page, sub=sub, home=True, pins=pins, holes=holes)
@cache.memoize(timeout=86400) @cache.memoize(timeout=86400)
def frontlist(v=None, sort="hot", page=1, t="all", ids_only=True, filter_words='', gt=0, lt=0, sub=None, site=None, pins=True, holes=True): def frontlist(v=None, sort="hot", page=1, t="all", ids_only=True, filter_words='', gt=0, lt=0, sub=None, site=None, pins=True, holes=True):
posts = g.db.query(Submission) posts = g.db.query(Submission)
@ -227,7 +231,7 @@ def all_comments(v):
next_exists = len(idlist) > PAGE_SIZE next_exists = len(idlist) > PAGE_SIZE
idlist = idlist[:PAGE_SIZE] idlist = idlist[:PAGE_SIZE]
if v.client: return {"data": [x.json for x in comments]} if v.client: return {"data": [x.json(g.db) for x in comments]}
return render_template("home_comments.html", v=v, sort=sort, t=t, page=page, comments=comments, standalone=True, next_exists=next_exists) return render_template("home_comments.html", v=v, sort=sort, t=t, page=page, comments=comments, standalone=True, next_exists=next_exists)

View File

@ -1,11 +1,10 @@
from flask import *
import requests import requests
from files.helpers.wrappers import *
from files.helpers.const import * from files.helpers.const import *
from files.routes.wrappers import *
from files.__main__ import app from files.__main__ import app
@app.get("/giphy") @app.get("/giphy")
@app.get("/giphy<path>") @app.get("/giphy<path>")
@auth_required @auth_required

View File

@ -1,10 +1,11 @@
from files.__main__ import app, limiter from sqlalchemy import func
from files.classes.hats import * from files.classes.hats import *
from files.helpers.alerts import * from files.helpers.alerts import *
from files.helpers.wrappers import *
from files.helpers.const import * from files.helpers.const import *
from files.helpers.useractions import * from files.helpers.useractions import *
from flask import g from files.routes.wrappers import *
from files.__main__ import app, limiter
@app.get("/hats") @app.get("/hats")
@feature_required('HATS') @feature_required('HATS')

View File

@ -1,20 +1,30 @@
from files.__main__ import app, cache
from jinja2 import pass_context
from .get import *
from os import listdir, environ
from .const import *
import time import time
from os import environ, listdir
from jinja2 import pass_context
from files.helpers.assetcache import assetcache_path from files.helpers.assetcache import assetcache_path
from files.helpers.wrappers import calc_users from files.helpers.const import *
from files.helpers.settings import get_settings
from files.helpers.sorting_and_time import make_age_string
from files.routes.routehelpers import get_formkey
from files.routes.wrappers import calc_users
from files.__main__ import app, cache
@app.template_filter("formkey")
def formkey(u):
return get_formkey(u)
@app.template_filter("post_embed") @app.template_filter("post_embed")
def post_embed(id, v): def post_embed(id, v):
from flask import render_template
from files.helpers.get import get_post
p = get_post(id, v, graceful=True) p = get_post(id, v, graceful=True)
if p: return render_template("submission_listing.html", listing=[p], v=v) if p: return render_template("submission_listing.html", listing=[p], v=v)
return '' return ''
@app.template_filter("asset") @app.template_filter("asset")
@pass_context @pass_context
def template_asset(ctx, asset_path): def template_asset(ctx, asset_path):
@ -26,7 +36,6 @@ def template_asset_siteimg(asset_path):
# TODO: Add hashing for these using files.helpers.assetcache # TODO: Add hashing for these using files.helpers.assetcache
return f'/i/{SITE_NAME}/{asset_path}?v=3010' return f'/i/{SITE_NAME}/{asset_path}?v=3010'
@app.template_filter("timestamp") @app.template_filter("timestamp")
def timestamp(timestamp): def timestamp(timestamp):
return make_age_string(timestamp) return make_age_string(timestamp)
@ -46,7 +55,7 @@ def inject_constants():
"BADGE_THREAD":BADGE_THREAD, "SNAPPY_THREAD":SNAPPY_THREAD, "BADGE_THREAD":BADGE_THREAD, "SNAPPY_THREAD":SNAPPY_THREAD,
"KOFI_TOKEN":KOFI_TOKEN, "KOFI_LINK":KOFI_LINK, "KOFI_TOKEN":KOFI_TOKEN, "KOFI_LINK":KOFI_LINK,
"approved_embed_hosts":approved_embed_hosts, "approved_embed_hosts":approved_embed_hosts,
"site_settings":app.config['SETTINGS'], "EMAIL":EMAIL, "calc_users":calc_users, "site_settings":get_settings(), "EMAIL":EMAIL, "calc_users":calc_users,
"max": max, "min": min, "max": max, "min": min,
"TELEGRAM_LINK":TELEGRAM_LINK, "EMAIL_REGEX_PATTERN":EMAIL_REGEX_PATTERN, "TELEGRAM_LINK":TELEGRAM_LINK, "EMAIL_REGEX_PATTERN":EMAIL_REGEX_PATTERN,
"CONTENT_SECURITY_POLICY_DEFAULT":CONTENT_SECURITY_POLICY_DEFAULT, "CONTENT_SECURITY_POLICY_DEFAULT":CONTENT_SECURITY_POLICY_DEFAULT,

View File

@ -1,17 +1,24 @@
from urllib.parse import urlencode
from files.mail import *
from files.__main__ import app, get_CF, limiter
from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.actions import *
from files.helpers.get import *
import requests
import secrets import secrets
from urllib.parse import urlencode
import requests
from files.__main__ import app, cache, get_CF, limiter
from files.classes.follows import Follow
from files.helpers.actions import *
from files.helpers.const import *
from files.helpers.settings import get_setting
from files.helpers.get import *
from files.helpers.mail import send_mail, send_verification_email
from files.helpers.regex import *
from files.helpers.security import *
from files.helpers.useractions import badge_grant
from files.routes.routehelpers import check_for_alts
from files.routes.wrappers import *
@app.get("/login") @app.get("/login")
@auth_desired @auth_desired
def login_get(v): def login_get(v):
redir = request.values.get("redirect", "/").strip().rstrip('?') redir = request.values.get("redirect", "/").strip().rstrip('?')
if redir: if redir:
if not is_site_url(redir): redir = "/" if not is_site_url(redir): redir = "/"
@ -19,63 +26,6 @@ def login_get(v):
return render_template("login.html", failed=False, redirect=redir) return render_template("login.html", failed=False, redirect=redir)
def check_for_alts(current:User, include_current_session=True):
current_id = current.id
if current_id in (1691,6790,7069,36152) and include_current_session:
session["history"] = []
return
ids = [x[0] for x in g.db.query(User.id).all()]
past_accs = set(session.get("history", [])) if include_current_session else set()
def add_alt(user1:int, user2:int):
li = [user1, user2]
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if not existing:
new_alt = Alt(user1=user1, user2=user2)
g.db.add(new_alt)
g.db.flush()
for past_id in list(past_accs):
if past_id not in ids:
past_accs.remove(past_id)
continue
if past_id == MOM_ID or current_id == MOM_ID: break
if past_id == current_id: continue
li = [past_id, current_id]
add_alt(past_id, current_id)
other_alts = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).all()
for a in other_alts:
if a.deleted:
if include_current_session:
try: session["history"].remove(a.user1)
except: pass
try: session["history"].remove(a.user2)
except: pass
continue # don't propagate deleted alt links
if a.user1 != past_id: add_alt(a.user1, past_id)
if a.user1 != current_id: add_alt(a.user1, current_id)
if a.user2 != past_id: add_alt(a.user2, past_id)
if a.user2 != current_id: add_alt(a.user2, current_id)
past_accs.add(current_id)
if include_current_session:
session["history"] = list(past_accs)
g.db.flush()
for u in current.alts_unique:
if u._alt_deleted: continue
if u.shadowbanned:
current.shadowbanned = u.shadowbanned
if not current.is_banned: current.ban_reason = u.ban_reason
g.db.add(current)
elif current.shadowbanned:
u.shadowbanned = current.shadowbanned
if not u.is_banned: u.ban_reason = current.ban_reason
g.db.add(u)
def login_deduct_when(resp): def login_deduct_when(resp):
if not g: if not g:
return False return False
@ -84,8 +34,7 @@ def login_deduct_when(resp):
return g.login_failed return g.login_failed
@app.post("/login") @app.post("/login")
@limiter.limit("6/minute;10/day", @limiter.limit("6/minute;10/day", deduct_when=login_deduct_when)
deduct_when=login_deduct_when)
def login_post(): def login_post():
template = '' template = ''
g.login_failed = True g.login_failed = True
@ -188,20 +137,16 @@ def me(v):
@auth_required @auth_required
@ratelimit_user() @ratelimit_user()
def logout(v): def logout(v):
loggedin = cache.get(f'{SITE}_loggedin') or {} loggedin = cache.get(f'{SITE}_loggedin') or {}
if session.get("lo_user") in loggedin: del loggedin[session["lo_user"]] if session.get("lo_user") in loggedin: del loggedin[session["lo_user"]]
cache.set(f'{SITE}_loggedin', loggedin) cache.set(f'{SITE}_loggedin', loggedin)
session.pop("lo_user", None) session.pop("lo_user", None)
return {"message": "Logout successful!"} return {"message": "Logout successful!"}
@app.get("/signup") @app.get("/signup")
@auth_desired @auth_desired
def sign_up_get(v): def sign_up_get(v):
if not app.config['SETTINGS']['Signups']: if not get_setting('Signups'):
return {"error": "New account registration is currently closed. Please come back later."}, 403 return {"error": "New account registration is currently closed. Please come back later."}, 403
if v: return redirect(SITE_FULL) if v: return redirect(SITE_FULL)
@ -219,7 +164,7 @@ def sign_up_get(v):
return render_template("sign_up_failed_ref.html") return render_template("sign_up_failed_ref.html")
now = int(time.time()) now = int(time.time())
token = token_hex(16) token = secrets.token_hex(16)
session["signup_token"] = token session["signup_token"] = token
formkey_hashstr = str(now) + token + g.agent formkey_hashstr = str(now) + token + g.agent
@ -249,7 +194,7 @@ def sign_up_get(v):
@limiter.limit("1/second;10/day") @limiter.limit("1/second;10/day")
@auth_desired @auth_desired
def sign_up_post(v): def sign_up_post(v):
if not app.config['SETTINGS']['Signups']: if not get_setting('Signups'):
return {"error": "New account registration is currently closed. Please come back later."}, 403 return {"error": "New account registration is currently closed. Please come back later."}, 403
if v: abort(403) if v: abort(403)
@ -261,18 +206,14 @@ def sign_up_post(v):
if not submitted_token: abort(400) if not submitted_token: abort(400)
correct_formkey_hashstr = form_timestamp + submitted_token + g.agent correct_formkey_hashstr = form_timestamp + submitted_token + g.agent
correct_formkey = hmac.new(key=bytes(SECRET_KEY, "utf-16"), correct_formkey = hmac.new(key=bytes(SECRET_KEY, "utf-16"),
msg=bytes(correct_formkey_hashstr, "utf-16"), msg=bytes(correct_formkey_hashstr, "utf-16"),
digestmod='md5' digestmod='md5'
).hexdigest() ).hexdigest()
now = int(time.time()) now = int(time.time())
username = request.values.get("username") username = request.values.get("username")
if not username: abort(400) if not username: abort(400)
username = username.strip() username = username.strip()
def signup_error(error): def signup_error(error):

View File

@ -1,12 +1,13 @@
from files.__main__ import app, limiter
from files.helpers.wrappers import *
from files.helpers.alerts import *
from files.helpers.get import *
from files.helpers.const import *
from files.helpers.wrappers import *
from files.helpers.lottery import *
import requests import requests
from files.helpers.alerts import *
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.lottery import *
from files.routes.wrappers import *
from files.__main__ import app, limiter
@app.post("/lottery/end") @app.post("/lottery/end")
@feature_required('GAMBLING') @feature_required('GAMBLING')
@admin_level_required(PERMS['LOTTERY_ADMIN']) @admin_level_required(PERMS['LOTTERY_ADMIN'])

View File

@ -1,75 +1,30 @@
import requests
import time import time
from flask import *
from urllib.parse import quote
from files.helpers.security import * from files.classes import *
from files.helpers.wrappers import *
from files.helpers.const import * from files.helpers.const import *
from files.helpers.get import * from files.helpers.get import *
from files.helpers.mail import *
from files.helpers.useractions import * from files.helpers.useractions import *
from files.classes import * from files.routes.wrappers import *
from files.__main__ import app, limiter from files.__main__ import app, limiter
def send_mail(to_address, subject, html):
if MAILGUN_KEY == 'blahblahblah': return
url = f"https://api.mailgun.net/v3/{SITE}/messages"
auth = ("api", MAILGUN_KEY)
data = {"from": EMAIL,
"to": [to_address],
"subject": subject,
"html": html,
}
requests.post(url, auth=auth, data=data)
def send_verification_email(user, email=None):
if not email:
email = user.email
url = f"https://{SITE}/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 {SITE_NAME} account email."
)
@app.post("/verify_email") @app.post("/verify_email")
@limiter.limit(DEFAULT_RATELIMIT_SLOWER) @limiter.limit(DEFAULT_RATELIMIT_SLOWER)
@auth_required @auth_required
@ratelimit_user() @ratelimit_user()
def verify_email(v): def verify_email(v):
send_verification_email(v) send_verification_email(v)
return {"message": "Email has been sent (ETA ~5 minutes)"} return {"message": "Email has been sent (ETA ~5 minutes)"}
@app.get("/activate") @app.get("/activate")
@auth_required @auth_required
def activate(v): def activate(v):
email = request.values.get("email", "").strip().lower() email = request.values.get("email", "").strip().lower()
if not email_regex.fullmatch(email): if not email_regex.fullmatch(email):
return render_template("message.html", v=v, title="Invalid email.", error="Invalid email."), 400 return render_template("message.html", v=v, title="Invalid email.", error="Invalid email."), 400
id = request.values.get("id", "").strip() id = request.values.get("id", "").strip()
timestamp = int(request.values.get("time", "0")) timestamp = int(request.values.get("time", "0"))
token = request.values.get("token", "").strip() token = request.values.get("token", "").strip()

View File

@ -1,9 +1,14 @@
from files.helpers.wrappers import *
from files.helpers.get import *
from files.helpers.const import *
from files.__main__ import app
import time import time
from sqlalchemy.sql.expression import not_, and_, or_
from files.classes.mod_logs import ModAction
from files.classes.sub_logs import SubAction
from files.helpers.const import *
from files.helpers.get import *
from files.routes.wrappers import *
from files.__main__ import app
@app.post("/clear") @app.post("/clear")
@auth_required @auth_required
@ratelimit_user() @ratelimit_user()
@ -36,7 +41,6 @@ def unread(v):
return {"data":[x[1].json for x in listing]} return {"data":[x[1].json for x in listing]}
@app.get("/notifications/modmail") @app.get("/notifications/modmail")
@admin_level_required(PERMS['VIEW_MODMAIL']) @admin_level_required(PERMS['VIEW_MODMAIL'])
def notifications_modmail(v): def notifications_modmail(v):

View File

@ -1,12 +1,12 @@
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 import sqlalchemy.exc
from files.classes import *
from files.helpers.alerts import *
from files.helpers.const import *
from files.helpers.get import *
from files.routes.wrappers import *
from files.__main__ import app, limiter
@app.get("/authorize") @app.get("/authorize")
@auth_required @auth_required
def authorize_prompt(v): def authorize_prompt(v):
@ -233,7 +233,7 @@ def admin_app_id_posts(v, aid):
oauth = g.db.get(OauthApp, aid) oauth = g.db.get(OauthApp, aid)
if not oauth: abort(404) if not oauth: abort(404)
pids=oauth.idlist(page=int(request.values.get("page",1))) pids=oauth.idlist(g.db, page=int(request.values.get("page",1)))
next_exists=len(pids)==101 next_exists=len(pids)==101
pids=pids[:100] pids=pids[:100]
@ -256,8 +256,7 @@ def admin_app_id_comments(v, aid):
oauth = g.db.get(OauthApp, aid) oauth = g.db.get(OauthApp, aid)
if not oauth: abort(404) if not oauth: abort(404)
cids=oauth.comments_idlist(page=int(request.values.get("page",1)), cids=oauth.comments_idlist(g.db, page=int(request.values.get("page",1)))
)
next_exists=len(cids)==101 next_exists=len(cids)==101
cids=cids[:100] cids=cids[:100]

View File

@ -1,8 +1,7 @@
from files.helpers.wrappers import *
from files.helpers.get import *
from files.helpers.const import *
from files.classes import * from files.classes import *
from flask import * from files.helpers.const import *
from files.helpers.get import *
from files.routes.wrappers import *
from files.__main__ import app from files.__main__ import app

View File

@ -1,33 +1,38 @@
import os
import time import time
import gevent
import requests
from files.helpers.wrappers import *
from files.helpers.sanitize import *
from files.helpers.alerts import *
from files.helpers.discord import *
from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.slots import *
from files.helpers.get import *
from files.helpers.actions import *
from files.helpers.sorting_and_time import *
from files.classes import *
from flask import *
from io import BytesIO from io import BytesIO
from files.__main__ import app, limiter, cache, db_session
from PIL import Image
from .front import frontlist
from urllib.parse import ParseResult, urlunparse, urlparse, quote, unquote
from os import path from os import path
import requests
from shutil import copyfile from shutil import copyfile
from sys import stdout from sys import stdout
import os from urllib.parse import ParseResult, quote, unquote, urlparse, urlunparse
import gevent
import requests
from flask import *
from PIL import Image
from files.__main__ import app, cache, limiter
from files.classes import *
from files.helpers.actions import *
from files.helpers.alerts import *
from files.helpers.const import *
from files.helpers.discord import *
from files.helpers.get import *
from files.helpers.regex import *
from files.helpers.sanitize import *
from files.helpers.settings import get_setting
from files.helpers.slots import *
from files.helpers.sorting_and_time import *
from files.routes.routehelpers import execute_shadowban_viewers_and_voters
from files.routes.wrappers import *
from .front import frontlist
from .users import userpagelisting
from files.__main__ import app, limiter
titleheaders = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"} titleheaders = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"}
@app.post("/club_post/<pid>") @app.post("/club_post/<pid>")
@feature_required('COUNTRY_CLUB') @feature_required('COUNTRY_CLUB')
@auth_required @auth_required
@ -100,7 +105,7 @@ def publish(pid, v):
cache.delete_memoized(frontlist) cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting) cache.delete_memoized(userpagelisting)
if post.sub == 'changelog': if post.sub == 'changelog':
send_changelog_message(post.permalink) send_changelog_message(post.permalink)
@ -147,6 +152,7 @@ def post_id(pid, anything=None, v=None, sub=None):
if post.club and not (v and (v.paid_dues or v.id == post.author_id)): abort(403) if post.club and not (v and (v.paid_dues or v.id == post.author_id)): abort(403)
if v: if v:
execute_shadowban_viewers_and_voters(v, post)
# shadowban check is done in sort_objects # shadowban check is done in sort_objects
# output is needed: see comments.py # output is needed: see comments.py
comments, output = get_comments_v_properties(v, True, None, Comment.parent_submission == post.id, Comment.level < 10) comments, output = get_comments_v_properties(v, True, None, Comment.parent_submission == post.id, Comment.level < 10)
@ -214,7 +220,7 @@ def post_id(pid, anything=None, v=None, sub=None):
g.db.add(post) g.db.add(post)
if v and v.client: if v and v.client:
return post.json return post.json(g.db)
template = "submission.html" template = "submission.html"
if (post.is_banned or post.author.shadowbanned) \ if (post.is_banned or post.author.shadowbanned) \
@ -223,7 +229,7 @@ def post_id(pid, anything=None, v=None, sub=None):
return render_template(template, v=v, p=post, ids=list(ids), return render_template(template, v=v, p=post, ids=list(ids),
sort=sort, render_replies=True, offset=offset, sub=post.subr, sort=sort, render_replies=True, offset=offset, sub=post.subr,
fart=app.config['SETTINGS']['Fart mode']) fart=get_setting('Fart mode'))
@app.get("/viewmore/<pid>/<sort>/<offset>") @app.get("/viewmore/<pid>/<sort>/<offset>")
@limiter.limit(DEFAULT_RATELIMIT_SLOWER) @limiter.limit(DEFAULT_RATELIMIT_SLOWER)
@ -297,7 +303,7 @@ def morecomments(v, cid):
comments = output comments = output
else: else:
c = get_comment(cid) c = get_comment(cid)
comments = c.replies(sort=request.values.get('sort'), v=v) comments = c.replies(sort=request.values.get('sort'), v=v, db=g.db)
if comments: p = comments[0].post if comments: p = comments[0].post
else: p = None else: p = None
@ -340,7 +346,7 @@ def edit_post(pid, v):
p.title = title p.title = title
p.title_html = title_html p.title_html = title_html
body += process_files() body += process_files(request.files, v)
body = body.strip()[:POST_BODY_LENGTH_LIMIT] # process_files() may be adding stuff to the body body = body.strip()[:POST_BODY_LENGTH_LIMIT] # process_files() may be adding stuff to the body
if body != p.body: if body != p.body:
@ -422,12 +428,8 @@ def edit_post(pid, v):
return redirect(p.permalink) return redirect(p.permalink)
def thumbnail_thread(pid): def thumbnail_thread(pid:int, db, vid:int):
db = db_session()
def expand_url(post_url, fragment_url): def expand_url(post_url, fragment_url):
if fragment_url.startswith("https://"): if fragment_url.startswith("https://"):
return fragment_url return fragment_url
elif fragment_url.startswith("https://"): elif fragment_url.startswith("https://"):
@ -464,8 +466,6 @@ def thumbnail_thread(pid):
if x.status_code != 200: if x.status_code != 200:
db.close() db.close()
return return
if x.headers.get("Content-Type","").startswith("text/html"): if x.headers.get("Content-Type","").startswith("text/html"):
soup=BeautifulSoup(x.content, 'lxml') soup=BeautifulSoup(x.content, 'lxml')
@ -505,7 +505,6 @@ def thumbnail_thread(pid):
for url in thumb_candidate_urls: for url in thumb_candidate_urls:
try: try:
image_req=requests.get(url, headers=headers, timeout=5, proxies=proxies) image_req=requests.get(url, headers=headers, timeout=5, proxies=proxies)
except: except:
@ -523,15 +522,11 @@ def thumbnail_thread(pid):
with Image.open(BytesIO(image_req.content)) as i: with Image.open(BytesIO(image_req.content)) as i:
if i.width < 30 or i.height < 30: if i.width < 30 or i.height < 30:
continue continue
break break
else: else:
db.close() db.close()
return return
elif x.headers.get("Content-Type","").startswith("image/"): elif x.headers.get("Content-Type","").startswith("image/"):
image_req=x image_req=x
with Image.open(BytesIO(x.content)) as i: with Image.open(BytesIO(x.content)) as i:
@ -550,9 +545,12 @@ def thumbnail_thread(pid):
for chunk in image_req.iter_content(1024): for chunk in image_req.iter_content(1024):
file.write(chunk) file.write(chunk)
post.thumburl = process_image(name, resize=100, uploader=post.author_id, db=db) v = db.get(User, vid)
db.add(post) url = process_image(name, v, resize=100, uploader_id=post.author_id, db=db)
db.commit() if url:
post.thumburl = url
db.add(post)
db.commit()
db.close() db.close()
stdout.flush() stdout.flush()
return return
@ -768,7 +766,7 @@ def submit_post(v, sub=None):
choices.append(i.group(1)) choices.append(i.group(1))
body = body.replace(i.group(0), "") body = body.replace(i.group(0), "")
body += process_files() body += process_files(request.files, v)
body = body.strip()[:POST_BODY_LENGTH_LIMIT] # process_files() adds content to the body, so we need to re-strip body = body.strip()[:POST_BODY_LENGTH_LIMIT] # process_files() adds content to the body, so we need to re-strip
torture = (v.agendaposter and not v.marseyawarded and sub != 'chudrama') torture = (v.agendaposter and not v.marseyawarded and sub != 'chudrama')
@ -858,33 +856,28 @@ def submit_post(v, sub=None):
) )
g.db.add(vote) g.db.add(vote)
if request.files.get('file-url') and request.headers.get("cf-ipcountry") != "T1": if request.files.get('file-url') and not g.is_tor:
file = request.files['file-url'] file = request.files['file-url']
if file.content_type.startswith('image/'): if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp' name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name) file.save(name)
post.url = process_image(name, patron=v.patron) post.url = process_image(name, v)
name2 = name.replace('.webp', 'r.webp') name2 = name.replace('.webp', 'r.webp')
copyfile(name, name2) copyfile(name, name2)
post.thumburl = process_image(name2, resize=100) post.thumburl = process_image(name2, v, resize=100)
elif file.content_type.startswith('video/'): elif file.content_type.startswith('video/'):
post.url = process_video(file) post.url = process_video(file, v)
elif file.content_type.startswith('audio/'): elif file.content_type.startswith('audio/'):
post.url = process_audio(file) post.url = process_audio(file, v)
else: else:
abort(415) abort(415)
if not post.thumburl and post.url: if not post.thumburl and post.url:
gevent.spawn(thumbnail_thread, post.id) gevent.spawn(thumbnail_thread, post.id, g.db, v.id)
if not post.private and not post.ghost: if not post.private and not post.ghost:
notify_users = NOTIFY_USERS(f'{title} {body}', v) notify_users = NOTIFY_USERS(f'{title} {body}', v)
if notify_users: if notify_users:
@ -936,7 +929,7 @@ def submit_post(v, sub=None):
execute_lawlz_actions(v, post) execute_lawlz_actions(v, post)
cache.delete_memoized(frontlist) cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting) cache.delete_memoized(userpagelisting)
if post.sub == 'changelog' and not post.private: if post.sub == 'changelog' and not post.private:
send_changelog_message(post.permalink) send_changelog_message(post.permalink)
@ -945,7 +938,7 @@ def submit_post(v, sub=None):
send_wpd_message(post.permalink) send_wpd_message(post.permalink)
g.db.commit() g.db.commit()
if v.client: return post.json if v.client: return post.json(g.db)
else: else:
post.voted = 1 post.voted = 1
if post.new or 'megathread' in post.title.lower(): sort = 'new' if post.new or 'megathread' in post.title.lower(): sort = 'new'
@ -972,7 +965,7 @@ def delete_post_pid(pid, v):
g.db.add(post) g.db.add(post)
cache.delete_memoized(frontlist) cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting) cache.delete_memoized(userpagelisting)
g.db.flush() g.db.flush()
v.post_count = g.db.query(Submission).filter_by(author_id=v.id, deleted_utc=0).count() v.post_count = g.db.query(Submission).filter_by(author_id=v.id, deleted_utc=0).count()
@ -993,7 +986,7 @@ def undelete_post_pid(pid, v):
g.db.add(post) g.db.add(post)
cache.delete_memoized(frontlist) cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting) cache.delete_memoized(userpagelisting)
g.db.flush() g.db.flush()
v.post_count = g.db.query(Submission).filter_by(author_id=v.id, deleted_utc=0).count() v.post_count = g.db.query(Submission).filter_by(author_id=v.id, deleted_utc=0).count()
@ -1075,7 +1068,7 @@ def pin_post(post_id, v):
if v.id != post.author_id: abort(403, "Only the post author can do that!") if v.id != post.author_id: abort(403, "Only the post author can do that!")
post.is_pinned = not post.is_pinned post.is_pinned = not post.is_pinned
g.db.add(post) g.db.add(post)
cache.delete_memoized(User.userpagelisting) cache.delete_memoized(userpagelisting)
if post.is_pinned: return {"message": "Post pinned!"} if post.is_pinned: return {"message": "Post pinned!"}
else: return {"message": "Post unpinned!"} else: return {"message": "Post unpinned!"}
return abort(404, "Post not found!") return abort(404, "Post not found!")

View File

@ -1,12 +1,15 @@
from files.helpers.wrappers import *
from files.helpers.get import *
from files.helpers.alerts import *
from files.helpers.actions import *
from flask import g from flask import g
from files.__main__ import app, limiter, cache
from os import path from files.classes.flags import Flag, CommentFlag
from files.classes.mod_logs import ModAction
from files.classes.sub_logs import SubAction
from files.helpers.actions import *
from files.helpers.alerts import *
from files.helpers.get import *
from files.helpers.sanitize import filter_emojis_only from files.helpers.sanitize import filter_emojis_only
from files.routes.front import frontlist from files.routes.front import frontlist
from files.routes.wrappers import *
from files.__main__ import app, limiter, cache
@app.post("/report/post/<pid>") @app.post("/report/post/<pid>")
@limiter.limit(DEFAULT_RATELIMIT_SLOWER) @limiter.limit(DEFAULT_RATELIMIT_SLOWER)

View File

@ -0,0 +1,94 @@
import time
from random import randint
from typing import Optional, Union
from flask import g, session
from files.classes import Alt, Comment, User, Submission
from files.helpers.const import *
from files.helpers.security import generate_hash, validate_hash
def get_raw_formkey(u:User):
return f"{session['session_id']}+{u.id}+{u.login_nonce}"
def get_formkey(u:Optional[User]):
if not u: return "" # if no user exists, give them a blank formkey
return generate_hash(get_raw_formkey(u))
def validate_formkey(u:User, formkey:Optional[str]) -> bool:
if not formkey: return False
return validate_hash(get_raw_formkey(u), formkey)
def check_for_alts(current:User, include_current_session=True):
current_id = current.id
if current_id in (1691,6790,7069,36152) and include_current_session:
session["history"] = []
return
ids = [x[0] for x in g.db.query(User.id).all()]
past_accs = set(session.get("history", [])) if include_current_session else set()
def add_alt(user1:int, user2:int):
li = [user1, user2]
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if not existing:
new_alt = Alt(user1=user1, user2=user2)
g.db.add(new_alt)
g.db.flush()
for past_id in list(past_accs):
if past_id not in ids:
past_accs.remove(past_id)
continue
if past_id == MOM_ID or current_id == MOM_ID: break
if past_id == current_id: continue
li = [past_id, current_id]
add_alt(past_id, current_id)
other_alts = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).all()
for a in other_alts:
if a.deleted:
if include_current_session:
try: session["history"].remove(a.user1)
except: pass
try: session["history"].remove(a.user2)
except: pass
continue # don't propagate deleted alt links
if a.user1 != past_id: add_alt(a.user1, past_id)
if a.user1 != current_id: add_alt(a.user1, current_id)
if a.user2 != past_id: add_alt(a.user2, past_id)
if a.user2 != current_id: add_alt(a.user2, current_id)
past_accs.add(current_id)
if include_current_session:
session["history"] = list(past_accs)
g.db.flush()
for u in current.alts_unique:
if u._alt_deleted: continue
if u.shadowbanned:
current.shadowbanned = u.shadowbanned
if not current.is_banned: current.ban_reason = u.ban_reason
g.db.add(current)
elif current.shadowbanned:
u.shadowbanned = current.shadowbanned
if not u.is_banned: u.ban_reason = current.ban_reason
g.db.add(u)
def execute_shadowban_viewers_and_voters(v:Optional[User], target:Union[Submission, Comment]):
if not v or not v.shadowbanned: return
if not target: return
if v.id != target.author_id: return
if not (86400 > time.time() - target.created_utc > 60): return
ti = max(int((time.time() - target.created_utc)/60), 1)
max_upvotes = min(ti, 13)
rand = randint(0, max_upvotes)
if target.upvotes >= rand: return
amount = randint(0, 3)
if amount != 1: return
target.upvotes += amount
if isinstance(target, Submission):
target.views += amount*randint(3, 5)
g.db.add(target)

View File

@ -1,13 +1,14 @@
from files.helpers.wrappers import *
import re import re
from sqlalchemy import *
from flask import *
from files.__main__ import app
from files.helpers.regex import *
from files.helpers.sorting_and_time import *
import time import time
from calendar import timegm from calendar import timegm
from sqlalchemy import *
from files.helpers.regex import *
from files.helpers.sorting_and_time import *
from files.routes.wrappers import *
from files.__main__ import app
search_operator_hole = HOLE_NAME search_operator_hole = HOLE_NAME
valid_params = [ valid_params = [

View File

@ -1,20 +1,28 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from files.helpers.alerts import *
from files.helpers.sanitize import *
from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.actions import *
from files.helpers.useractions import *
from files.helpers.get import *
from files.helpers.security import *
from files.mail import *
from files.__main__ import app, cache, limiter
import youtube_dl
from .front import frontlist
import os import os
from files.helpers.sanitize import filter_emojis_only
from shutil import copyfile from shutil import copyfile
import pyotp
import requests import requests
import youtube_dl
from files.helpers.actions import *
from files.helpers.alerts import *
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.mail import *
from files.helpers.media import process_files, process_image
from files.helpers.regex import *
from files.helpers.sanitize import *
from files.helpers.sanitize import filter_emojis_only
from files.helpers.security import *
from files.helpers.useractions import *
from files.routes.wrappers import *
from .front import frontlist
from files.__main__ import app, cache, limiter
@app.get("/settings") @app.get("/settings")
@auth_required @auth_required
@ -220,7 +228,7 @@ def settings_personal_post(v):
elif not updated and FEATURES['USERS_PROFILE_BODYTEXT'] and \ elif not updated and FEATURES['USERS_PROFILE_BODYTEXT'] and \
(request.values.get("bio") or request.files.get('file')): (request.values.get("bio") or request.files.get('file')):
bio = request.values.get("bio")[:1500] bio = request.values.get("bio")[:1500]
bio += process_files() bio += process_files(request.files, v)
bio = bio.strip() bio = bio.strip()
bio_html = sanitize(bio) bio_html = sanitize(bio)
@ -336,6 +344,7 @@ def themecolor(v):
@auth_required @auth_required
@ratelimit_user() @ratelimit_user()
def gumroad(v): def gumroad(v):
if GUMROAD_TOKEN == DEFAULT_CONFIG_VALUE: abort(404)
if not (v.email and v.is_activated): if not (v.email and v.is_activated):
abort(400, f"You must have a verified email to verify {patron} status and claim your rewards!") abort(400, f"You must have a verified email to verify {patron} status and claim your rewards!")
@ -380,7 +389,7 @@ def titlecolor(v):
@ratelimit_user() @ratelimit_user()
def verifiedcolor(v): def verifiedcolor(v):
if not v.verified: abort(403, "You don't have a checkmark") if not v.verified: abort(403, "You don't have a checkmark")
return set_color(v, "verifiedcolor", "verifiedcolor") return set_color(v, "verifiedcolor", request.values.get("verifiedcolor"))
@app.post("/settings/security") @app.post("/settings/security")
@limiter.limit(DEFAULT_RATELIMIT_SLOWER) @limiter.limit(DEFAULT_RATELIMIT_SLOWER)
@ -475,19 +484,19 @@ def settings_log_out_others(v):
@auth_required @auth_required
@ratelimit_user() @ratelimit_user()
def settings_images_profile(v): def settings_images_profile(v):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") if g.is_tor: abort(403, "Image uploads are not allowed through TOR.")
file = request.files["profile"] file = request.files["profile"]
name = f'/images/{time.time()}'.replace('.','') + '.webp' name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name) file.save(name)
highres = process_image(name, patron=v.patron) highres = process_image(name, v)
if not highres: abort(400) if not highres: abort(400)
name2 = name.replace('.webp', 'r.webp') name2 = name.replace('.webp', 'r.webp')
copyfile(name, name2) copyfile(name, name2)
imageurl = process_image(name2, resize=100) imageurl = process_image(name2, v, resize=100)
if not imageurl: abort(400) if not imageurl: abort(400)
@ -511,13 +520,13 @@ def settings_images_profile(v):
@auth_required @auth_required
@ratelimit_user() @ratelimit_user()
def settings_images_banner(v): def settings_images_banner(v):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") if g.is_tor: abort(403, "Image uploads are not allowed through TOR.")
file = request.files["banner"] file = request.files["banner"]
name = f'/images/{time.time()}'.replace('.','') + '.webp' name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name) file.save(name)
bannerurl = process_image(name, patron=v.patron) bannerurl = process_image(name, v)
if bannerurl: if bannerurl:
if v.bannerurl and '/images/' in v.bannerurl: if v.bannerurl and '/images/' in v.bannerurl:

View File

@ -1,15 +1,19 @@
from files.mail import * import os
from files.__main__ import app, limiter from shutil import copyfile
from sqlalchemy import func, nullslast
from files.helpers.media import process_files
import files.helpers.stats as statshelper
from files.classes.award import AWARDS
from files.classes.badges import Badge, BadgeDef
from files.classes.mod_logs import ModAction, ACTIONTYPES, ACTIONTYPES2
from files.classes.userblock import UserBlock
from files.helpers.actions import *
from files.helpers.alerts import * from files.helpers.alerts import *
from files.helpers.const import * from files.helpers.const import *
from files.helpers.actions import * from files.routes.wrappers import *
from files.classes.award import AWARDS from files.__main__ import app, cache, limiter
from sqlalchemy import func, nullslast
import os
from files.classes.mod_logs import ACTIONTYPES, ACTIONTYPES2
from files.classes.badges import BadgeDef
import files.helpers.stats as statshelper
from shutil import move, copyfile
@app.get("/r/drama/comments/<id>/<title>") @app.get("/r/drama/comments/<id>/<title>")
@ -214,7 +218,7 @@ def submit_contact(v):
abort(403) abort(403)
body = f'This message has been sent automatically to all admins via [/contact](/contact)\n\nMessage:\n\n' + body body = f'This message has been sent automatically to all admins via [/contact](/contact)\n\nMessage:\n\n' + body
body += process_files() body += process_files(request.files, v)
body = body.strip() body = body.strip()
body_html = sanitize(body) body_html = sanitize(body)
@ -408,172 +412,3 @@ if not os.path.exists(f'files/templates/donate_{SITE_NAME}.html'):
@auth_desired_with_logingate @auth_desired_with_logingate
def donate(v): def donate(v):
return render_template(f'donate_{SITE_NAME}.html', v=v) return render_template(f'donate_{SITE_NAME}.html', v=v)
if SITE == 'pcmemes.net':
from files.classes.streamers import *
id_regex = re.compile('"externalId":"([^"]*?)"', flags=re.A)
live_regex = re.compile('playerOverlayVideoDetailsRenderer":\{"title":\{"simpleText":"(.*?)"\},"subtitle":\{"runs":\[\{"text":"(.*?)"\},\{"text":""\},\{"text":"(.*?)"\}', flags=re.A)
live_thumb_regex = re.compile('\{"thumbnail":\{"thumbnails":\[\{"url":"(.*?)"', flags=re.A)
offline_regex = re.compile('","title":"(.*?)".*?"width":48,"height":48\},\{"url":"(.*?)"', flags=re.A)
offline_details_regex = re.compile('simpleText":"Streamed ([0-9]*?) ([^"]*?)"\},.*?"viewCountText":\{"simpleText":"([0-9,]*?) views"', flags=re.A)
def process_streamer(id, live='live'):
url = f'https://www.youtube.com/channel/{id}/{live}'
req = requests.get(url, cookies={'CONSENT': 'YES+1'}, timeout=5)
text = req.text
if '"videoDetails":{"videoId"' in text:
y = live_regex.search(text)
count = y.group(3)
if count == '1 watching now':
count = "1"
if 'waiting' in count:
if live != '':
return process_streamer(id, '')
else:
return None
count = int(count.replace(',', ''))
t = live_thumb_regex.search(text)
thumb = t.group(1)
name = y.group(2)
title = y.group(1)
return (True, (id, req.url, thumb, name, title, count))
else:
t = offline_regex.search(text)
if not t:
if live != '':
return process_streamer(id, '')
else:
return None
y = offline_details_regex.search(text)
if y:
views = y.group(3).replace(',', '')
quantity = int(y.group(1))
unit = y.group(2)
if unit.startswith('second'):
modifier = 1/60
elif unit.startswith('minute'):
modifier = 1
elif unit.startswith('hour'):
modifier = 60
elif unit.startswith('day'):
modifier = 1440
elif unit.startswith('week'):
modifier = 10080
elif unit.startswith('month'):
modifier = 43800
elif unit.startswith('year'):
modifier = 525600
minutes = quantity * modifier
actual = f'{quantity} {unit}'
else:
minutes = 9999999999
actual = '???'
views = 0
thumb = t.group(2)
name = t.group(1)
return (False, (id, req.url.rstrip('/live'), thumb, name, minutes, actual, views))
def live_cached():
live = []
offline = []
db = db_session()
streamers = [x[0] for x in db.query(Streamer.id).all()]
db.close()
for id in streamers:
processed = process_streamer(id)
if processed:
if processed[0]: live.append(processed[1])
else: offline.append(processed[1])
live = sorted(live, key=lambda x: x[5], reverse=True)
offline = sorted(offline, key=lambda x: x[4])
if live: cache.set('live', live)
if offline: cache.set('offline', offline)
@app.get('/live')
@auth_desired_with_logingate
def live_list(v):
live = cache.get('live') or []
offline = cache.get('offline') or []
return render_template('live.html', v=v, live=live, offline=offline)
@app.post('/live/add')
@admin_level_required(PERMS['STREAMERS_MODERATION'])
def live_add(v):
link = request.values.get('link').strip()
if 'youtube.com/channel/' in link:
id = link.split('youtube.com/channel/')[1].rstrip('/')
else:
text = requests.get(link, cookies={'CONSENT': 'YES+1'}, timeout=5).text
try: id = id_regex.search(text).group(1)
except: abort(400, "Invalid ID")
live = cache.get('live') or []
offline = cache.get('offline') or []
if not id or len(id) != 24:
abort(400, "Invalid ID")
existing = g.db.get(Streamer, id)
if not existing:
streamer = Streamer(id=id)
g.db.add(streamer)
g.db.flush()
if v.id != KIPPY_ID:
send_repeatable_notification(KIPPY_ID, f"@{v.username} (Admin) has added a [new YouTube channel](https://www.youtube.com/channel/{streamer.id})")
processed = process_streamer(id)
if processed:
if processed[0]: live.append(processed[1])
else: offline.append(processed[1])
live = sorted(live, key=lambda x: x[5], reverse=True)
offline = sorted(offline, key=lambda x: x[4])
if live: cache.set('live', live)
if offline: cache.set('offline', offline)
return redirect('/live')
@app.post('/live/remove')
@admin_level_required(PERMS['STREAMERS_MODERATION'])
def live_remove(v):
id = request.values.get('id').strip()
if not id: abort(400)
streamer = g.db.get(Streamer, id)
if streamer:
if v.id != KIPPY_ID:
send_repeatable_notification(KIPPY_ID, f"@{v.username} (Admin) has removed a [YouTube channel](https://www.youtube.com/channel/{streamer.id})")
g.db.delete(streamer)
live = cache.get('live') or []
offline = cache.get('offline') or []
live = [x for x in live if x[0] != id]
offline = [x for x in offline if x[0] != id]
if live: cache.set('live', live)
if offline: cache.set('offline', offline)
return redirect('/live')

View File

@ -0,0 +1,174 @@
import re
import requests
from files.classes.streamers import Streamer
from files.helpers.alerts import send_repeatable_notification
from files.helpers.const import *
from files.routes.wrappers import *
from files.__main__ import app, cache
id_regex = re.compile('"externalId":"([^"]*?)"', flags=re.A)
live_regex = re.compile('playerOverlayVideoDetailsRenderer":\{"title":\{"simpleText":"(.*?)"\},"subtitle":\{"runs":\[\{"text":"(.*?)"\},\{"text":""\},\{"text":"(.*?)"\}', flags=re.A)
live_thumb_regex = re.compile('\{"thumbnail":\{"thumbnails":\[\{"url":"(.*?)"', flags=re.A)
offline_regex = re.compile('","title":"(.*?)".*?"width":48,"height":48\},\{"url":"(.*?)"', flags=re.A)
offline_details_regex = re.compile('simpleText":"Streamed ([0-9]*?) ([^"]*?)"\},.*?"viewCountText":\{"simpleText":"([0-9,]*?) views"', flags=re.A)
def process_streamer(id, live='live'):
url = f'https://www.youtube.com/channel/{id}/{live}'
req = requests.get(url, cookies={'CONSENT': 'YES+1'}, timeout=5)
text = req.text
if '"videoDetails":{"videoId"' in text:
y = live_regex.search(text)
count = y.group(3)
if count == '1 watching now':
count = "1"
if 'waiting' in count:
if live != '':
return process_streamer(id, '')
else:
return None
count = int(count.replace(',', ''))
t = live_thumb_regex.search(text)
thumb = t.group(1)
name = y.group(2)
title = y.group(1)
return (True, (id, req.url, thumb, name, title, count))
else:
t = offline_regex.search(text)
if not t:
if live != '':
return process_streamer(id, '')
else:
return None
y = offline_details_regex.search(text)
if y:
views = y.group(3).replace(',', '')
quantity = int(y.group(1))
unit = y.group(2)
if unit.startswith('second'):
modifier = 1/60
elif unit.startswith('minute'):
modifier = 1
elif unit.startswith('hour'):
modifier = 60
elif unit.startswith('day'):
modifier = 1440
elif unit.startswith('week'):
modifier = 10080
elif unit.startswith('month'):
modifier = 43800
elif unit.startswith('year'):
modifier = 525600
minutes = quantity * modifier
actual = f'{quantity} {unit}'
else:
minutes = 9999999999
actual = '???'
views = 0
thumb = t.group(2)
name = t.group(1)
return (False, (id, req.url.rstrip('/live'), thumb, name, minutes, actual, views))
def live_cached():
live = []
offline = []
db = db_session()
streamers = [x[0] for x in db.query(Streamer.id).all()]
db.close()
for id in streamers:
processed = process_streamer(id)
if processed:
if processed[0]: live.append(processed[1])
else: offline.append(processed[1])
live = sorted(live, key=lambda x: x[5], reverse=True)
offline = sorted(offline, key=lambda x: x[4])
if live: cache.set('live', live)
if offline: cache.set('offline', offline)
@app.get('/live')
@auth_desired_with_logingate
def live_list(v):
live = cache.get('live') or []
offline = cache.get('offline') or []
return render_template('live.html', v=v, live=live, offline=offline)
@app.post('/live/add')
@admin_level_required(PERMS['STREAMERS_MODERATION'])
def live_add(v):
link = request.values.get('link').strip()
if 'youtube.com/channel/' in link:
id = link.split('youtube.com/channel/')[1].rstrip('/')
else:
text = requests.get(link, cookies={'CONSENT': 'YES+1'}, timeout=5).text
try: id = id_regex.search(text).group(1)
except: abort(400, "Invalid ID")
live = cache.get('live') or []
offline = cache.get('offline') or []
if not id or len(id) != 24:
abort(400, "Invalid ID")
existing = g.db.get(Streamer, id)
if not existing:
streamer = Streamer(id=id)
g.db.add(streamer)
g.db.flush()
if v.id != KIPPY_ID:
send_repeatable_notification(KIPPY_ID, f"@{v.username} (Admin) has added a [new YouTube channel](https://www.youtube.com/channel/{streamer.id})")
processed = process_streamer(id)
if processed:
if processed[0]: live.append(processed[1])
else: offline.append(processed[1])
live = sorted(live, key=lambda x: x[5], reverse=True)
offline = sorted(offline, key=lambda x: x[4])
if live: cache.set('live', live)
if offline: cache.set('offline', offline)
return redirect('/live')
@app.post('/live/remove')
@admin_level_required(PERMS['STREAMERS_MODERATION'])
def live_remove(v):
id = request.values.get('id').strip()
if not id: abort(400)
streamer = g.db.get(Streamer, id)
if streamer:
if v.id != KIPPY_ID:
send_repeatable_notification(KIPPY_ID, f"@{v.username} (Admin) has removed a [YouTube channel](https://www.youtube.com/channel/{streamer.id})")
g.db.delete(streamer)
live = cache.get('live') or []
offline = cache.get('offline') or []
live = [x for x in live if x[0] != id]
offline = [x for x in offline if x[0] != id]
if live: cache.set('live', live)
if offline: cache.set('offline', offline)
return redirect('/live')

View File

@ -1,12 +1,14 @@
from files.__main__ import app, limiter from sqlalchemy import nullslast
from files.classes import *
from files.helpers.alerts import * from files.helpers.alerts import *
from files.helpers.wrappers import *
from files.helpers.get import * from files.helpers.get import *
from files.helpers.regex import * from files.helpers.regex import *
from files.classes import * from files.routes.wrappers import *
from .front import frontlist from .front import frontlist
from sqlalchemy import nullslast from files.__main__ import app, cache, limiter
import tldextract
@app.post("/exile/post/<pid>") @app.post("/exile/post/<pid>")
@is_not_permabanned @is_not_permabanned
@ -457,7 +459,7 @@ def get_sub_css(sub):
@limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}')
@is_not_permabanned @is_not_permabanned
def sub_banner(v, sub): def sub_banner(v, sub):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") if g.is_tor: abort(403, "Image uploads are not allowed through TOR.")
sub = get_sub_by_name(sub) sub = get_sub_by_name(sub)
if not v.mods(sub.name): abort(403) if not v.mods(sub.name): abort(403)
@ -467,7 +469,7 @@ def sub_banner(v, sub):
name = f'/images/{time.time()}'.replace('.','') + '.webp' name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name) file.save(name)
bannerurl = process_image(name, patron=v.patron, resize=1200) bannerurl = process_image(name, v, resize=1200)
if bannerurl: if bannerurl:
if sub.bannerurl and '/images/' in sub.bannerurl: if sub.bannerurl and '/images/' in sub.bannerurl:
@ -490,7 +492,7 @@ def sub_banner(v, sub):
@limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}')
@is_not_permabanned @is_not_permabanned
def sub_sidebar(v, sub): def sub_sidebar(v, sub):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") if g.is_tor: abort(403, "Image uploads are not allowed through TOR.")
sub = get_sub_by_name(sub) sub = get_sub_by_name(sub)
if not v.mods(sub.name): abort(403) if not v.mods(sub.name): abort(403)
@ -499,7 +501,7 @@ def sub_sidebar(v, sub):
file = request.files["sidebar"] file = request.files["sidebar"]
name = f'/images/{time.time()}'.replace('.','') + '.webp' name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name) file.save(name)
sidebarurl = process_image(name, patron=v.patron, resize=400) sidebarurl = process_image(name, v, resize=400)
if sidebarurl: if sidebarurl:
if sub.sidebarurl and '/images/' in sub.sidebarurl: if sub.sidebarurl and '/images/' in sub.sidebarurl:
@ -522,7 +524,7 @@ def sub_sidebar(v, sub):
@limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}') @limiter.limit("1/second;10/day", key_func=lambda:f'{SITE}-{session.get("lo_user")}')
@is_not_permabanned @is_not_permabanned
def sub_marsey(v, sub): def sub_marsey(v, sub):
if request.headers.get("cf-ipcountry") == "T1": abort(403, "Image uploads are not allowed through TOR.") if g.is_tor: abort(403, "Image uploads are not allowed through TOR.")
sub = get_sub_by_name(sub) sub = get_sub_by_name(sub)
if not v.mods(sub.name): abort(403) if not v.mods(sub.name): abort(403)
@ -531,7 +533,7 @@ def sub_marsey(v, sub):
file = request.files["marsey"] file = request.files["marsey"]
name = f'/images/{time.time()}'.replace('.','') + '.webp' name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name) file.save(name)
marseyurl = process_image(name, patron=v.patron, resize=200) marseyurl = process_image(name, v, resize=200)
if marseyurl: if marseyurl:
if sub.marseyurl and '/images/' in sub.marseyurl: if sub.marseyurl and '/images/' in sub.marseyurl:

View File

@ -1,28 +1,30 @@
from typing import Literal
import qrcode
import io import io
import time
import math
from files.classes.leaderboard import Leaderboard
from files.classes.views import *
from files.classes.transactions import *
from files.helpers.alerts import *
from files.helpers.sanitize import *
from files.helpers.const import *
from files.helpers.sorting_and_time import *
from files.helpers.actions import *
from files.mail import *
from flask import *
from files.__main__ import app, limiter, db_session
import sqlalchemy
from sqlalchemy.orm import aliased
from sqlalchemy import desc
from collections import Counter
import gevent
from sys import stdout
import os
import json import json
from .login import check_for_alts import math
import time
from collections import Counter
from typing import Literal
import gevent
import qrcode
from sqlalchemy.orm import aliased
from files.classes import *
from files.classes.leaderboard import Leaderboard
from files.classes.transactions import *
from files.classes.views import *
from files.helpers.actions import execute_blackjack
from files.helpers.alerts import *
from files.helpers.const import *
from files.helpers.mail import *
from files.helpers.sanitize import *
from files.helpers.sorting_and_time import *
from files.helpers.useractions import badge_grant
from files.routes.routehelpers import check_for_alts
from files.routes.wrappers import *
from files.__main__ import app, cache, limiter
def upvoters_downvoters(v, username, uid, cls, vote_cls, vote_dir, template, standalone): def upvoters_downvoters(v, username, uid, cls, vote_cls, vote_dir, template, standalone):
u = get_user(username, v=v, include_shadowbanned=False) u = get_user(username, v=v, include_shadowbanned=False)
@ -306,7 +308,7 @@ def transfer_currency(v:User, username:str, currency_name:Literal['coins', 'proc
else: else:
raise ValueError(f"Invalid currency '{currency_name}' got when transferring {amount} from {v.id} to {receiver.id}") raise ValueError(f"Invalid currency '{currency_name}' got when transferring {amount} from {v.id} to {receiver.id}")
g.db.add(receiver) g.db.add(receiver)
send_repeatable_notification(GIFT_NOTIF_ID, log_message) if GIFT_NOTIF_ID: send_repeatable_notification(GIFT_NOTIF_ID, log_message)
send_repeatable_notification(receiver.id, notif_text) send_repeatable_notification(receiver.id, notif_text)
g.db.add(v) g.db.add(v)
return {"message": f"{amount - tax} {friendly_currency_name} have been transferred to @{receiver.username}"} return {"message": f"{amount - tax} {friendly_currency_name} have been transferred to @{receiver.username}"}
@ -508,7 +510,7 @@ def messagereply(v):
abort(403, f"You're blocked by @{user.username}") abort(403, f"You're blocked by @{user.username}")
if parent.sentto == 2: if parent.sentto == 2:
body += process_files() body += process_files(request.files, v)
body = body.strip() body = body.strip()
@ -544,9 +546,9 @@ def messagereply(v):
gevent.spawn(pusher_thread, interests, title, notifbody, url) gevent.spawn(pusher_thread, interests, title, notifbody, url)
top_comment = c.top_comment(g.db)
if top_comment.sentto == 2:
if c.top_comment.sentto == 2:
admins = g.db.query(User.id).filter(User.admin_level >= PERMS['NOTIFICATIONS_MODMAIL'], User.id != v.id) admins = g.db.query(User.id).filter(User.admin_level >= PERMS['NOTIFICATIONS_MODMAIL'], User.id != v.id)
if SITE == 'watchpeopledie.tv': if SITE == 'watchpeopledie.tv':
admins = admins.filter(User.id != AEVANN_ID) admins = admins.filter(User.id != AEVANN_ID)
@ -560,7 +562,7 @@ def messagereply(v):
notif = Notification(comment_id=c.id, user_id=admin) notif = Notification(comment_id=c.id, user_id=admin)
g.db.add(notif) g.db.add(notif)
ids = [c.top_comment.id] + [x.id for x in c.top_comment.replies(sort="old", v=v)] ids = [top_comment.id] + [x.id for x in top_comment.replies(sort="old", v=v, db=g.db)]
notifications = g.db.query(Notification).filter(Notification.comment_id.in_(ids), Notification.user_id.in_(admins)) notifications = g.db.query(Notification).filter(Notification.comment_id.in_(ids), Notification.user_id.in_(admins))
for n in notifications: for n in notifications:
g.db.delete(n) g.db.delete(n)
@ -663,6 +665,16 @@ def visitors(v):
viewers=sorted(v.viewers, key = lambda x: x.last_view_utc, reverse=True) viewers=sorted(v.viewers, key = lambda x: x.last_view_utc, reverse=True)
return render_template("userpage/viewers.html", v=v, viewers=viewers) return render_template("userpage/viewers.html", v=v, viewers=viewers)
@cache.memoize(timeout=86400)
def userpagelisting(user:User, site=None, v=None, page:int=1, sort="new", t="all"):
if user.shadowbanned and not (v and v.can_see_shadowbanned): return []
posts = g.db.query(Submission.id).filter_by(author_id=user.id, is_pinned=False)
if not (v and (v.admin_level >= PERMS['POST_COMMENT_MODERATION'] or v.id == user.id)):
posts = posts.filter_by(is_banned=False, private=False, ghost=False, deleted_utc=0)
posts = apply_time_filter(t, posts, Submission)
posts = sort_objects(sort, posts, Submission, include_shadowbanned=v and v.can_see_shadowbanned)
posts = posts.offset(PAGE_SIZE * (page - 1)).limit(PAGE_SIZE+1).all()
return [x[0] for x in posts]
@app.get("/@<username>") @app.get("/@<username>")
@app.get("/@<username>.json") @app.get("/@<username>.json")
@ -701,7 +713,7 @@ def u_username(username, v=None):
try: page = max(int(request.values.get("page", 1)), 1) try: page = max(int(request.values.get("page", 1)), 1)
except: page = 1 except: page = 1
ids = u.userpagelisting(site=SITE, v=v, page=page, sort=sort, t=t) ids = userpagelisting(u, site=SITE, v=v, page=page, sort=sort, t=t)
next_exists = (len(ids) > PAGE_SIZE) next_exists = (len(ids) > PAGE_SIZE)
ids = ids[:PAGE_SIZE] ids = ids[:PAGE_SIZE]
@ -717,7 +729,7 @@ def u_username(username, v=None):
if u.unban_utc: if u.unban_utc:
if (v and v.client) or request.path.endswith(".json"): if (v and v.client) or request.path.endswith(".json"):
return {"data": [x.json for x in listing]} return {"data": [x.json(g.db) for x in listing]}
return render_template("userpage.html", return render_template("userpage.html",
unban=u.unban_string, unban=u.unban_string,
@ -731,7 +743,7 @@ def u_username(username, v=None):
is_following=is_following) is_following=is_following)
if (v and v.client) or request.path.endswith(".json"): if (v and v.client) or request.path.endswith(".json"):
return {"data": [x.json for x in listing]} return {"data": [x.json(g.db) for x in listing]}
return render_template("userpage.html", return render_template("userpage.html",
u=u, u=u,
@ -799,7 +811,7 @@ def u_username_comments(username, v=None):
listing = get_comments(ids, v=v) listing = get_comments(ids, v=v)
if (v and v.client) or request.path.endswith(".json"): if (v and v.client) or request.path.endswith(".json"):
return {"data": [c.json for c in listing]} return {"data": [c.json(g.db) for c in listing]}
return render_template("userpage/comments.html", u=u, v=v, listing=listing, page=page, sort=sort, t=t,next_exists=next_exists, is_following=is_following, standalone=True) return render_template("userpage/comments.html", u=u, v=v, listing=listing, page=page, sort=sort, t=t,next_exists=next_exists, is_following=is_following, standalone=True)
@ -1082,22 +1094,17 @@ kofi_tiers={
def settings_kofi(v): def settings_kofi(v):
if not (v.email and v.is_activated): if not (v.email and v.is_activated):
abort(400, f"You must have a verified email to verify {patron} status and claim your rewards!") abort(400, f"You must have a verified email to verify {patron} status and claim your rewards!")
transaction = g.db.query(Transaction).filter_by(email=v.email).order_by(Transaction.created_utc.desc()).first() transaction = g.db.query(Transaction).filter_by(email=v.email).order_by(Transaction.created_utc.desc()).first()
if not transaction: if not transaction:
abort(404, "Email not found") abort(404, "Email not found")
if transaction.claimed: if transaction.claimed:
abort(400, f"{patron} rewards already claimed") abort(400, f"{patron} rewards already claimed")
tier = kofi_tiers[transaction.amount] tier = kofi_tiers[transaction.amount]
procoins = procoins_li[tier] procoins = procoins_li[tier]
v.procoins += procoins v.procoins += procoins
send_repeatable_notification(v.id, f"You have received {procoins} Marseybux! You can use them to buy awards in the [shop](/shop).") send_repeatable_notification(v.id, f"You have received {procoins} Marseybux! You can use them to buy awards in the [shop](/shop).")
g.db.add(v) g.db.add(v)
if tier > v.patron: if tier > v.patron:
@ -1107,7 +1114,5 @@ def settings_kofi(v):
badge_grant(badge_id=20+tier, user=v) badge_grant(badge_id=20+tier, user=v)
transaction.claimed = True transaction.claimed = True
g.db.add(transaction) g.db.add(transaction)
return {"message": f"{patron} rewards claimed!"} return {"message": f"{patron} rewards claimed!"}

View File

@ -1,9 +1,9 @@
from files.helpers.wrappers import *
from files.helpers.get import *
from files.helpers.const import *
from files.classes import * from files.classes import *
from flask import * from files.helpers.const import *
from files.__main__ import app, limiter, cache from files.helpers.get import *
from files.routes.wrappers import *
from files.__main__ import app, limiter
@app.get("/votes/<link>") @app.get("/votes/<link>")
@admin_level_required(PERMS['VOTES_VISIBLE']) @admin_level_required(PERMS['VOTES_VISIBLE'])

View File

@ -1,14 +1,16 @@
from .get import *
from .alerts import *
from files.helpers.const import *
from files.helpers.get import *
from files.__main__ import db_session, limiter
from flask import g, request
from random import randint
import functools
import user_agents
import time import time
import user_agents
from flask import g, request, session
from files.classes.clients import ClientAuth
from files.helpers.alerts import *
from files.helpers.const import *
from files.helpers.get import get_account
from files.helpers.settings import get_setting
from files.routes.routehelpers import validate_formkey
from files.__main__ import app, cache, db_session, limiter
def calc_users(v): def calc_users(v):
loggedin = cache.get(f'{SITE}_loggedin') or {} loggedin = cache.get(f'{SITE}_loggedin') or {}
loggedout = cache.get(f'{SITE}_loggedout') or {} loggedout = cache.get(f'{SITE}_loggedout') or {}
@ -32,7 +34,7 @@ def calc_users(v):
def get_logged_in_user(): def get_logged_in_user():
if hasattr(g, 'v'): return g.v if hasattr(g, 'v'): return g.v
if not (hasattr(g, 'db') and g.db): g.db = db_session() if not getattr(g, 'db', None): g.db = db_session()
g.desires_auth = True g.desires_auth = True
v = None v = None
token = request.headers.get("Authorization","").strip() token = request.headers.get("Authorization","").strip()
@ -57,13 +59,12 @@ def get_logged_in_user():
if request.method != "GET": if request.method != "GET":
submitted_key = request.values.get("formkey") submitted_key = request.values.get("formkey")
if not submitted_key: abort(401) if not validate_formkey(v, submitted_key): abort(401)
if not v.validate_formkey(submitted_key): abort(401)
v.client = None v.client = None
g.is_api_or_xhr = bool((v and v.client) or request.headers.get("xhr")) g.is_api_or_xhr = bool((v and v.client) or request.headers.get("xhr"))
if request.method.lower() != "get" and app.config['SETTINGS']['Read-only mode'] and not (v and v.admin_level >= PERMS['SITE_BYPASS_READ_ONLY_MODE']): if request.method.lower() != "get" and get_setting('Read-only mode') and not (v and v.admin_level >= PERMS['SITE_BYPASS_READ_ONLY_MODE']):
abort(403) abort(403)
g.v = v g.v = v
@ -84,7 +85,6 @@ def get_logged_in_user():
if f'@{v.username}, ' not in f.read(): if f'@{v.username}, ' not in f.read():
t = str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(time.time()))) t = str(time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(time.time())))
f.write(f'@{v.username}, {v.truescore}, {ip}, {t}\n') f.write(f'@{v.username}, {v.truescore}, {ip}, {t}\n')
return v return v
def auth_desired(f): def auth_desired(f):
@ -97,7 +97,7 @@ def auth_desired(f):
def auth_desired_with_logingate(f): def auth_desired_with_logingate(f):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
v = get_logged_in_user() v = get_logged_in_user()
if app.config['SETTINGS']['login_required'] and not v: abort(401) if get_setting('login_required') and not v: abort(401)
if request.path.startswith('/logged_out'): if request.path.startswith('/logged_out'):
redir = request.full_path.replace('/logged_out','') redir = request.full_path.replace('/logged_out','')

View File

@ -19,7 +19,7 @@
<div class="body w-lg-100"> <div class="body w-lg-100">
<label for="edit-{{app.id}}-author" class="mb-0 w-lg-25">User</label> <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 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}}"> <input type="hidden" name="formkey" value="{{v|formkey}}">
<label for="edit-{{app.id}}-name" class="mb-0 w-lg-25">App Name</label> <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> <input autocomplete="off" id="edit-{{app.id}}-name" class="form-control" type="text" name="name" value="{{app.app_name}}" readonly=readonly>

View File

@ -40,7 +40,7 @@
{% endif %} {% endif %}
<form action="{{form_action}}" method="post"> <form action="{{form_action}}" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}"> <input type="hidden" name="formkey" value="{{v|formkey}}">
<label for="input-username">Username</label> <label for="input-username">Username</label>
<input autocomplete="off" id="input-username" class="form-control" type="text" name="username" required> <input autocomplete="off" id="input-username" class="form-control" type="text" name="username" required>

View File

@ -38,7 +38,7 @@
<form action="/admin/ban_domain" method="post"> <form action="/admin/ban_domain" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}"> <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="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 mt-2"> <input autocomplete="off" name="reason" placeholder="Enter ban reason here.." oninput="document.getElementById('ban-submit').disabled=false" class="form-control mt-2">
<input autocomplete="off" id="ban-submit" type="submit" onclick="disable(this)" class="btn btn-primary mt-2" value="Ban domain" disabled> <input autocomplete="off" id="ban-submit" type="submit" onclick="disable(this)" class="btn btn-primary mt-2" value="Ban domain" disabled>

View File

@ -11,7 +11,7 @@
<form id="banModalForm"> <form id="banModalForm">
<input type="hidden" name="formkey" value="{{v.formkey}}"> <input type="hidden" name="formkey" value="{{v|formkey}}">
<label for="ban-modal-link">Public ban reason (optional)</label> <label for="ban-modal-link">Public ban reason (optional)</label>
<textarea autocomplete="off" maxlength="256" name="reason" form="banModalForm" class="form-control" id="ban-modal-link" aria-label="With textarea" placeholder="Enter reason"></textarea> <textarea autocomplete="off" maxlength="256" name="reason" form="banModalForm" class="form-control" id="ban-modal-link" aria-label="With textarea" placeholder="Enter reason"></textarea>
@ -46,7 +46,7 @@
<div class="modal-body pt-0" id="chud-modal-body"> <div class="modal-body pt-0" id="chud-modal-body">
<form id="chudModalForm"> <form id="chudModalForm">
<input type="hidden" name="formkey" value="{{v.formkey}}"> <input type="hidden" name="formkey" value="{{v|formkey}}">
<input type="hidden" name="reason" id="chud-modal-link"> <input type="hidden" name="reason" id="chud-modal-link">
<label for="days">Days</label> <label for="days">Days</label>

View File

@ -21,7 +21,7 @@
{% set score=ups-downs %} {% set score=ups-downs %}
{% if render_replies %} {% if render_replies %}
{% set replies=c.replies(sort=sort, v=v) %} {% set replies=c.replies(sort=sort, v=v, db=g.db) %}
{% endif %} {% endif %}
{% if c.is_blocking and not c.ghost or (c.is_banned or c.deleted_utc) and not (v and v.admin_level >= PERMS['POST_COMMENT_MODERATION']) and not (v and v.id==c.author_id) %} {% if c.is_blocking and not c.ghost or (c.is_banned or c.deleted_utc) and not (v and v.admin_level >= PERMS['POST_COMMENT_MODERATION']) and not (v and v.id==c.author_id) %}
@ -271,7 +271,7 @@
{% if v and v.id==c.author_id %} {% if v and v.id==c.author_id %}
<div id="comment-edit-{{c.id}}" class="d-none comment-write collapsed child"> <div id="comment-edit-{{c.id}}" class="d-none comment-write collapsed child">
<form id="comment-edit-form-{{c.id}}" action="/edit_comment/{{c.id}}" method="post" enctype="multipart/form-data"> <form id="comment-edit-form-{{c.id}}" action="/edit_comment/{{c.id}}" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v.formkey}}"> <input type="hidden" name="formkey" value="{{v|formkey}}">
<textarea autocomplete="off" {% if v.longpost %}minlength="280"{% endif %} maxlength="{% if v.bird %}140{% else %}10000{% endif %}" data-preview="preview-edit-{{c.id}}" oninput="markdown(this);charLimit('comment-edit-body-{{c.id}}','charcount-edit-{{c.id}}')" id="comment-edit-body-{{c.id}}" data-id="{{c.id}}" name="body" form="comment-edit-form-{{c.id}}" class="comment-box form-control rounded" aria-label="With textarea" placeholder="Add your comment..." rows="3">{{c.body}}</textarea> <textarea autocomplete="off" {% if v.longpost %}minlength="280"{% endif %} maxlength="{% if v.bird %}140{% else %}10000{% endif %}" data-preview="preview-edit-{{c.id}}" oninput="markdown(this);charLimit('comment-edit-body-{{c.id}}','charcount-edit-{{c.id}}')" id="comment-edit-body-{{c.id}}" data-id="{{c.id}}" name="body" form="comment-edit-form-{{c.id}}" class="comment-box form-control rounded" aria-label="With textarea" placeholder="Add your comment..." rows="3">{{c.body}}</textarea>
<div class="text-small font-weight-bold mt-1" id="charcount-edit-{{c.id}}" style="right: 1rem; bottom: 0.5rem; z-index: 3;"></div> <div class="text-small font-weight-bold mt-1" id="charcount-edit-{{c.id}}" style="right: 1rem; bottom: 0.5rem; z-index: 3;"></div>
@ -284,7 +284,7 @@
<label class="btn btn-secondary format m-0" for="file-edit-reply-{{c.id}}"> <label class="btn btn-secondary format m-0" for="file-edit-reply-{{c.id}}">
<div id="filename-edit-reply-{{c.id}}"><i class="fas fa-file"></i></div> <div id="filename-edit-reply-{{c.id}}"><i class="fas fa-file"></i></div>
<input autocomplete="off" id="file-edit-reply-{{c.id}}" accept="image/*, video/*, audio/*" type="file" multiple="multiple" name="file" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename-edit-reply-{{c.id}}','file-edit-reply-{{c.id}}')" hidden> <input autocomplete="off" id="file-edit-reply-{{c.id}}" accept="image/*, video/*, audio/*" type="file" multiple="multiple" name="file" {% if g.is_tor %}disabled{% endif %} onchange="changename('filename-edit-reply-{{c.id}}','file-edit-reply-{{c.id}}')" hidden>
</label> </label>
</div> </div>
<button type="button" id="edit-btn-{{c.id}}" form="comment-edit-form-{{c.id}}" class="btn btn-primary ml-2 fl-r commentmob" onclick="comment_edit('{{c.id}}');remove_dialog()">Save Edit</button> <button type="button" id="edit-btn-{{c.id}}" form="comment-edit-form-{{c.id}}" class="btn btn-primary ml-2 fl-r commentmob" onclick="comment_edit('{{c.id}}');remove_dialog()">Save Edit</button>
@ -515,7 +515,7 @@
<div id="reply-to-{{c.id}}" class="d-none"> <div id="reply-to-{{c.id}}" class="d-none">
<div id="comment-form-space-{{c.fullname}}" class="comment-write collapsed child"> <div id="comment-form-space-{{c.fullname}}" class="comment-write collapsed child">
<form id="reply-to-c_{{c.id}}" action="/comment" method="post" enctype="multipart/form-data"> <form id="reply-to-c_{{c.id}}" action="/comment" method="post" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v.formkey}}"> <input type="hidden" name="formkey" value="{{v|formkey}}">
<input type="hidden" name="parent_fullname" value="{{c.fullname}}"> <input type="hidden" name="parent_fullname" value="{{c.fullname}}">
<input autocomplete="off" id="reply-form-submission-{{c.fullname}}" type="hidden" name="submission" value="{{c.post.id}}"> <input autocomplete="off" id="reply-form-submission-{{c.fullname}}" type="hidden" name="submission" value="{{c.post.id}}">
<textarea required autocomplete="off" {% if v.longpost %}minlength="280"{% else %}minlength="1"{% endif %} maxlength="{% if v.bird %}140{% else %}10000{% endif %}" data-preview="form-preview-{{c.fullname}}" oninput="markdown(this);charLimit('reply-form-body-{{c.fullname}}','charcount-{{c.id}}')" id="reply-form-body-{{c.fullname}}" data-fullname="{{c.fullname}}" name="body" form="reply-to-c_{{c.id}}" class="comment-box form-control rounded" aria-label="With textarea" placeholder="Add your comment..." rows="3"></textarea> <textarea required autocomplete="off" {% if v.longpost %}minlength="280"{% else %}minlength="1"{% endif %} maxlength="{% if v.bird %}140{% else %}10000{% endif %}" data-preview="form-preview-{{c.fullname}}" oninput="markdown(this);charLimit('reply-form-body-{{c.fullname}}','charcount-{{c.id}}')" id="reply-form-body-{{c.fullname}}" data-fullname="{{c.fullname}}" name="body" form="reply-to-c_{{c.id}}" class="comment-box form-control rounded" aria-label="With textarea" placeholder="Add your comment..." rows="3"></textarea>
@ -533,7 +533,7 @@
&nbsp; &nbsp;
<label class="btn btn-secondary format m-0" for="file-upload-reply-{{c.fullname}}"> <label class="btn btn-secondary format m-0" for="file-upload-reply-{{c.fullname}}">
<div id="filename-show-reply-{{c.fullname}}"><i class="fas fa-file"></i></div> <div id="filename-show-reply-{{c.fullname}}"><i class="fas fa-file"></i></div>
<input autocomplete="off" id="file-upload-reply-{{c.fullname}}" accept="image/*, video/*, audio/*" type="file" multiple="multiple" name="file" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename-show-reply-{{c.fullname}}','file-upload-reply-{{c.fullname}}')" hidden> <input autocomplete="off" id="file-upload-reply-{{c.fullname}}" accept="image/*, video/*, audio/*" type="file" multiple="multiple" name="file" {% if g.is_tor %}disabled{% endif %} onchange="changename('filename-show-reply-{{c.fullname}}','file-upload-reply-{{c.fullname}}')" hidden>
</label> </label>
</div> </div>
<button type="button" id="save-reply-to-{{c.fullname}}" class="btn btn-primary ml-2 fl-r commentmob" onclick="post_comment('{{c.fullname}}', 'reply-to-{{c.id}}');remove_dialog()">Comment</button> <button type="button" id="save-reply-to-{{c.fullname}}" class="btn btn-primary ml-2 fl-r commentmob" onclick="post_comment('{{c.fullname}}', 'reply-to-{{c.id}}');remove_dialog()">Comment</button>
@ -576,7 +576,7 @@
<div id="reply-message-{{c.id}}" class="d-none"> <div id="reply-message-{{c.id}}" class="d-none">
<div id="comment-form-space-{{c.id}}" class="comment-write collapsed child"> <div id="comment-form-space-{{c.id}}" class="comment-write collapsed child">
<form id="reply-to-message-{{c.id}}" action="/reply" method="post" class="input-group" enctype="multipart/form-data"> <form id="reply-to-message-{{c.id}}" action="/reply" method="post" class="input-group" enctype="multipart/form-data">
<input type="hidden" name="formkey" value="{{v.formkey}}"> <input type="hidden" name="formkey" value="{{v|formkey}}">
<textarea required autocomplete="off" minlength="1" maxlength="10000" name="body" form="reply-to-c_{{c.id}}" data-id="{{c.id}}" class="comment-box form-control rounded" id="reply-form-body-{{c.id}}" aria-label="With textarea" rows="3" data-preview="message-reply-{{c.id}}" oninput="markdown(this)"></textarea> <textarea required autocomplete="off" minlength="1" maxlength="10000" name="body" form="reply-to-c_{{c.id}}" data-id="{{c.id}}" class="comment-box form-control rounded" id="reply-form-body-{{c.id}}" aria-label="With textarea" rows="3" data-preview="message-reply-{{c.id}}" oninput="markdown(this)"></textarea>
<div class="comment-format" id="comment-format-bar-{{c.id}}"> <div class="comment-format" id="comment-format-bar-{{c.id}}">
<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> <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>
@ -584,7 +584,7 @@
{% if c.sentto == 2 %} {% if c.sentto == 2 %}
<label class="btn btn-secondary m-0 mt-3" for="file-upload"> <label class="btn btn-secondary m-0 mt-3" for="file-upload">
<div id="filename"><i class="fas fa-file"></i></div> <div id="filename"><i class="fas fa-file"></i></div>
<input autocomplete="off" id="file-upload" accept="image/*, video/*, audio/*" type="file" name="file" multiple="multiple" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename','file-upload')" hidden> <input autocomplete="off" id="file-upload" accept="image/*, video/*, audio/*" type="file" name="file" multiple="multiple" {% if g.is_tor %}disabled{% endif %} onchange="changename('filename','file-upload')" hidden>
</label> </label>
{% endif %} {% endif %}
</div> </div>

View File

@ -1,12 +1,8 @@
{% extends "default.html" %} {% extends "default.html" %}
{% block title %} {% block title %}
<title>{{SITE_NAME}} - Contact</title> <title>{{SITE_NAME}} - Contact</title>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if msg %} {% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert"> <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> <i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
@ -27,14 +23,14 @@
<form id="contactform" action="/send_admin" method="post" enctype="multipart/form-data"> <form id="contactform" action="/send_admin" method="post" enctype="multipart/form-data">
<label for="input-message" class="mt-3">Your message</label> <label for="input-message" class="mt-3">Your message</label>
<input type="hidden" name="formkey" value="{{v.formkey}}"> <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> <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"> <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> <i class="fas fa-smile-beam"></i>
</label> </label>
<label class="btn btn-secondary m-0 mt-3" for="file-upload"> <label class="btn btn-secondary m-0 mt-3" for="file-upload">
<div id="filename"><i class="fas fa-file"></i></div> <div id="filename"><i class="fas fa-file"></i></div>
<input autocomplete="off" id="file-upload" accept="image/*, video/*, audio/*" type="file" name="file" multiple="multiple" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename','file-upload')" hidden> <input autocomplete="off" id="file-upload" accept="image/*, video/*, audio/*" type="file" name="file" multiple="multiple" {% if g.is_tor %}disabled{% endif %} onchange="changename('filename','file-upload')" hidden>
</label> </label>
<input type="submit" onclick="disable(this)" value="Submit" class="btn btn-primary mt-3"> <input type="submit" onclick="disable(this)" value="Submit" class="btn btn-primary mt-3">
</form> </form>

View File

@ -117,7 +117,7 @@
{% if SITE == 'rdrama.net' %} {% if SITE == 'rdrama.net' %}
<td>{% include "user_in_table.html" %}</td> <td>{% include "user_in_table.html" %}</td>
{% endif %} {% endif %}
<td><a href="/hat_owners/{{hat.id}}">{{hat.number_sold}}</a></td> <td><a href="/hat_owners/{{hat.id}}">{{hat.number_sold(g.db)}}</a></td>
<td>{{hat.price}}</td> <td>{{hat.price}}</td>
<td class="shop-table-actions" style="width:unset"> <td class="shop-table-actions" style="width:unset">
{% if hat.id not in owned_hat_ids and hat.is_purchasable %} {% if hat.id not in owned_hat_ids and hat.is_purchasable %}

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