[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
gevent.monkey.patch_all()
from os import environ, path
import secrets
from files.helpers.cloudflare import CLOUDFLARE_AVAILABLE
from flask import *
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 faulthandler
from os import environ
from sys import argv, stdout
import gevent
import redis
import time
from sys import stdout, argv
import faulthandler
import json
import random
from flask import Flask
from flask_caching import Cache
from flask_compress import Compress
from flask_limiter import Limiter
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.url_map.strict_slashes = False
@ -25,11 +26,12 @@ app.jinja_env.auto_reload = True
app.jinja_env.add_extension('jinja2.ext.do')
faulthandler.enable()
SITE = environ.get("SITE").strip()
is_localhost = SITE == "localhost"
app.config['SERVER_NAME'] = SITE
app.config['SECRET_KEY'] = environ.get('SECRET_KEY').strip()
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['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
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_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)
def get_CF():
@ -54,78 +54,24 @@ def get_CF():
limiter = Limiter(
app,
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"],
storage_uri=environ.get("REDIS_URL", "redis://localhost")
)
Base = declarative_base()
engine = create_engine(app.config['SQLALCHEMY_DATABASE_URL'])
db_session = scoped_session(sessionmaker(bind=engine, autoflush=False))
const_initialize(db_session)
reload_settings()
start_watching_settings()
cache = Cache(app)
Compress(app)
if not path.isfile(f'/site_settings.json'):
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()
from files.routes.allroutes import *
@limiter.request_filter
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 .clients import *
from .comment import *
@ -10,7 +18,6 @@ from .submission import *
from .votes import *
from .domains import *
from .subscriptions import *
from files.__main__ import app
from .mod_logs import *
from .award import *
from .sub_block import *
@ -25,6 +32,7 @@ from .casino_game import *
from .hats import *
from .marsey import *
from .transactions import *
from .streamers import *
from .sub_logs 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
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Alt(Base):
__tablename__ = "alts"
@ -16,5 +19,4 @@ class Alt(Base):
super().__init__(*args, **kwargs)
def __repr__(self):
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
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"
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
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):
__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 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']

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

View File

@ -1,26 +1,23 @@
import re
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 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):
if not v: return body
if v.reddit != 'old.reddit.com':
body = reddit_to_vreddit_regex.sub(rf'\1https://{v.reddit}/\2/', body)
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/')
if v.imginn:
body = body.replace('https://instagram.com/', 'https://imginn.com/')
return body
class Comment(Base):
__tablename__ = "comments"
id = Column(Integer, primary_key=True)
@ -99,12 +94,9 @@ class Comment(Base):
if v.id == self.post.author_id: return True
return False
@property
@lazy
def top_comment(self):
return g.db.get(Comment, self.top_comment_id)
def top_comment(self, db:scoped_session):
return db.get(Comment, self.top_comment_id)
@property
@lazy
@ -115,7 +107,7 @@ class Comment(Base):
@property
@lazy
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
@lazy
@ -142,15 +134,11 @@ class Comment(Base):
def fullname(self):
return f"c_{self.id}"
@property
@lazy
def parent(self):
def parent(self, db:scoped_session):
if not self.parent_submission: return None
if self.level == 1: return self.post
else: return g.db.get(Comment, self.parent_comment_id)
else: return db.get(Comment, self.parent_comment_id)
@property
@lazy
@ -159,14 +147,12 @@ class Comment(Base):
elif self.parent_submission: return f"p_{self.parent_submission}"
@lazy
def replies(self, sort, v):
def replies(self, sort, v, db:scoped_session):
if self.replies2 != None:
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'
return sort_objects(sort, replies, Comment,
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
return len([x for x in self.awards if x.kind == kind])
@property
def json(self):
def json(self, db:scoped_session):
if self.is_banned:
data = {'is_banned': True,
'ban_reason': self.ban_reason,
@ -253,7 +238,7 @@ class Comment(Base):
'is_bot': self.is_bot,
'flags': flags,
'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
@ -278,10 +263,7 @@ class Comment(Base):
if body:
body = censor_slurs(body, v)
body = normalize_urls_runtime(body, v)
if not v or v.controversial:
captured = []
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)}<')
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:
curr = [x for x in self.options if x.exclusive and x.voted(v)]
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
class BannedDomain(Base):
from sqlalchemy import Column
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class BannedDomain(Base):
__tablename__ = "banneddomains"
domain = Column(String, primary_key=True)
reason = Column(String)

View File

@ -1,10 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
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"
user_id = Column(Integer, ForeignKey("users.id"), 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
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"
post_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
@ -30,7 +31,6 @@ class Flag(Base):
class CommentFlag(Base):
__tablename__ = "commentflags"
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
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class Follow(Base):
__tablename__ = "follows"
target_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

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

View File

@ -1,5 +1,6 @@
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 files.helpers.const import LEADERBOARD_LIMIT

View File

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

View File

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

View File

@ -1,9 +1,10 @@
from sqlalchemy import *
from files.__main__ import Base
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"
kind = 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
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"
user_id = Column(Integer, ForeignKey("users.id"), 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
from files.helpers.lazy import lazy
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.lazy import lazy
from files.helpers.regex import censor_slurs
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
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"
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
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"
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
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"
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 *
from files.__main__ import Base
import time
from sqlalchemy import Column
from sqlalchemy.sql.sqltypes import *
class Streamer(Base):
from files.classes import Base
__tablename__ = "streamers"
id = Column(String, primary_key=True)
created_utc = Column(Integer)
class Streamer(Base):
__tablename__ = "streamers"
id = Column(String, primary_key=True)
created_utc = Column(Integer)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Streamer(id={self.id})>"
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<Streamer(id={self.id})>"

View File

@ -1,11 +1,15 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
from files.helpers.lazy import lazy
import time
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_subscription import *
import time
SITE_NAME = environ.get("SITE_NAME", '').strip()
SITE = environ.get("SITE", '').strip()

View File

@ -1,7 +1,10 @@
from sqlalchemy import *
from files.__main__ import Base
import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class SubBlock(Base):
__tablename__ = "sub_blocks"
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
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class SubJoin(Base):
__tablename__ = "sub_joins"
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
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.lazy import lazy
from files.helpers.regex import censor_slurs
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
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class SubSubscription(Base):
__tablename__ = "sub_subscriptions"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

@ -1,23 +1,21 @@
import random
import re
import time
from urllib.parse import urlparse
from flask import render_template
from sqlalchemy import *
from sqlalchemy.orm import relationship, deferred
from files.__main__ import Base
from sqlalchemy import Column, FetchedValue, ForeignKey
from sqlalchemy.orm import deferred, relationship, scoped_session
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
from files.helpers.const import *
from files.helpers.regex import *
from files.helpers.lazy import lazy
from files.helpers.regex import *
from files.helpers.sorting_and_time import make_age_string
from .flags import Flag
from .comment import Comment, normalize_urls_runtime
from .saves import SaveRelationship
from .comment import normalize_urls_runtime
from .polls import *
from .sub import *
from .subscriptions import *
from .votes import CommentVote
from .polls import *
from flask import g
class Submission(Base):
__tablename__ = "submissions"
@ -175,10 +173,8 @@ class Submission(Base):
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"
@property
@lazy
def json(self):
def json(self, db:scoped_session):
if self.is_banned:
return {'is_banned': True,
'deleted_utc': self.deleted_utc,
@ -196,7 +192,6 @@ class Submission(Base):
'permalink': self.permalink,
}
flags = {}
for f in self.flags: flags[f.user.username] = f.reason
@ -232,7 +227,7 @@ class Submission(Base):
}
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
@ -293,21 +288,8 @@ class Submission(Base):
body = self.body_html or ""
body = censor_slurs(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:
curr = [x for x in self.options if x.exclusive and x.voted(v)]
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>"
body = self.body
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 = normalize_urls_runtime(body, v)
return body

View File

@ -1,8 +1,11 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
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):
__tablename__ = "subscriptions"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)

View File

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

View File

@ -1,34 +1,38 @@
from sqlalchemy.orm import deferred, aliased
from sqlalchemy.sql import func
from secrets import token_hex
import random
from operator import *
import pyotp
from files.classes.sub import Sub
from files.helpers.media import *
from files.helpers.const import *
from sqlalchemy import Column, ForeignKey
from sqlalchemy.orm import aliased, deferred
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.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 .alts import Alt
from .saves import *
from .notifications import Notification
from .award import AwardRelationship
from .follows import *
from .subscriptions import *
from .userblock import *
from .badges import *
from .clients import *
from .mod_logs import *
from .sub_logs import *
from .mod import *
from .exiles import *
from .sub_block import *
from .sub_subscription import *
from .sub_join import *
from .follows import *
from .hats import *
from files.__main__ import Base, cache
from files.helpers.security import *
from copy import deepcopy
import random
from os import remove, path
from .mod import *
from .mod_logs import *
from .notifications import Notification
from .saves import *
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):
__tablename__ = "users"
@ -472,24 +476,6 @@ class User(Base):
if u.patron: return True
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
@lazy
def follow_count(self):
@ -522,18 +508,6 @@ class User(Base):
def verifyPass(self, 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
@lazy
def url(self):

View File

@ -1,10 +1,12 @@
from sqlalchemy import *
from sqlalchemy.orm import relationship
from files.__main__ import Base
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"
user_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
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
class ViewerRelationship(Base):
__tablename__ = "viewers"
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
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"
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 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.const import *
from files.helpers.const_stateful import *
from files.helpers.get import *
from files.helpers.sanitize import *
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)'}
SNAPPY_MARSEYS = []
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):
def _archiveorg(url):
try: requests.get(f'https://web.archive.org/save/{url}', headers=headers, timeout=10, proxies=proxies)
except: pass
requests.post('https://ghostarchive.org/archive2', data={"archive": url}, headers=headers, timeout=10, proxies=proxies)
def archive_url(url):
gevent.spawn(archiveorg, url)
def archive_url(url):
gevent.spawn(_archiveorg, url)
if url.startswith('https://twitter.com/'):
url = url.replace('https://twitter.com/', 'https://nitter.lacontrevoie.fr/')
gevent.spawn(archiveorg, url)
gevent.spawn(_archiveorg, url)
if url.startswith('https://instagram.com/'):
url = url.replace('https://instagram.com/', 'https://imginn.com/')
gevent.spawn(archiveorg, url)
gevent.spawn(_archiveorg, url)
def execute_snappy(post, v):

View File

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

View File

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

View File

@ -1,9 +1,10 @@
from flask import g
import time
from files.helpers.alerts import send_repeatable_notification
from files.helpers.const import *
from files.classes.badges import Badge
from flask import g
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):
now = time.time()

View File

@ -1,20 +1,16 @@
from files.__main__ import app, limiter, db_session
from files.helpers.wrappers import *
from files.helpers.useractions import badge_grant
import time
from files.classes.casino_game import Casino_Game
from files.helpers.alerts import *
from files.helpers.get import *
from files.helpers.const import *
from files.helpers.wrappers import *
from files.helpers.useractions import badge_grant
def get_game_feed(game):
games = g.db.query(Casino_Game) \
def get_game_feed(game, db):
games = db.query(Casino_Game) \
.filter(Casino_Game.active == False, Casino_Game.kind == game) \
.order_by(Casino_Game.created_utc.desc()).limit(30).all()
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'
relevant_currency = "coin" if game.currency == "coins" else "marseybux"
@ -28,20 +24,20 @@ def get_game_feed(game):
return list(map(format_game, games))
def get_game_leaderboard(game):
def get_game_leaderboard(game, db):
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()
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()
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()
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()
if not biggest_win_all_time:

View File

@ -1,13 +1,14 @@
import json
from typing import List, Union, Optional
from files.helpers.const import CF_HEADERS, CF_ZONE
from typing import List, Optional, Union
import requests
from files.helpers.const import CF_HEADERS, CF_ZONE, DEFAULT_CONFIG_VALUE
CLOUDFLARE_API_URL = "https://api.cloudflare.com/client/v4"
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:
if not CLOUDFLARE_AVAILABLE: return False

View File

@ -1,44 +1,41 @@
from os import environ
import re
from copy import deepcopy
from json import loads
from flask import request
from os import environ, path
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()
blackjack = environ.get("BLACKJACK", "").strip()
FP = environ.get("FP", "").strip()
@ -46,12 +43,14 @@ KOFI_TOKEN = environ.get("KOFI_TOKEN", "").strip()
KOFI_LINK = environ.get("KOFI_LINK", "").strip()
PUSHER_ID_CSP = ""
if PUSHER_ID != "blahblahblah":
if PUSHER_ID != DEFAULT_CONFIG_VALUE:
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_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_SLOWER = "1/second;30/minute;200/hour;1000/day"
@ -67,6 +66,8 @@ if SITE_NAME == 'PCM': CC = "SPLASH MOUNTAIN"
else: CC = "COUNTRY CLUB"
CC_TITLE = CC.title()
CASINO_RELEASE_DAY = 1662825600
if SITE_NAME == 'rDrama': patron = 'Paypig'
else: patron = 'Patron'
@ -298,6 +299,8 @@ FEATURES = {
'MARKUP_COMMANDS': True,
'REPOST_DETECTION': True,
'PATRON_ICONS': False,
'ASSET_SUBMISSIONS': False,
'STREAMERS': False,
}
WERKZEUG_ERROR_DESCRIPTIONS = {
@ -468,6 +471,7 @@ if SITE == 'rdrama.net':
FEATURES['PRONOUNS'] = True
FEATURES['HOUSES'] = True
FEATURES['USERS_PERMANENT_WORD_FILTERS'] = True
FEATURES['ASSET_SUBMISSIONS'] = True
PERMS['ADMIN_ADD'] = 4
SIDEBAR_THREAD = 37696
@ -520,6 +524,7 @@ if SITE == 'rdrama.net':
elif SITE == 'pcmemes.net':
PIN_LIMIT = 10
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_MARSEYS[500] = "wholesome"
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
FEATURES['ASSET_SUBMISSIONS'] = True
FEATURES['PRONOUNS'] = True
FEATURES['HOUSES'] = True
FEATURES['USERS_PERMANENT_WORD_FILTERS'] = True
FEATURES['STREAMERS'] = True
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']
VIDEO_FORMATS = ['mp4','webm','mov','avi','mkv','flv','m4v','3gp']
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 time
from sys import stdout
import click
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.')
@click.option('--every-5m', is_flag=True, help='Call every 5 minutes.')
@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 FEATURES['GAMBLING']:
lottery.check_if_end_lottery_task()
check_if_end_lottery_task()
spin_roulette_wheel()
offsitementions.offsite_mentions_task(cache)
if SITE == 'pcmemes.net':
route_static.live_cached()
if FEATURES['STREAMERS']:
route_streamers.live_cached()
if every_1h:
awards.award_timers_bots_task()
if every_1d:
stats.generate_charts_task(SITE)
sub_inactive_purge_task()
_sub_inactive_purge_task()
cache.delete_memoized(route_static.stats_cached)
route_static.stats_cached()
if every_1mo:
if KOFI_LINK: give_monthly_marseybux_task_kofi()
else: give_monthly_marseybux_task()
if KOFI_LINK: _give_monthly_marseybux_task_kofi()
else: _give_monthly_marseybux_task()
g.db.commit()
g.db.close()
stdout.flush()
def sub_inactive_purge_task():
def _sub_inactive_purge_task():
if not HOLE_INACTIVITY_DELETION:
return False
@ -108,7 +109,7 @@ def sub_inactive_purge_task():
return True
def give_monthly_marseybux_task():
def _give_monthly_marseybux_task():
month = datetime.datetime.now() + datetime.timedelta(days=5)
month = month.strftime('%B')
@ -157,7 +158,7 @@ def give_monthly_marseybux_task():
return True
def give_monthly_marseybux_task_kofi():
def _give_monthly_marseybux_task_kofi():
month = datetime.datetime.now() + datetime.timedelta(days=5)
month = month.strftime('%B')

View File

@ -1,7 +1,11 @@
from typing import Callable, Iterable, List, Optional, Union
from flask import *
from sqlalchemy import and_, any_, or_
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:
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):
'''
Prevents certain properties from having to be recomputed each time they are referenced
'''
def wrapper(*args, **kwargs):
o = args[0]
if "_lazy" not in o.__dict__:
o.__dict__["_lazy"] = {}
name = f.__name__ + str(args) + str(kwargs),
if name not in o.__dict__["_lazy"]:
o.__dict__["_lazy"][name] = f(*args, **kwargs)
return o.__dict__["_lazy"][name]
wrapper.__name__ = f.__name__
return wrapper

View File

@ -1,10 +1,13 @@
import time
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 sqlalchemy import *
from files.classes.lottery import Lottery
from files.helpers.alerts import *
from files.helpers.useractions import *
from .const import *
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 .const_stateful import marsey_mappings
def marsify(text):
new_text = ''
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
from flask import abort, g
import requests
import subprocess
import time
from .const import *
from shutil import copyfile
from typing import Optional
import gevent
import imagehash
from shutil import copyfile
from flask import abort, g, has_request_context
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.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 = ''
if request.files.get("file") and request.headers.get("cf-ipcountry") != "T1":
files = request.files.getlist('file')[:4]
for file in files:
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
url = process_image(name, patron=g.v.patron)
body += f"\n\n![]({url})"
elif file.content_type.startswith('video/'):
body += f"\n\n{SITE_FULL}{process_video(file)}"
elif file.content_type.startswith('audio/'):
body += f"\n\n{SITE_FULL}{process_audio(file)}"
else:
abort(415)
if g.is_tor or not files.get("file"): return body
files = files.getlist('file')[:4]
for file in files:
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
url = process_image(name, v)
body += f"\n\n![]({url})"
elif file.content_type.startswith('video/'):
body += f"\n\n{SITE_FULL}{process_video(file, v)}"
elif file.content_type.startswith('audio/'):
body += f"\n\n{SITE_FULL}{process_audio(file, v)}"
else:
abort(415)
return body
def process_audio(file):
def process_audio(file, v):
name = f'/audio/{time.time()}'.replace('.','')
name_original = secure_filename(file.filename)
extension = name_original.split('.')[-1].lower()
name = name + '.' + extension
file.save(name)
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)
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(
kind='audio',
filename=name,
user_id=g.v.id,
user_id=v.id,
size=size
)
g.db.add(media)
@ -62,13 +64,12 @@ def process_audio(file):
return name
def webm_to_mp4(old, new, vid):
def webm_to_mp4(old, new, vid, db):
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)
os.replace(tmp, new)
os.remove(old)
purge_files_in_cache(f"{SITE_FULL}{new}")
db = db_session()
media = db.query(Media).filter_by(filename=new, kind='video').one_or_none()
if media: db.delete(media)
@ -84,14 +85,14 @@ def webm_to_mp4(old, new, vid):
db.close()
def process_video(file):
def process_video(file, v):
old = f'/videos/{time.time()}'.replace('.','')
file.save(old)
size = os.stat(old).st_size
if (SITE_NAME != 'WPD' and
(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)
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':
new = new.replace('.webm', '.mp4')
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:
subprocess.run(["ffmpeg", "-y", "-loglevel", "warning", "-nostats", "-i", old, "-map_metadata", "-1", "-c:v", "copy", "-c:a", "copy", new], check=True)
os.remove(old)
@ -113,7 +115,7 @@ def process_video(file):
media = Media(
kind='video',
filename=new,
user_id=g.v.id,
user_id=v.id,
size=os.stat(new).st_size
)
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
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:
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:
params = ["convert", "-coalesce", filename, "-quality", "88", "-define", "webp:method=5", "-strip", "-auto-orient"]
if trim and len(list(Iterator(i))) == 1:
params.append("-trim")
if resize and i.width > resize:
params.extend(["-resize", f"{resize}>"])
try:
with Image.open(filename) as i:
params = ["convert", "-coalesce", filename, "-quality", "88", "-define", "webp:method=5", "-strip", "-auto-orient"]
if trim and len(list(Iterator(i))) == 1:
params.append("-trim")
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)
try:
subprocess.run(params, timeout=MAX_IMAGE_CONVERSION_TIMEOUT)
except subprocess.TimeoutExpired:
abort(413, ("An uploaded image took too long to convert to WEBP. "
"Consider uploading elsewhere."))
if has_request:
abort(413, ("An uploaded image took too long to convert to WEBP. "
"Consider uploading elsewhere."))
return None
if resize:
if os.stat(filename).st_size > MAX_IMAGE_SIZE_BANNER_RESIZED_KB * 1024:
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/'):
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():
os.remove(filename)
abort(409, "Image already exists!")
if has_request:
abort(409, "Image already exists!")
return None
db = db or g.db
@ -183,7 +207,7 @@ def process_image(filename=None, resize=0, trim=False, uploader=None, patron=Fal
media = Media(
kind='image',
filename=filename,
user_id=uploader or g.v.id,
user_id=uploader_id or v.id,
size=os.stat(filename).st_size
)
db.add(media)

View File

@ -1,15 +1,17 @@
import time
from typing import Iterable
import itertools
import requests
from flask_caching import Cache
from flask import g
import itertools
import requests
from sqlalchemy import or_
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.comment import Comment
from files.classes.notifications import Notification
from files.classes.user import User
from files.helpers.sanitize import sanitize
# 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.presets import *
from owoify.structures.word import Word
import re
import files.helpers.regex as help_re
import files.helpers.sanitize as sanitize

View File

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

View File

@ -1,11 +1,13 @@
import json
from random import randint
from enum import Enum
from files.helpers.alerts import *
from files.classes.casino_game import Casino_Game
from files.helpers.get import get_account
from random import randint
import time
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):
STRAIGHT_UP_BET = "STRAIGHT_UP_BET"

View File

@ -1,31 +1,22 @@
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
from bs4 import BeautifulSoup
from bleach.css_sanitizer import CSSSanitizer
from bleach.linkifier import LinkifyFilter
from functools import partial
from .get import *
from os import path
import re
from bs4 import BeautifulSoup
from mistletoe import markdown
from random import random, choice
import signal
from files.__main__ import db_session
from files.classes.marsey import Marsey
from files.classes.domains import BannedDomain
db = db_session()
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()
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()
from files.helpers.const import *
from files.helpers.const_stateful import *
from files.helpers.regex import *
from .get import *
TLDS = ( # Original gTLDs and ccTLDs
'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 = ''
if b: attrs += ' b'
if golden and len(emojis) <= 20 and ('marsey' in emoji or emoji in marseys_const2):
if random() < 0.0025: attrs += ' g'
elif random() < 0.00125: attrs += ' glow'
if random.random() < 0.0025: attrs += ' g'
elif random.random() < 0.00125: attrs += ' glow'
old = emoji
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 = '<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:
sanitized = torture_ap(sanitized, g.v.username)
emoji = choice(['trumpjaktalking', 'reposthorse'])
emoji = random.choice(['trumpjaktalking', 'reposthorse'])
sanitized += f'\n:#{emoji}:'
sanitized = normalize_url(sanitized)

View File

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

View File

@ -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
from json.encoder import INFINITY
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.helpers.casino import distribute_wager_badges
from flask import g, abort
from files.classes.comment import Comment
from files.classes.user import User
from files.helpers.casino import distribute_wager_badges
from .const import *
minimum_bet = 5
maximum_bet = INFINITY

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,29 @@
# import classes then...
from files.classes.sub import Sub
# import constants then...
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 .comments import *
from .errors import *
from .reporting import *
from .front import *
from .login import *
from .mail import *
from .oauth import *
from .posts import *
from .search import *
@ -24,4 +40,7 @@ from .casino import *
from .polls import *
from .notifications 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 re
from os import remove
from PIL import Image as IMAGE
from urllib.parse import quote, urlencode
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.sanitize import *
from files.helpers.security import *
from files.helpers.cloudflare import *
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.media import *
from files.helpers.const import *
from files.helpers.actions import *
from files.helpers.sanitize import *
from files.helpers.security import *
from files.helpers.settings import toggle_setting
from files.helpers.useractions import *
import files.helpers.cloudflare as cloudflare
from files.classes import *
from flask import *
from files.__main__ import app, cache, limiter
from files.routes.routehelpers import check_for_alts
from files.routes.wrappers import *
from .front import frontlist
from .login import check_for_alts
import datetime
import requests
from urllib.parse import quote, urlencode
@app.post('/kippy')
@admin_level_required(PERMS['PRINT_MARSEYBUX_FOR_KIPPY_ON_PCMEMES'])
@ -431,7 +430,7 @@ def admin_home(v):
under_attack = False
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()
@ -458,27 +457,20 @@ def admin_git_head():
@app.post("/admin/site_settings/<setting>")
@admin_level_required(PERMS['SITE_SETTINGS'])
def change_settings(v, setting):
site_settings = app.config['SETTINGS']
site_settings[setting] = not site_settings[setting]
with open("/site_settings.json", "w", encoding='utf_8') as f:
json.dump(site_settings, f)
if site_settings[setting]: word = 'enable'
val = toggle_setting(setting)
if val: word = 'enable'
else: word = 'disable'
ma = ModAction(
kind=f"{word}_{setting}",
user_id=v.id,
)
g.db.add(ma)
return {'message': f"{setting} {word}d successfully!"}
@app.post("/admin/clear_cloudflare_cache")
@admin_level_required(PERMS['SITE_CACHE_PURGE_CDN'])
def clear_cloudflare_cache(v):
if not cloudflare.clear_entire_cache():
if not clear_entire_cache():
abort(400, 'Failed to clear cloudflare cache!')
ma = ModAction(
kind="clear_cloudflare_cache",
@ -503,13 +495,13 @@ def admin_clear_internal_cache(v):
@app.post("/admin/under_attack")
@admin_level_required(PERMS['SITE_SETTINGS_UNDER_ATTACK'])
def under_attack(v):
response = cloudflare.get_security_level()
response = get_security_level()
if not response:
abort(400, 'Could not retrieve the current security level')
old_under_attack_mode = response == 'under_attack'
enable_disable_str = 'disable' if old_under_attack_mode else 'enable'
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')
ma = ModAction(
kind=f"{enable_disable_str}_under_attack",
@ -760,7 +752,7 @@ def admin_add_alt(v, username):
a = Alt(
user1=user1.id,
user2=user2.id,
manual=True,
is_manual=True,
deleted=deleted
)
g.db.add(a)
@ -1200,7 +1192,7 @@ def remove_post(post_id, v):
v.coins += 1
g.db.add(v)
cloudflare.purge_files_in_cache(f"https://{SITE}/")
purge_files_in_cache(f"https://{SITE}/")
return {"message": "Post removed!"}
@ -1208,7 +1200,6 @@ def remove_post(post_id, v):
@limiter.limit(DEFAULT_RATELIMIT_SLOWER)
@admin_level_required(PERMS['POST_COMMENT_MODERATION'])
def approve_post(post_id, v):
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':

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 rename, path
from typing import Union
from os import path, rename
from shutil import copyfile, move
from files.__main__ import app, limiter
from files.helpers.const import *
from files.helpers.useractions import *
from files.helpers.media import *
from files.helpers.get import *
from files.helpers.wrappers import *
from files.classes.marsey import Marsey
from files.classes.hats import Hat, HatDef
from files.classes.mod_logs import ModAction
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.wrappers import *
from files.__main__ import app, cache, limiter
if SITE not in ('pcmemes.net', 'watchpeopledie.tv'):
ASSET_TYPES = (Marsey, HatDef)
CAN_APPROVE_ASSETS = (AEVANN_ID, CARP_ID, SNAKES_ID)
CAN_UPDATE_ASSETS = (AEVANN_ID, CARP_ID, SNAKES_ID, GEESE_ID, JUSTCOOL_ID)
ASSET_TYPES = (Marsey, HatDef)
CAN_APPROVE_ASSETS = (AEVANN_ID, CARP_ID, SNAKES_ID)
CAN_UPDATE_ASSETS = (AEVANN_ID, CARP_ID, SNAKES_ID, GEESE_ID, JUSTCOOL_ID)
@app.get('/asset_submissions/<path:path>')
@limiter.exempt
def asset_submissions(path):
resp = make_response(send_from_directory('/asset_submissions', path))
resp.headers.remove("Cache-Control")
resp.headers.add("Cache-Control", "public, max-age=3153600")
resp.headers.remove("Content-Type")
resp.headers.add("Content-Type", "image/webp")
return resp
@app.get('/asset_submissions/<path:path>')
@limiter.exempt
def asset_submissions(path):
resp = make_response(send_from_directory('/asset_submissions', path))
resp.headers.remove("Cache-Control")
resp.headers.add("Cache-Control", "public, max-age=3153600")
resp.headers.remove("Content-Type")
resp.headers.add("Content-Type", "image/webp")
return resp
@app.get("/submit/marseys")
@auth_required
def submit_marseys(v):
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()
@app.get("/submit/marseys")
@auth_required
def submit_marseys(v):
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]
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)
return render_template("submit_marseys.html", v=v, marseys=marseys)
@app.post("/submit/marseys")
@auth_required
def submit_marsey(v):
file = request.files["image"]
name = request.values.get('name', '').lower().strip()
tags = request.values.get('tags', '').lower().strip()
username = request.values.get('author', '').lower().strip()
@app.post("/submit/marseys")
@auth_required
def submit_marsey(v):
file = request.files["image"]
name = request.values.get('name', '').lower().strip()
tags = request.values.get('tags', '').lower().strip()
username = request.values.get('author', '').lower().strip()
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()
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
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 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
if not file or not file.content_type.startswith('image/'):
return error("You need to submit an image!")
@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!")
if not marsey_regex.fullmatch(name):
return error("Invalid name!")
new_name = request.values.get('name').lower().strip()
if not new_name:
abort(400, "You need to include 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, 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):
abort(400, "Invalid name!")
if not tags_regex.fullmatch(tags):
abort(400, "Invalid tags!")
if not marsey_regex.fullmatch(new_name):
abort(400, "Invalid name!")
if not tags_regex.fullmatch(tags):
abort(400, "Invalid tags!")
marsey.name = new_name
marsey.tags = tags
g.db.add(marsey)
marsey.name = new_name
marsey.tags = tags
g.db.add(marsey)
author = get_account(marsey.author_id)
all_by_author = g.db.query(Marsey).filter_by(author_id=author.id).count()
author = get_account(marsey.author_id)
all_by_author = g.db.query(Marsey).filter_by(author_id=author.id).count()
if all_by_author >= 99:
badge_grant(badge_id=143, user=author)
elif all_by_author >= 9:
badge_grant(badge_id=16, user=author)
else:
badge_grant(badge_id=17, user=author)
purge_files_in_cache(f"https://{SITE}/e/{marsey.name}/webp")
cache.delete_memoized(marsey_list)
if all_by_author >= 99:
badge_grant(badge_id=143, user=author)
elif all_by_author >= 9:
badge_grant(badge_id=16, user=author)
else:
badge_grant(badge_id=17, user=author)
purge_files_in_cache(f"https://{SITE}/e/{marsey.name}/webp")
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}"
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):
def error(error):
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)
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")
@auth_required
def submit_hat(v):
name = request.values.get('name', '').strip()
description = request.values.get('description', '').strip()
username = request.values.get('author', '').strip()
@app.post("/admin/approve/hat/<name>")
@admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_HATS'])
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!")
def error(error):
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, error=error, name=name, description=description, username=username), 400
new_name = request.values.get('name').strip()
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!")
if request.headers.get("cf-ipcountry") == "T1":
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, 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!")
try:
hat.price = int(request.values.get('price'))
if hat.price < 0: raise ValueError("Invalid hat price")
except:
abort(400, "Invalid hat price")
hat.name = new_name
hat.description = description
g.db.add(hat)
@app.post("/admin/approve/hat/<name>")
@admin_level_required(PERMS['MODERATE_PENDING_SUBMITTED_HATS'])
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!")
g.db.flush()
author = hat.author
new_name = request.values.get('name').strip()
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!")
all_by_author = g.db.query(HatDef).filter_by(author_id=author.id).count()
try:
hat.price = int(request.values.get('price'))
if hat.price < 0: raise ValueError("Invalid hat price")
except:
abort(400, "Invalid hat price")
hat.name = new_name
hat.description = description
g.db.add(hat)
if all_by_author >= 250:
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(
user_id=author.id,
hat_id=hat.id
)
g.db.add(hat_copy)
g.db.flush()
author = hat.author
if v.id != author.id:
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:
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.submitter_id = None
hat_copy = Hat(
user_id=author.id,
hat_id=hat.id
)
g.db.add(hat_copy)
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")
if v.id != author.id:
msg = f"@{v.username} (Admin) has approved a hat you made: '{hat.name}'"
send_repeatable_notification(author.id, msg)
@app.post("/admin/update/marseys")
@admin_level_required(PERMS['UPDATE_MARSEYS'])
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:
msg = f"@{v.username} (Admin) has approved a hat you submitted: '{hat.name}'"
send_repeatable_notification(hat.submitter_id, msg)
file = request.files["image"]
name = request.values.get('name', '').lower().strip()
tags = request.values.get('tags', '').lower().strip()
hat.submitter_id = None
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!"
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!")
@app.post("/admin/update/marseys")
@admin_level_required(PERMS['UPDATE_MARSEYS'])
def update_marsey(v):
if AEVANN_ID and v.id not in CAN_UPDATE_ASSETS:
abort(403)
existing = g.db.get(Marsey, name)
if not existing:
return error("A marsey with this name doesn't exist!")
file = request.files["image"]
name = request.values.get('name', '').lower().strip()
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":
if file:
if g.is_tor:
return error("Image uploads are not allowed through TOR.")
if not file or not file.content_type.startswith('image/'):
if 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}')
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/hats/{name}.webp"
filename = f"files/assets/images/emojis/{name}.webp"
copyfile(new_path, filename)
process_image(filename, 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")
process_image(filename, v, 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 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 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 copy import deepcopy
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.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("/settings/shop")

View File

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

View File

@ -1,17 +1,17 @@
import atexit
import time
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 files.__main__ import app, limiter, cache
from flask import render_template
import sys
import atexit
from files.helpers.actions import *
from files.helpers.alerts import *
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':
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.media import *
from files.helpers.cloudflare import purge_files_in_cache
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.sanitize import filter_emojis_only
from files.helpers.slots 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 flask import *
from files.__main__ import app, limiter
from files.helpers.sanitize import filter_emojis_only
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
from files.routes.routehelpers import execute_shadowban_viewers_and_voters
from files.routes.wrappers import *
from files.__main__ import app, cache, limiter
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
output = get_comments_v_properties(v, False, None, Comment.top_comment_id == c.top_comment_id)[1]
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
else:
@ -142,13 +146,13 @@ def comment(v):
choices.append(i.group(1))
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]
for file in files:
if file.content_type.startswith('image/'):
oldname = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(oldname)
image = process_image(oldname, patron=v.patron)
image = process_image(oldname, v)
if image == "": abort(400, "Image upload failed")
if v.admin_level >= PERMS['SITE_SETTINGS_SIDEBARS_BANNERS_BADGES'] and level == 1:
def process_sidebar_or_banner(type, resize=0):
@ -157,7 +161,7 @@ def comment(v):
num = int(li.split('.webp')[0]) + 1
filename = f'files/assets/images/{SITE_NAME}/{type}/{num}.webp'
copyfile(oldname, filename)
process_image(filename, resize=resize)
process_image(filename, v, resize=resize)
if parent_post.id == SIDEBAR_THREAD:
process_sidebar_or_banner('sidebar', 400)
@ -177,15 +181,15 @@ def comment(v):
g.db.flush()
filename = f'files/assets/images/badges/{badge.id}.webp'
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")
except Exception as e:
abort(400, str(e))
body += f"\n\n![]({image})"
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/'):
body += f"\n\n{SITE_FULL}{process_audio(file)}"
body += f"\n\n{SITE_FULL}{process_audio(file, v)}"
else:
abort(415)
@ -362,7 +366,7 @@ def comment(v):
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])}
@ -382,10 +386,10 @@ def edit_comment(cid, v):
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!")
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('[](')):
abort(403, "You have to type more than 280 characters!")
elif v.bird and len(body) > 140:
@ -415,7 +419,7 @@ def edit_comment(cid, 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_for_sanitize = body
@ -463,17 +467,11 @@ def edit_comment(cid, v):
@auth_required
@ratelimit_user()
def delete_comment(cid, v):
c = get_comment(cid, v=v)
if not c.deleted_utc:
if c.author_id != v.id: abort(403)
c.deleted_utc = int(time.time())
g.db.add(c)
cache.delete_memoized(comment_idlist)
g.db.flush()
@ -483,7 +481,6 @@ def delete_comment(cid, v):
Comment.deleted_utc == 0
).count()
g.db.add(v)
return {"message": "Comment deleted!"}
@app.post("/undelete/comment/<cid>")
@ -491,18 +488,12 @@ def delete_comment(cid, v):
@auth_required
@ratelimit_user()
def undelete_comment(cid, v):
c = get_comment(cid, v=v)
if c.deleted_utc:
if c.author_id != v.id: abort(403)
c.deleted_utc = 0
g.db.add(c)
cache.delete_memoized(comment_idlist)
g.db.flush()
v.comment_count = g.db.query(Comment).filter(
Comment.author_id == v.id,
@ -510,10 +501,8 @@ def undelete_comment(cid, v):
Comment.deleted_utc == 0
).count()
g.db.add(v)
return {"message": "Comment undeleted!"}
@app.post("/pin_comment/<cid>")
@feature_required('PINS')
@auth_required

View File

@ -1,8 +1,10 @@
from files.helpers.wrappers import *
from flask import *
from urllib.parse import quote, urlencode
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:
# https://github.com/pallets/werkzeug/blob/main/src/werkzeug/exceptions.py
@ -53,6 +55,6 @@ def error_500(e):
@app.post("/allow_nsfw")
def allow_nsfw():
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)
return redirect('/')

View File

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

View File

@ -1,10 +1,14 @@
from files.helpers.wrappers import *
from files.helpers.get import *
from files.helpers.const import *
from files.helpers.sorting_and_time import *
from files.__main__ import app, cache, limiter
from sqlalchemy import or_, not_
from files.classes.submission import Submission
from files.classes.votes import Vote
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("/h/<sub>")
@ -13,8 +17,9 @@ from files.helpers.awards import award_timers
@auth_desired_with_logingate
def front_all(v, sub=None, subdomain=None):
#### WPD TEMP #### special front logic
from files.helpers.security import generate_hash, validate_hash
from datetime import datetime
from files.helpers.security import generate_hash, validate_hash
now = datetime.utcnow()
if SITE == 'watchpeopledie.co':
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]
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)
@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):
posts = g.db.query(Submission)
@ -227,7 +231,7 @@ def all_comments(v):
next_exists = len(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)

View File

@ -1,11 +1,10 @@
from flask import *
import requests
from files.helpers.wrappers import *
from files.helpers.const import *
from files.routes.wrappers import *
from files.__main__ import app
@app.get("/giphy")
@app.get("/giphy<path>")
@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.helpers.alerts import *
from files.helpers.wrappers import *
from files.helpers.const import *
from files.helpers.useractions import *
from flask import g
from files.routes.wrappers import *
from files.__main__ import app, limiter
@app.get("/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
from os import environ, listdir
from jinja2 import pass_context
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")
def post_embed(id, v):
from flask import render_template
from files.helpers.get import get_post
p = get_post(id, v, graceful=True)
if p: return render_template("submission_listing.html", listing=[p], v=v)
return ''
@app.template_filter("asset")
@pass_context
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
return f'/i/{SITE_NAME}/{asset_path}?v=3010'
@app.template_filter("timestamp")
def timestamp(timestamp):
return make_age_string(timestamp)
@ -46,7 +55,7 @@ def inject_constants():
"BADGE_THREAD":BADGE_THREAD, "SNAPPY_THREAD":SNAPPY_THREAD,
"KOFI_TOKEN":KOFI_TOKEN, "KOFI_LINK":KOFI_LINK,
"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,
"TELEGRAM_LINK":TELEGRAM_LINK, "EMAIL_REGEX_PATTERN":EMAIL_REGEX_PATTERN,
"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
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")
@auth_desired
def login_get(v):
redir = request.values.get("redirect", "/").strip().rstrip('?')
if 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)
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):
if not g:
return False
@ -84,8 +34,7 @@ def login_deduct_when(resp):
return g.login_failed
@app.post("/login")
@limiter.limit("6/minute;10/day",
deduct_when=login_deduct_when)
@limiter.limit("6/minute;10/day", deduct_when=login_deduct_when)
def login_post():
template = ''
g.login_failed = True
@ -188,20 +137,16 @@ def me(v):
@auth_required
@ratelimit_user()
def logout(v):
loggedin = cache.get(f'{SITE}_loggedin') or {}
if session.get("lo_user") in loggedin: del loggedin[session["lo_user"]]
cache.set(f'{SITE}_loggedin', loggedin)
session.pop("lo_user", None)
return {"message": "Logout successful!"}
@app.get("/signup")
@auth_desired
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
if v: return redirect(SITE_FULL)
@ -219,7 +164,7 @@ def sign_up_get(v):
return render_template("sign_up_failed_ref.html")
now = int(time.time())
token = token_hex(16)
token = secrets.token_hex(16)
session["signup_token"] = token
formkey_hashstr = str(now) + token + g.agent
@ -249,7 +194,7 @@ def sign_up_get(v):
@limiter.limit("1/second;10/day")
@auth_desired
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
if v: abort(403)
@ -261,18 +206,14 @@ def sign_up_post(v):
if not submitted_token: abort(400)
correct_formkey_hashstr = form_timestamp + submitted_token + g.agent
correct_formkey = hmac.new(key=bytes(SECRET_KEY, "utf-16"),
msg=bytes(correct_formkey_hashstr, "utf-16"),
digestmod='md5'
).hexdigest()
now = int(time.time())
username = request.values.get("username")
if not username: abort(400)
username = username.strip()
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
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")
@feature_required('GAMBLING')
@admin_level_required(PERMS['LOTTERY_ADMIN'])

View File

@ -1,75 +1,30 @@
import requests
import time
from flask import *
from urllib.parse import quote
from files.helpers.security import *
from files.helpers.wrappers import *
from files.classes import *
from files.helpers.const import *
from files.helpers.get import *
from files.helpers.mail import *
from files.helpers.useractions import *
from files.classes import *
from files.routes.wrappers import *
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")
@limiter.limit(DEFAULT_RATELIMIT_SLOWER)
@auth_required
@ratelimit_user()
def verify_email(v):
send_verification_email(v)
return {"message": "Email has been sent (ETA ~5 minutes)"}
@app.get("/activate")
@auth_required
def activate(v):
email = request.values.get("email", "").strip().lower()
if not email_regex.fullmatch(email):
return render_template("message.html", v=v, title="Invalid email.", error="Invalid email."), 400
id = request.values.get("id", "").strip()
timestamp = int(request.values.get("time", "0"))
token = request.values.get("token", "").strip()

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
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")
@auth_required
@ratelimit_user()
@ -36,7 +41,6 @@ def unread(v):
return {"data":[x[1].json for x in listing]}
@app.get("/notifications/modmail")
@admin_level_required(PERMS['VIEW_MODMAIL'])
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
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")
@auth_required
def authorize_prompt(v):
@ -233,7 +233,7 @@ def admin_app_id_posts(v, aid):
oauth = g.db.get(OauthApp, aid)
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
pids=pids[:100]
@ -256,8 +256,7 @@ def admin_app_id_comments(v, aid):
oauth = g.db.get(OauthApp, aid)
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
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 flask import *
from files.helpers.const import *
from files.helpers.get import *
from files.routes.wrappers import *
from files.__main__ import app

View File

@ -1,33 +1,38 @@
import os
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 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
import requests
from shutil import copyfile
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"}
@app.post("/club_post/<pid>")
@feature_required('COUNTRY_CLUB')
@auth_required
@ -100,7 +105,7 @@ def publish(pid, v):
cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting)
cache.delete_memoized(userpagelisting)
if post.sub == 'changelog':
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 v:
execute_shadowban_viewers_and_voters(v, post)
# shadowban check is done in sort_objects
# output is needed: see comments.py
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)
if v and v.client:
return post.json
return post.json(g.db)
template = "submission.html"
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),
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>")
@limiter.limit(DEFAULT_RATELIMIT_SLOWER)
@ -297,7 +303,7 @@ def morecomments(v, cid):
comments = output
else:
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
else: p = None
@ -340,7 +346,7 @@ def edit_post(pid, v):
p.title = title
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
if body != p.body:
@ -422,12 +428,8 @@ def edit_post(pid, v):
return redirect(p.permalink)
def thumbnail_thread(pid):
db = db_session()
def thumbnail_thread(pid:int, db, vid:int):
def expand_url(post_url, fragment_url):
if fragment_url.startswith("https://"):
return fragment_url
elif fragment_url.startswith("https://"):
@ -464,8 +466,6 @@ def thumbnail_thread(pid):
if x.status_code != 200:
db.close()
return
if x.headers.get("Content-Type","").startswith("text/html"):
soup=BeautifulSoup(x.content, 'lxml')
@ -505,7 +505,6 @@ def thumbnail_thread(pid):
for url in thumb_candidate_urls:
try:
image_req=requests.get(url, headers=headers, timeout=5, proxies=proxies)
except:
@ -523,15 +522,11 @@ def thumbnail_thread(pid):
with Image.open(BytesIO(image_req.content)) as i:
if i.width < 30 or i.height < 30:
continue
break
else:
db.close()
return
elif x.headers.get("Content-Type","").startswith("image/"):
image_req=x
with Image.open(BytesIO(x.content)) as i:
@ -550,9 +545,12 @@ def thumbnail_thread(pid):
for chunk in image_req.iter_content(1024):
file.write(chunk)
post.thumburl = process_image(name, resize=100, uploader=post.author_id, db=db)
db.add(post)
db.commit()
v = db.get(User, vid)
url = process_image(name, v, resize=100, uploader_id=post.author_id, db=db)
if url:
post.thumburl = url
db.add(post)
db.commit()
db.close()
stdout.flush()
return
@ -768,7 +766,7 @@ def submit_post(v, sub=None):
choices.append(i.group(1))
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
torture = (v.agendaposter and not v.marseyawarded and sub != 'chudrama')
@ -858,33 +856,28 @@ def submit_post(v, sub=None):
)
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']
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
post.url = process_image(name, patron=v.patron)
post.url = process_image(name, v)
name2 = name.replace('.webp', 'r.webp')
copyfile(name, name2)
post.thumburl = process_image(name2, resize=100)
post.thumburl = process_image(name2, v, resize=100)
elif file.content_type.startswith('video/'):
post.url = process_video(file)
post.url = process_video(file, v)
elif file.content_type.startswith('audio/'):
post.url = process_audio(file)
post.url = process_audio(file, v)
else:
abort(415)
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:
notify_users = NOTIFY_USERS(f'{title} {body}', v)
if notify_users:
@ -936,7 +929,7 @@ def submit_post(v, sub=None):
execute_lawlz_actions(v, post)
cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting)
cache.delete_memoized(userpagelisting)
if post.sub == 'changelog' and not post.private:
send_changelog_message(post.permalink)
@ -945,7 +938,7 @@ def submit_post(v, sub=None):
send_wpd_message(post.permalink)
g.db.commit()
if v.client: return post.json
if v.client: return post.json(g.db)
else:
post.voted = 1
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)
cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting)
cache.delete_memoized(userpagelisting)
g.db.flush()
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)
cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting)
cache.delete_memoized(userpagelisting)
g.db.flush()
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!")
post.is_pinned = not post.is_pinned
g.db.add(post)
cache.delete_memoized(User.userpagelisting)
cache.delete_memoized(userpagelisting)
if post.is_pinned: return {"message": "Post pinned!"}
else: return {"message": "Post unpinned!"}
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 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.routes.front import frontlist
from files.routes.wrappers import *
from files.__main__ import app, limiter, cache
@app.post("/report/post/<pid>")
@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
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
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
valid_params = [

View File

@ -1,20 +1,28 @@
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
from files.helpers.sanitize import filter_emojis_only
from shutil import copyfile
import pyotp
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")
@auth_required
@ -220,7 +228,7 @@ def settings_personal_post(v):
elif not updated and FEATURES['USERS_PROFILE_BODYTEXT'] and \
(request.values.get("bio") or request.files.get('file')):
bio = request.values.get("bio")[:1500]
bio += process_files()
bio += process_files(request.files, v)
bio = bio.strip()
bio_html = sanitize(bio)
@ -336,6 +344,7 @@ def themecolor(v):
@auth_required
@ratelimit_user()
def gumroad(v):
if GUMROAD_TOKEN == DEFAULT_CONFIG_VALUE: abort(404)
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!")
@ -380,7 +389,7 @@ def titlecolor(v):
@ratelimit_user()
def verifiedcolor(v):
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")
@limiter.limit(DEFAULT_RATELIMIT_SLOWER)
@ -475,19 +484,19 @@ def settings_log_out_others(v):
@auth_required
@ratelimit_user()
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"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
highres = process_image(name, patron=v.patron)
highres = process_image(name, v)
if not highres: abort(400)
name2 = name.replace('.webp', 'r.webp')
copyfile(name, name2)
imageurl = process_image(name2, resize=100)
imageurl = process_image(name2, v, resize=100)
if not imageurl: abort(400)
@ -511,13 +520,13 @@ def settings_images_profile(v):
@auth_required
@ratelimit_user()
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"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
bannerurl = process_image(name, patron=v.patron)
bannerurl = process_image(name, v)
if bannerurl:
if v.bannerurl and '/images/' in v.bannerurl:

View File

@ -1,15 +1,19 @@
from files.mail import *
from files.__main__ import app, limiter
import os
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.const import *
from files.helpers.actions import *
from files.classes.award import AWARDS
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
from files.routes.wrappers import *
from files.__main__ import app, cache, limiter
@app.get("/r/drama/comments/<id>/<title>")
@ -214,7 +218,7 @@ def submit_contact(v):
abort(403)
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_html = sanitize(body)
@ -408,172 +412,3 @@ if not os.path.exists(f'files/templates/donate_{SITE_NAME}.html'):
@auth_desired_with_logingate
def donate(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.wrappers import *
from files.helpers.get import *
from files.helpers.regex import *
from files.classes import *
from files.routes.wrappers import *
from .front import frontlist
from sqlalchemy import nullslast
import tldextract
from files.__main__ import app, cache, limiter
@app.post("/exile/post/<pid>")
@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")}')
@is_not_permabanned
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)
if not v.mods(sub.name): abort(403)
@ -467,7 +469,7 @@ def sub_banner(v, sub):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
bannerurl = process_image(name, patron=v.patron, resize=1200)
bannerurl = process_image(name, v, resize=1200)
if 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")}')
@is_not_permabanned
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)
if not v.mods(sub.name): abort(403)
@ -499,7 +501,7 @@ def sub_sidebar(v, sub):
file = request.files["sidebar"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
sidebarurl = process_image(name, patron=v.patron, resize=400)
sidebarurl = process_image(name, v, resize=400)
if 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")}')
@is_not_permabanned
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)
if not v.mods(sub.name): abort(403)
@ -531,7 +533,7 @@ def sub_marsey(v, sub):
file = request.files["marsey"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
marseyurl = process_image(name, patron=v.patron, resize=200)
marseyurl = process_image(name, v, resize=200)
if marseyurl:
if sub.marseyurl and '/images/' in sub.marseyurl:

View File

@ -1,28 +1,30 @@
from typing import Literal
import qrcode
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
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):
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:
raise ValueError(f"Invalid currency '{currency_name}' got when transferring {amount} from {v.id} to {receiver.id}")
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)
g.db.add(v)
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}")
if parent.sentto == 2:
body += process_files()
body += process_files(request.files, v)
body = body.strip()
@ -544,9 +546,9 @@ def messagereply(v):
gevent.spawn(pusher_thread, interests, title, notifbody, url)
top_comment = c.top_comment(g.db)
if c.top_comment.sentto == 2:
if top_comment.sentto == 2:
admins = g.db.query(User.id).filter(User.admin_level >= PERMS['NOTIFICATIONS_MODMAIL'], User.id != v.id)
if SITE == 'watchpeopledie.tv':
admins = admins.filter(User.id != AEVANN_ID)
@ -560,7 +562,7 @@ def messagereply(v):
notif = Notification(comment_id=c.id, user_id=admin)
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))
for n in notifications:
g.db.delete(n)
@ -663,6 +665,16 @@ def visitors(v):
viewers=sorted(v.viewers, key = lambda x: x.last_view_utc, reverse=True)
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>.json")
@ -701,7 +713,7 @@ def u_username(username, v=None):
try: page = max(int(request.values.get("page", 1)), 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)
ids = ids[:PAGE_SIZE]
@ -717,7 +729,7 @@ def u_username(username, v=None):
if u.unban_utc:
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",
unban=u.unban_string,
@ -731,7 +743,7 @@ def u_username(username, v=None):
is_following=is_following)
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",
u=u,
@ -799,7 +811,7 @@ def u_username_comments(username, v=None):
listing = get_comments(ids, v=v)
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)
@ -1082,22 +1094,17 @@ kofi_tiers={
def settings_kofi(v):
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!")
transaction = g.db.query(Transaction).filter_by(email=v.email).order_by(Transaction.created_utc.desc()).first()
if not transaction:
abort(404, "Email not found")
if transaction.claimed:
abort(400, f"{patron} rewards already claimed")
tier = kofi_tiers[transaction.amount]
procoins = procoins_li[tier]
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).")
g.db.add(v)
if tier > v.patron:
@ -1107,7 +1114,5 @@ def settings_kofi(v):
badge_grant(badge_id=20+tier, user=v)
transaction.claimed = True
g.db.add(transaction)
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 flask import *
from files.__main__ import app, limiter, cache
from files.helpers.const import *
from files.helpers.get import *
from files.routes.wrappers import *
from files.__main__ import app, limiter
@app.get("/votes/<link>")
@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 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):
loggedin = cache.get(f'{SITE}_loggedin') or {}
loggedout = cache.get(f'{SITE}_loggedout') or {}
@ -32,7 +34,7 @@ def calc_users(v):
def get_logged_in_user():
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
v = None
token = request.headers.get("Authorization","").strip()
@ -57,13 +59,12 @@ def get_logged_in_user():
if request.method != "GET":
submitted_key = request.values.get("formkey")
if not submitted_key: abort(401)
if not v.validate_formkey(submitted_key): abort(401)
if not validate_formkey(v, submitted_key): abort(401)
v.client = None
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)
g.v = v
@ -84,7 +85,6 @@ def get_logged_in_user():
if f'@{v.username}, ' not in f.read():
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')
return v
def auth_desired(f):
@ -97,7 +97,7 @@ def auth_desired(f):
def auth_desired_with_logingate(f):
def wrapper(*args, **kwargs):
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'):
redir = request.full_path.replace('/logged_out','')

View File

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

View File

@ -40,7 +40,7 @@
{% endif %}
<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>
<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">
<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="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>

View File

@ -11,7 +11,7 @@
<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>
<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">
<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">
<label for="days">Days</label>

View File

@ -21,7 +21,7 @@
{% set score=ups-downs %}
{% if render_replies %}
{% set replies=c.replies(sort=sort, v=v) %}
{% set replies=c.replies(sort=sort, v=v, db=g.db) %}
{% 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) %}
@ -271,7 +271,7 @@
{% if v and v.id==c.author_id %}
<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">
<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>
<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}}">
<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>
</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>
@ -515,7 +515,7 @@
<div id="reply-to-{{c.id}}" class="d-none">
<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">
<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 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>
@ -533,7 +533,7 @@
&nbsp;
<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>
<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>
</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>
@ -576,7 +576,7 @@
<div id="reply-message-{{c.id}}" class="d-none">
<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">
<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>
<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>
@ -584,7 +584,7 @@
{% if c.sentto == 2 %}
<label class="btn btn-secondary m-0 mt-3" for="file-upload">
<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>
{% endif %}
</div>

View File

@ -1,12 +1,8 @@
{% extends "default.html" %}
{% block title %}
<title>{{SITE_NAME}} - Contact</title>
{% endblock %}
{% block content %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
@ -27,14 +23,14 @@
<form id="contactform" action="/send_admin" method="post" enctype="multipart/form-data">
<label for="input-message" class="mt-3">Your message</label>
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input type="hidden" name="formkey" value="{{v|formkey}}">
<textarea autocomplete="off" maxlength="10000" id="input-message" form="contactform" name="message" class="form-control" required></textarea>
<label class="btn btn-secondary format m-0 mt-3" onclick="loadEmojis('input-message')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji">
<i class="fas fa-smile-beam"></i>
</label>
<label class="btn btn-secondary m-0 mt-3" for="file-upload">
<div id="filename"><i class="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>
<input type="submit" onclick="disable(this)" value="Submit" class="btn btn-primary mt-3">
</form>

View File

@ -117,7 +117,7 @@
{% if SITE == 'rdrama.net' %}
<td>{% include "user_in_table.html" %}</td>
{% 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 class="shop-table-actions" style="width:unset">
{% 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