forked from rDrama/rDrama
[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>
master
parent
4a05910161
commit
8f2f48d6d1
|
@ -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():
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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})>"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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})>"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import zlib
|
||||
from collections import defaultdict
|
||||
|
||||
import gevent
|
||||
import gevent_inotifyx as inotify
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <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)
|
||||
|
|
|
@ -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")
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
)
|
|
@ -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(' '):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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 *
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from flask import g
|
||||
|
||||
from files.classes.badges import Badge
|
||||
from files.helpers.alerts import send_repeatable_notification
|
||||
|
||||
|
|
|
@ -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 *
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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()
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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('/')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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,
|
|
@ -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):
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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()
|
|
@ -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):
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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!")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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 = [
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
|
@ -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:
|
||||
|
|
|
@ -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!"}
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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','')
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 @@
|
|||
|
||||
<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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue