Merge branch 'frost' into new-user-filtering

This commit is contained in:
Julian Rota 2022-05-22 17:01:45 -04:00
commit 0002f66f36
82 changed files with 1036 additions and 1528 deletions

View file

@ -58,6 +58,7 @@ app.config['MAIL_USERNAME'] = environ.get("MAIL_USERNAME", "").strip()
app.config['MAIL_PASSWORD'] = environ.get("MAIL_PASSWORD", "").strip()
app.config['DESCRIPTION'] = environ.get("DESCRIPTION", "DESCRIPTION GOES HERE").strip()
app.config['SETTINGS'] = {}
app.config['SQLALCHEMY_DATABASE_URI'] = app.config['DATABASE_URL']
r=redis.Redis(host=environ.get("REDIS_URL", "redis://localhost"), decode_responses=True, ssl_cert_reqs=None)

View file

@ -4625,9 +4625,7 @@ img[pat][src^="/pp/"], img[pat][src$="/pic"] {
color: #f27d0c !important;
font-weight: 800;
}
.agendaposter {
text-transform: uppercase !important;
}
code {
text-transform: none !important;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

@ -1,158 +0,0 @@
const fav = document.getElementById('EMOJIS_favorite')
const search_bar = document.getElementById('emoji_search')
const search_container = document.getElementById('emoji-tab-search')
let emojis = 1
function loadEmojis(form) {
if (emojis == 1)
{
const xhr = new XMLHttpRequest();
xhr.open("GET", '/marsey_list');
xhr.setRequestHeader('xhr', 'xhr');
var f = new FormData();
xhr.onload = function() {
emojis = {
'marsey': JSON.parse(xhr.response),
'marseyalphabet': ['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','space','period','questionmark'],
'platy': ['platytrans','platyfuckyou','plarsy','platyabused','platyblizzard','platyboxer','platydevil','platyfear','platygirlmagic','platygolong','platyhaes','platyking','platylove','platyneet','platyold','platypatience','platypopcorn','platyrich','platysarcasm','platysilly','platysleeping','platythink','platytired','platytuxedomask','platyblush','platybruh','platycaveman','platycheer','platydown','platyeyes','platyheart','platylol','platymicdrop','platynooo','platysalute','platyseethe','platythumbsup','platywave'],
'tay': ['taylove','tayaaa','tayadmire','taycat','taycelebrate','taychefkiss','taychristmas','tayclap','taycold','taycrown','tayflex','tayflirt','taygrimacing','tayhappy','tayheart','tayhmm','tayhuh','tayhyperdab','tayjammin','taylaugh','taymindblown','tayno','taynod','taypeace','taypray','tayrun','tayscrunch','tayshake','tayshrug','taysilly','tayslide','taysmart','taystop','taytantrum','taytea','taythink','tayvibin','taywhat','taywine','taywine2','taywink','tayyes'],
'classic': ['flowers','scootlgbt','idhitit','2thumbsup','aliendj','ambulance','angry','angrywhip','argue','aroused','ashamed','badass','banana','band','banghead','batman','bigeyes','bite','blind','blowkiss','blush','bong','bounce','bow','breakheart','bs','cartwheel','cat','celebrate','chainsaw','cheers','clap','cold','confused','crazyeyes','cry','cthulhu','cute','laughing','daydream','ddr','deadpool','devilsmile','diddle','die','distress','disturbing','dizzy','domo','doughboy','drink','drool','dudeweedlmao','edward','electro','elephant','embarrassed','emo','emo2','evil','evilclown','evilgrin','facepalm','fap','flamethrower','flipbird','flirt','frown','gasp','glomp','go','gooby','grr','gtfo','guitar','haha','handshake','happydance','headbang','heart','heartbeat','hearts','highfive','hmm','hmph','holdhands','horny','hug','hugging','hugs','hump','humpbed','hysterical','ily','inlove','jason','jawdrop','jedi','jester','kaboom','kick','kiss','kitty','laughchair','lick','link','lol','lolbeat','loving','makeout','medal','megaman','megamanguitar','meow','metime','mooning','mummy','na','nauseous','nervous','ninja','nod','nono','omg','onfire','ooo','orly','tongueout','paddle','panda','pandabutt','paranoid','party','pat','peek','pikachu','pimp','plzdie','poke','popcorn','pout','probe','puke','punch','quote','raccoon','roar','rofl','roflmao','rolleyes','sad','sadeyes','sadhug','samurai','sarcasm','scoot','scream','shmoopy','shrug','skull','slap','slapfight','sleepy','smackfish','smackhead','smh','smile','smoke','sonic','spank','sparta','sperm','spiderman','stab','star','stare','stfu','suicide','surprisehug','suspicious','sweat','swordfight','taco','talk2hand','tantrum','teehee','thinking','threesome','throw','throwaway','tickle','typing','uhuh','vampbat','viking','violin','vulgar','wah','wat','whip','whipping','wink','witch','wizard','woah','worm','woo','work','worship','wow','xd','yay','zzz'],
'rage': ['trolldespair','clueless','troll','bitchplease','spit','challengeaccepted','contentiouscereal','cryingatcuteness','derp','derpcornsyrup','derpcrying','derpcute','derpdumb','derpeuphoria','derpinahd','derpinapokerface','derpinasnickering','derpprocessing','derprealization','derpsnickering','derptalking','derpthinking','derpthumbsup','derpunimpressed','derpwhy','donotwant','epicfacefeatures','fancywithwine','fffffffuuuuuuuuuuuu','flipthetable','foreveralone','foreveralonehappy','hewillnever','idontknow','interuptedreading','iseewhatyoudidthere','killherkillher','ledesire','leexcited','legenius','lelolidk','lemiddlefinger','lemindblown','leokay','lepanicrunning','lepokerface','lepokerface2','lerageface','leseriousface','likeaboss','lolface','longwhiskers','manymiddlefingers','megusta','motherfucker','motherofgod','mysides','ohgodwhy','pervertedspiderman','picard','ragestrangle','rukiddingme','tfwyougettrolled','trollolol','truestorybro','xallthey','yuno'],
'wojak': ['purerage','naziseethe','holdupjak','ethottalking','chadwomanasian','chadwomanblack','chadwomanlatinx','chadwomannordic','trumpjaktalking','rdramajanny','soyreddit','doomerboy','npcsupport','npcoppse','grugthink','soyconsoomer','soyjaktalking','soyquack','tradboy','sciencejak','soyjakanimeglasses','soymad','boomerportrait','soycry','punchjak','seethejak','chadyes','chadno','abusivewife','ancap','bardfinn','bloomer','boomer','boomermonster','brainletbush','brainletcaved','brainletchair','brainletchest','brainletmaga','brainletpit','chad','chadarab','chadasian','chadblack','chadjesus','chadjew','chadjihadi','chadlatino','chadlibleft','chadnordic','chadsikh','chadusa','coomer','doomer','doomerfront','doomergirl','ethot','fatbrain','fatpriest','femboy','gogetter','grug','monke','nazijak','npc','npcfront','npcmaga','psychojak','ragejak','ragemask','ramonajak','soyjackwow','soyjak','soyjakfront','soyjakhipster','soyjakmaga','soyjakyell','tomboy','zoomer','zoomersoy'],
'flags': ['russia','niger','lgbt','animesexual','blacknation','blm','blueline','dreamgender','fatpride','incelpride','israel','kazakhstan','landlordlove','scalperpride','superstraight','trans','translord','transracial','usa'],
'wolf': ['wombiezolf','wolfdramanomicon','wolfamogus','wolfmarine','wolfromulusremus','wolfrope','wolftinfoil','wolfmarseymask','wolfputin','wolfdrama','wolfcumjar','wolflgbt','wolfmarseyhead','wolfnoir','wolfsherifssmoking','wolftrans','wolfvaporwave','wolfangry','wolfbrains','wolfcry','wolfdead','wolfdevilish','wolffacepalm','wolfhappy','wolfidea','wolfkoala','wolflaugh','wolflove','wolfmeditate','wolfphone','wolfrainbow','wolfroses','wolfsad','wolfsfear','wolfsleep','wolftear','wolfthink','wolfthumbsup','wolfupsidedown','wolfvictory','wolfwave','wolfwink'],
'misc': ['chadyescapy','chadnocapy','capygitcommit','capysneedboat','chadbasedcapy','chadcopecapy','chaddilatecapy','chaddonekingcapy','chaddonequeencapy','chadfixedkingcapy','chadfixedqueencapy','chadokcapy','chadseethecapy','chadsneedcapy','chadthankskingcapy','chadthanksqueencapy','sherpat','xdoubt','gigachadjesus','yotsubafish','yotsubalol','sigmatalking','zoroarkconfused','zoroarkhappy','zoroarkpout','zoroarksleepy','casanovanova','deuxwaifu','flairlessmong','hardislife','redditgigachad','rfybear','etika','sneed','retardedchildren','bruh','autism','doot','kylieface','queenyes','wholesomeseal','gigachadglow','gigachadorthodox','gigachad','gigachad2','gigachad3']
}
cringetopia = document.getElementById('EMOJIS_cringetopia')
if (cringetopia)
emojis['cringetopia'] = ['19dollarfortnitecard','ahawow','ahayeah','alex','amogus','amogusbubble','amongpoop','amusing','anarchy','angelblob','angry','angryman','awaakesoyjack','backseatmodding','based','bassproshop','berkfail','berkstache','bert','bestmovieever','binface','blackpill','blingbob','blingbobhd','bluepill','booba','boof','boohoo','brainlet','britishmoment','brownnose','bruh','bruhsmoke','brunocheers','brunopog','bussin','byelol','canigetmod','car','cereal','cerkies','chad','chaddiscordmoderator','chadstare','cheeks','cheers','cheesed','cheeseu','chungus','chunky','clueless','cocka','cokeza','comeagain','coom','cope','coping','cryaboutit','cryingcool','cryrage','cum','cummies','cutelittledude','da','dab','dababy','datrolly','davyree','dayum','death','delight','disbelief','disgostang','disgust','donotdisturb','doritoberk','downisrael','downvote','downwert','drbob','drumtime','erotic','evilblob','face','facepalm','factsandlogic','fake','fatcryrage','fear','fedora','fistbumpl','fistbumpr','fjalsunna','floppacheers','floppagun','fluoride','flushspin','funwa','furrymurder','gas','getajob','gigachad','gigajammin','godiwishthatwereme','gogetter','goingcrazy','goinginsane','gold','gone','goofy','goonpog','goteem','grabhand','grind','gun','hahawyd','hatred','heart','heh','hehehe','hesapeonyouknow','hesepsteinyouknow','hesgayyouknow','hesretardedyouknow','hesrightyouknow','hitormiss','holy','hopeless','horny','hot','hulk','hunky','iloveyou','insanity','ipgrabber','irantroll','isawthat','itendsnow','itsover','itsoverseal','jewbob','joebiden','josh','kanyerant','keith','kill','kissin','laughing','lean','leftgun','lessgooooooooooo','lessgoooooooooooo','letsfuckinggooooooooooo','lick','light','like','littledrummerboy','lmao','logo','logoff','lolrage','love','madman','malding','maldinggif','manpain','march','me_lon','menendez','mewhen','mf','mhm','mhmdoubt','mlady','murder','nefarious','nefariousacts','newport','newport~1','nibburger_king','nigerianreaction','nooo','noooo','noose','noperms','normalize','ohwow','okboomer','orange','pain','pardon','payne','peepotalk','pepecringe','pepecringe2','pepeherrington','peperagecry','pepoboner','pepoojamm','phillip','pitchfork','pleading','please','pouroneout40','praiseallah','prayge','psycho','quagmirelet','ratio','real','really','redditnation','redpill','redwojack','regice','ricardosmile','ripbozo','sad','sadcat','sadge','sadtroll','salute','scare','schizothread','secretside','sheesh','shh','shocked','shotty','sigma','sigmamale','sigmasnap','simpjack','siren','skill_issue','slime','slow','smoketime','sniff','society','society2','soy','spit','spit2','splendid','squidwardno','stare','stare1','stfu','stoppedwert','stopwert','susamogus','suscoin','susge','susiety','tacobell','tempest','thanosd','thanoslol','thistbh','thonk','tiktokcoin','tiresome','tm06','troll','trollcrazy','trollcrazyfurry','trollcrazypoint','trollcrazyrussia','trollcrazyukraine','trolldisappointed','trollface','trollinsane','trollsus','truetrue','turbobrainlet','tuvvokmoment','twetwe','unblockme','updonda','upiran','upvote','upwert','vengeance','virgin','wa','waaaah','wallstreet','wertegg','wertpinion','what','whatdeath','whensus','wholesome','wolfcode','womanmoment','woohoo','wtff','xplode','yeeeees','yeet','yes','yeshoney','yet','you','zombie_phillip']
loadEmojis(form);
}
xhr.send(f);
}
else {
fav.setAttribute('data-form-destination', form)
const commentBox = document.getElementById(form)
commentBox.setAttribute('data-curr-pos', commentBox.selectionStart);
if (fav.innerHTML == "")
{
let str = ""
const favorite_emojis = JSON.parse(localStorage.getItem("favorite_emojis"))
if (favorite_emojis)
{
const sortable = Object.fromEntries(
Object.entries(favorite_emojis).sort(([,a],[,b]) => b-a)
);
for (const emoji of Object.keys(sortable).slice(0, 25))
str += `<button class="btn m-1 px-0 emoji2" onclick="getEmoji('${emoji}')" data-bs-toggle="tooltip" title=":${emoji}:" delay:="0"><img loading="lazy" width=50 src="/e/${emoji}.webp" alt="${emoji}-emoji"></button>`
fav.innerHTML = str
}
}
if (search_bar.value == "") {
search_container.innerHTML = ""
document.getElementById("tab-content").classList.remove("d-none");
if (document.getElementById("EMOJIS_marsey").innerHTML == "")
{
for (const [k, v] of Object.entries(emojis)) {
let container = document.getElementById(`EMOJIS_${k}`)
let str = ''
if (k == 'marsey')
{
for (const e of v) {
let k = e.toLowerCase().split(" : ")[0];
str += `<button class="btn m-1 px-0 emoji2" onclick="getEmoji('${k}')" style="background: None!important; width:60px; overflow: hidden; border: none" data-bs-toggle="tooltip" title=":${k}:" delay:="0"><img loading="lazy" width=50 src="/e/${k}.webp" alt="${k}-emoji"></button>`;
}
}
else {
for (const e of v) {
str += `<button class="btn m-1 px-0 emoji2" onclick="getEmoji('${e}')" style="background: None!important; width:60px; overflow: hidden; border: none" data-bs-toggle="tooltip" title=":${e}:" delay:="0"><img loading="lazy" width=50 src="/e/${e}.webp" alt="${e}-emoji"></button>`;
}
}
container.innerHTML = str
}
}
}
else {
let str = ''
for (const [key, value] of Object.entries(emojis)) {
if (key == "marsey")
{
for (const e of value) {
let arr = e.toLowerCase().split(" : ");
let k = arr[0];
let v = arr[1];
if (str.includes(`'${k}'`)) continue;
if (k.match(search_bar.value.toLowerCase()) || search_bar.value.toLowerCase().match(k) || v.match(search_bar.value.toLowerCase())) {
str += `<button class="btn m-1 px-0 emoji2" onclick="getEmoji('${k}')" data-bs-toggle="tooltip" title=":${k}:" delay:="0"><img loading="lazy" width=50 src="/e/${k}.webp" alt="${k}-emoji"></button>`;
}
}
}
else if (key != "marseyalphabet")
{
for (const e of value) {
if (e.match(search_bar.value.toLowerCase()) || search_bar.value.toLowerCase().match(e)) {
str += `<button class="btn m-1 px-0 emoji2" onclick="getEmoji('${e}')" style="background: None!important; width:60px; overflow: hidden; border: none" data-bs-toggle="tooltip" title=":${e}:" delay:="0"><img loading="lazy" width=50 src="/e/${e}.webp" alt="${e}-emoji"></button>`;
}
}
}
}
document.getElementById("tab-content").classList.add("d-none");
search_container.innerHTML = str
}
search_bar.oninput = function () {
loadEmojis(form);
};
}
}
function getEmoji(searchTerm) {
const form = fav.getAttribute('data-form-destination')
const commentBox = document.getElementById(form)
const old = commentBox.value;
const curPos = parseInt(commentBox.getAttribute('data-curr-pos'));
const firstHalf = old.slice(0, curPos)
const lastHalf = old.slice(curPos)
let emoji = ':' + searchTerm + ':'
const previousChar = firstHalf.slice(-1)
if (firstHalf.length > 0 && previousChar !== " " && previousChar !== "\n") {
emoji = " " + emoji
}
if (lastHalf.length > 0 && lastHalf[0] !== " ") {
emoji = emoji + " "
}
commentBox.value = firstHalf + emoji + lastHalf;
const newPos = curPos + emoji.length
commentBox.setAttribute('data-curr-pos', newPos.toString());
commentBox.dispatchEvent(new Event('input'));
const favorite_emojis = JSON.parse(localStorage.getItem("favorite_emojis")) || {}
if (favorite_emojis[searchTerm]) favorite_emojis[searchTerm] += 1
else favorite_emojis[searchTerm] = 1
localStorage.setItem("favorite_emojis", JSON.stringify(favorite_emojis))
}

View file

@ -0,0 +1,221 @@
function appendToken(parentElement, childToken) {
var childElement = tokenToHTMLElement(childToken);
if (typeof childElement === 'string') {
parentElement.textContent += childElement;
} else if (childElement !== null) {
parentElement.appendChild(childElement)
}
}
function appentTextOrElement(parentElement, textOrElement) {
if (typeof textOrElement === 'string') {
parentElement.textContent += textOrElement;
} else {
parentElement.appendChild(textOrElement);
}
}
function motteSpecialMarkdown(text) {
if (typeof text !== 'string') {
return '';
}
var spoilerMatch = text.match(/^(.*?)\|\|(.*?)\|\|(.*)$/);
if (spoilerMatch) {
var left = spoilerMatch[1];
var mid = spoilerMatch[2];
var right = spoilerMatch[3];
var parentSpan = document.createElement('span');
var leftSpan = document.createElement('span');
var spoilerSpan = document.createElement('span');
var rightSpan = document.createElement('span');
spoilerSpan.textContent = mid;
spoilerSpan.classList.add('spoiler');
appentTextOrElement(leftSpan, motteSpecialMarkdown(left));
appentTextOrElement(rightSpan, motteSpecialMarkdown(right));
parentSpan.appendChild(leftSpan);
parentSpan.appendChild(spoilerSpan);
parentSpan.appendChild(rightSpan);
return parentSpan;
} else {
return text;
}
}
function tokenToHTMLElement(token) {
var element = null;
if (token.type === 'space') {
element = document.createElement('span');
element.textContent = ' ';
return element;
} else if (typeof token.type === 'undefined' || token.type === 'text') {
element = document.createElement('span');
appentTextOrElement(element, motteSpecialMarkdown(token.text));
return element;
} else if (token.type === 'hr') {
return document.createElement('hr');
} else if (token.type === 'br') {
return document.createElement('br');
} else if (token.type === 'heading') {
element = document.createElement('h' + Math.floor(Math.max(1, Math.min(6, token.depth))));
} else if (token.type === 'code') {
element = document.createElement('pre');
element.setAttribute('data-lang', token.lang);
} else if (token.type === 'list_item') {
element = document.createElement('li');
} else if (token.type === 'blockquote') {
element = document.createElement('blockquote');
} else if (token.type === 'list') {
if (token.ordered) {
element = document.createElement('ol');
} else {
element = document.createElement('ul');
}
} else if (token.type === 'table') {
var table, thead, tbody, tr, th, td;
table = document.createElement('table');
table.classList.add('table');
thead = document.createElement('thead');
tbody = document.createElement('thead');
tr = document.createElement('tr');
for (var x = 0; x < token.header.length; x++) {
th = document.createElement('th');
appendToken(th, token.header[x]);
tr.appendChild(th);
}
thead.appendChild(tr);
table.appendChild(thead);
for (var y = 0; y < token.rows.length; y++) {
row = token.rows[y];
tr = document.createElement('tr');
for (var x = 0; x < row.length; x++) {
td = document.createElement('td');
appendToken(td, row[x]);
tr.appendChild(td);
}
tbody.appendChild(tr);
}
table.appendChild(tbody);
return table;
} else if (token.type === 'paragraph') {
element = document.createElement('p');
} else if (token.type === 'div') {
element = document.createElement('div');
} else if (token.type === 'em') {
element = document.createElement('i');
} else if (token.type === 'strong') {
element = document.createElement('b');
} else if (token.type === 'del') {
element = document.createElement('del');
} else if (token.type === 'codespan') {
element = document.createElement('code');
element.textContent = token.text;
return element;
} else if (token.type === 'escape') {
} else if (token.type === 'link') {
element = document.createElement('a');
element.setAttribute('href', token.href);
element.textContent = token.text;
return element;
} else if (token.type === 'image') {
element = document.createElement('img');
element.setAttribute('src', token.href);
element.setAttribute('alt', token.text);
return element;
} else {
console.log(token);
element = document.createElement('div');
}
var children = (token.tokens || token.items || []);
if (element !== null && children.length > 0) {
for (var i = 0; i < children.length; i++) {
var childElement = tokenToHTMLElement(children[i]);
if (childElement.outerHTML.match(/"spoiler"/g)) {
console.log(element, token, childElement, children[i]);
}
appendToken(element, children[i]);
}
} else if (element.children.length === 0 && element.textContent === '') {
element.textContent += token.text;
}
return element;
}
function safeMarkdown(input) {
var seenTokens = [];
var outputToken = {"type": "div", "raw": input, "text": "", "tokens": []};
function seeRecursive(token) {
seenTokens.push(token);
var children = (
token.tokens
|| token.items
|| (token.header||[]).concat(token.rows||[])
|| []
);
if (token.header) {
children = children.concat(token.header);
}
for (var y = 0; y < (token.rows||[]).length; y++) {
children = children.concat(token.rows[y]);
}
for (var i = 0; i < children.length; i++) {
seeRecursive(children[i]);
}
}
marked.use({
walkTokens: function(token) {
if (!seenTokens.includes(token)) {
outputToken.tokens.push(token);
seeRecursive(token);
}
},
});
marked(input);
marked.use({
walkTokens: false,
tokenizer: false,
});
[ 'escape', 'del', ];
return tokenToHTMLElement(outputToken);
}
setTimeout(() => markdown('post-text','preview'), 200);
function markdown(first, second) {
var input = document.getElementById(first).value;
var dest = document.getElementById(second);
for (var i = 0; i < dest.children.length; i++) {
dest.removeChild(dest.children[i]);
}
document.getElementById(second).appendChild(safeMarkdown(input));
}
function charLimit(form, text) {
var input = document.getElementById(form);
var text = document.getElementById(text);
var length = input.value.length;
var maxLength = input.getAttribute("maxlength");
if (length >= maxLength) {
text.style.color = "#E53E3E";
}
else if (length >= maxLength * .72){
text.style.color = "#FFC107";
}
else {
text.style.color = "#A0AEC0";
}
text.innerText = length + ' / ' + maxLength;
}

File diff suppressed because one or more lines are too long

View file

@ -1,22 +1,95 @@
from .alts import *
from .badges import *
from .clients import *
from .comment import *
from .domains import *
from .flags import *
from .user import *
from .userblock import *
from .usernotes import *
from .submission import *
from .votes import *
from .domains import *
from .subscriptions import *
from files.__main__ import app
from .mod_logs import *
from .award import *
from .marsey import *
from .sub_block import *
from .saves import *
from .views import *
from .notifications import *
from .follows import *
################################################################
# WARNING! THIS FILE IS EVIL. #
################################################################
# Previously, this file had a series of #
# from .alts import * #
# from .award import * #
# from .badges import * #
# and so on in that fashion. That means that anywhere that #
# from files.classes import * #
# (and there were a lot of places like that) got anything #
# was imported for any model imported. So if you, for example, #
# removed #
# from secrets import token_hex #
# from files/classes/user.py, the registration page would #
# break because files/routes/login.py did #
# from files.classes import * #
# in order to get the token_hex function rather than #
# importing it with something like #
# from secrets import token_hex #
# #
# Anyway, not fixing that right now, but in order to #
# what needed to be imported here such that #
# from files.classes import * #
# still imported the same stuff as before I ran the following: #
# $ find files/classes -type f -name '*.py' \ #
# -exec grep import '{}' ';' \ #
# | grep 'import' \ #
# | grep -v 'from [.]\|__init__\|from files.classes' \ #
# | sed 's/^[^:]*://g' \ #
# | sort \ #
# | uniq #
# and then reordered the list such that import * did not stomp #
# over stuff that had been explicitly imported. #
################################################################
# First the import * from places which don't go circular
from sqlalchemy import *
from flask import *
# Then everything except what's in files.*
import pyotp
import random
import re
import time
from copy import deepcopy
from datetime import datetime
from flask import g
from flask import render_template
from json import loads
from math import floor
from os import environ
from os import environ, remove, path
from random import randint
from secrets import token_hex
from sqlalchemy.orm import deferred, aliased
from sqlalchemy.orm import relationship
from sqlalchemy.orm import relationship, deferred
from urllib.parse import urlencode, urlparse, parse_qs
from urllib.parse import urlparse
# It is now safe to define the models
from .alts import Alt
from .award import AwardRelationship
from .badges import BadgeDef, Badge
from .clients import OauthApp, ClientAuth
from .comment import Comment
from .domains import BannedDomain
from .exiles import Exile
from .flags import Flag, CommentFlag
from .follows import Follow
from .marsey import Marsey
from .mod import Mod
from .mod_logs import ModAction
from .notifications import Notification
from .saves import SaveRelationship, CommentSaveRelationship
from .sub import Sub
from .sub_block import SubBlock
from .submission import Submission
from .subscriptions import Subscription
from .user import User
from .userblock import UserBlock
from .usernotes import UserTag, UserNote
from .views import ViewerRelationship
from .votes import Vote, CommentVote
# Then the import * from files.*
from files.helpers.const import *
from files.helpers.images import *
from files.helpers.lazy import *
from files.helpers.security import *
# Then the specific stuff we don't want stomped on
from files.helpers.discord import remove_user
from files.helpers.lazy import lazy
from files.__main__ import Base, app, cache

View file

@ -7,7 +7,9 @@ class Alt(Base):
user1 = Column(Integer, ForeignKey("users.id"), primary_key=True)
user2 = Column(Integer, ForeignKey("users.id"), primary_key=True)
is_manual = Column(Boolean, default=False)
is_manual = Column(Boolean, nullable=False, default=False)
Index('alts_user2_idx', user2)
def __repr__(self):

View file

@ -8,17 +8,24 @@ from files.helpers.const import *
class AwardRelationship(Base):
__tablename__ = "award_relationships"
__table_args__ = (
UniqueConstraint('user_id', 'submission_id', 'comment_id', name='award_constraint'),
)
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"))
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
submission_id = Column(Integer, ForeignKey("submissions.id"))
comment_id = Column(Integer, ForeignKey("comments.id"))
kind = Column(String)
kind = Column(String, nullable=False)
user = relationship("User", primaryjoin="AwardRelationship.user_id==User.id", viewonly=True)
post = relationship("Submission", primaryjoin="AwardRelationship.submission_id==Submission.id", viewonly=True)
comment = relationship("Comment", primaryjoin="AwardRelationship.comment_id==Comment.id", viewonly=True)
Index('award_user_idx', user_id)
Index('award_post_idx', submission_id)
Index('award_comment_idx', comment_id)
@property
@lazy

View file

@ -9,15 +9,17 @@ from json import loads
class BadgeDef(Base):
__tablename__ = "badge_defs"
__table_args__ = (
UniqueConstraint('name', name='badge_def_name_unique'),
)
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String)
name = Column(String, nullable=False)
description = Column(String)
def __repr__(self):
return f"<BadgeDef(id={self.id})>"
class Badge(Base):
__tablename__ = "badges"
@ -27,6 +29,8 @@ class Badge(Base):
description = Column(String)
url = Column(String)
Index('badges_badge_id_idx', badge_id)
user = relationship("User", viewonly=True)
badge = relationship("BadgeDef", primaryjoin="foreign(Badge.badge_id) == remote(BadgeDef.id)", viewonly=True)
@ -36,11 +40,7 @@ class Badge(Base):
@property
@lazy
def text(self):
if self.name == "Chud":
ti = self.user.agendaposter
if ti: text = self.badge.description + " until " + datetime.utcfromtimestamp(ti).strftime('%Y-%m-%d %H:%M:%S')
else: text = self.badge.description + " permanently"
elif self.badge_id in {94,95,96,97,98,109}:
if self.badge_id in {94,95,96,97,98,109}:
if self.badge_id == 94: ti = self.user.progressivestack
elif self.badge_id == 95: ti = self.user.bird
elif self.badge_id == 96: ti = self.user.flairchanged

View file

@ -11,13 +11,16 @@ import time
class OauthApp(Base):
__tablename__ = "oauth_apps"
__table_args__ = (
UniqueConstraint('client_id', name='unique_id'),
)
id = Column(Integer, primary_key=True)
client_id = Column(String)
app_name = Column(String)
redirect_uri = Column(String)
description = Column(String)
author_id = Column(Integer, ForeignKey("users.id"))
app_name = Column(String(length=50), nullable=False)
redirect_uri = Column(String(length=50), nullable=False)
description = Column(String(length=256), nullable=False)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
author = relationship("User", viewonly=True)
@ -65,10 +68,13 @@ class OauthApp(Base):
class ClientAuth(Base):
__tablename__ = "client_auths"
__table_args__ = (
UniqueConstraint('access_token', name='unique_access'),
)
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)
access_token = Column(String, nullable=False)
user = relationship("User", viewonly=True)
application = relationship("OauthApp", viewonly=True)

View file

@ -19,28 +19,28 @@ class Comment(Base):
__tablename__ = "comments"
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"))
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
parent_submission = Column(Integer, ForeignKey("submissions.id"))
created_utc = Column(Integer)
edited_utc = Column(Integer, default=0)
is_banned = Column(Boolean, default=False)
ghost = Column(Boolean, default=False)
created_utc = Column(Integer, nullable=False)
edited_utc = Column(Integer, default=0, nullable=False)
is_banned = Column(Boolean, default=False, nullable=False)
ghost = Column(Boolean, default=False, nullable=False)
bannedfor = Column(Boolean)
distinguish_level = Column(Integer, default=0)
deleted_utc = Column(Integer, default=0)
distinguish_level = Column(Integer, default=0, nullable=False)
deleted_utc = Column(Integer, default=0, nullable=False)
is_approved = Column(Integer, ForeignKey("users.id"))
level = Column(Integer, default=1)
level = Column(Integer, default=1, nullable=False)
parent_comment_id = Column(Integer, ForeignKey("comments.id"))
top_comment_id = Column(Integer)
over_18 = Column(Boolean, default=False)
is_bot = Column(Boolean, default=False)
over_18 = Column(Boolean, default=False, nullable=False)
is_bot = Column(Boolean, default=False, nullable=False)
is_pinned = Column(String)
is_pinned_utc = Column(Integer)
sentto = Column(Integer, ForeignKey("users.id"))
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
upvotes = Column(Integer, default=1)
downvotes = Column(Integer, default=0)
realupvotes = Column(Integer, default=1)
upvotes = Column(Integer, default=1, nullable=False)
downvotes = Column(Integer, default=0, nullable=False)
realupvotes = Column(Integer, default=1, nullable=False)
body = Column(String)
body_html = Column(String)
ban_reason = Column(String)
@ -49,6 +49,12 @@ class Comment(Base):
wordle_result = Column(String)
treasure_amount = Column(String)
Index('comment_parent_index', parent_comment_id)
Index('comment_post_id_index', parent_submission)
Index('comments_user_index', author_id)
Index('fki_comment_approver_fkey', is_approved)
Index('fki_comment_sentto_fkey', sentto)
oauth_app = relationship("OauthApp", viewonly=True)
post = relationship("Submission", viewonly=True)
author = relationship("User", primaryjoin="User.id==Comment.author_id")
@ -351,7 +357,6 @@ class Comment(Base):
body = self.body_html or ""
if body:
body = censor_slurs(body, v)
if v:
body = body.replace("old.reddit.com", v.reddit)
@ -417,7 +422,7 @@ class Comment(Base):
if not body: return ""
return censor_slurs(body, v)
return body
def print(self):
print(f'post: {self.id}, comment: {self.author_id}', flush=True)

View file

@ -5,4 +5,10 @@ class BannedDomain(Base):
__tablename__ = "banneddomains"
domain = Column(String, primary_key=True)
reason = Column(String)
reason = Column(String, nullable=False)
Index(
'domains_domain_trgm_idx',
domain,
postgresql_using='gin',
postgresql_ops={'description':'gin_trgm_ops'}
)

View file

@ -7,7 +7,10 @@ class Exile(Base):
__tablename__ = "exiles"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
exiler_id = Column(Integer, ForeignKey("users.id"))
exiler_id = Column(Integer, ForeignKey("users.id"), nullable=False)
Index('fki_exile_exiler_fkey', exiler_id)
Index('fki_exile_sub_fkey', sub)
exiler = relationship("User", primaryjoin="User.id==Exile.exiler_id", viewonly=True)

View file

@ -13,7 +13,9 @@ class Flag(Base):
post_id = Column(Integer, ForeignKey("submissions.id"))
user_id = Column(Integer, ForeignKey("users.id"))
reason = Column(String)
created_utc = Column(Integer)
created_utc = Column(Integer, nullable=False)
Index('flag_user_idx', user_id)
user = relationship("User", primaryjoin = "Flag.user_id == User.id", uselist = False, viewonly=True)
@ -36,7 +38,7 @@ class Flag(Base):
@lazy
def realreason(self, v):
return censor_slurs(self.reason, v)
return self.reason
class CommentFlag(Base):
@ -46,7 +48,9 @@ class CommentFlag(Base):
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
reason = Column(String)
created_utc = Column(Integer)
created_utc = Column(Integer, nullable=False)
Index('cflag_user_idx', user_id)
user = relationship("User", primaryjoin = "CommentFlag.user_id == User.id", uselist = False, viewonly=True)
@ -69,4 +73,4 @@ class CommentFlag(Base):
@lazy
def realreason(self, v):
return censor_slurs(self.reason, v)
return self.reason

View file

@ -7,7 +7,9 @@ class Follow(Base):
__tablename__ = "follows"
target_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
created_utc = Column(Integer)
created_utc = Column(Integer, nullable=False)
Index('follow_user_id_index', user_id)
user = relationship("User", uselist=False, primaryjoin="User.id==Follow.user_id", viewonly=True)
target = relationship("User", primaryjoin="User.id==Follow.target_id", viewonly=True)

View file

@ -5,9 +5,13 @@ class Marsey(Base):
__tablename__ = "marseys"
name = Column(String, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"))
tags = Column(String)
count = Column(Integer, default=0)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
tags = Column(String(length=200), nullable=False)
count = Column(Integer, default=0, nullable=False)
Index('marseys_idx2', author_id)
Index('marseys_idx3', count.desc())
Index('marseys_idx', name)
def __repr__(self):
return f"<Marsey(name={self.name})>"

View file

@ -9,7 +9,9 @@ class Mod(Base):
__tablename__ = "mods"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
created_utc = Column(Integer)
created_utc = Column(Integer, nullable=False)
Index('fki_mod_sub_fkey', sub)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())

View file

@ -16,7 +16,13 @@ class ModAction(Base):
target_submission_id = Column(Integer, ForeignKey("submissions.id"))
target_comment_id = Column(Integer, ForeignKey("comments.id"))
_note=Column(String)
created_utc = Column(Integer)
created_utc = Column(Integer, nullable=False)
Index('fki_modactions_user_fkey', target_user_id)
Index('modaction_action_idx', kind)
Index('modaction_cid_idx', target_comment_id)
Index('modaction_id_idx', id.desc())
Index('modaction_pid_idx', target_submission_id)
user = relationship("User", primaryjoin="User.id==ModAction.user_id", viewonly=True)
target_user = relationship("User", primaryjoin="User.id==ModAction.target_user_id", viewonly=True)
@ -110,11 +116,6 @@ class ModAction(Base):
return f"/log/{self.id}"
ACTIONTYPES = {
'agendaposter': {
"str": 'set chud theme on {self.target_link}',
"icon": 'fa-snooze',
"color": 'bg-danger'
},
'approve_app': {
"str": 'approved an application by {self.target_link}',
"icon": 'fa-robot',
@ -350,11 +351,6 @@ ACTIONTYPES = {
"icon": 'fa-eye-slash',
"color": 'bg-danger'
},
'unagendaposter': {
"str": 'removed chud theme from {self.target_link}',
"icon": 'fa-snooze',
"color": 'bg-success'
},
'unban_comment': {
"str": 'reinstated {self.target_link}',
"icon": 'fa-comment',

View file

@ -9,8 +9,12 @@ class Notification(Base):
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
read = Column(Boolean, default=False)
created_utc = Column(Integer)
read = Column(Boolean, default=False, nullable=False)
created_utc = Column(Integer, nullable=False)
Index('notification_read_idx', read)
Index('notifications_comment_idx', comment_id)
Index('notifs_user_read_idx', user_id, read)
comment = relationship("Comment", viewonly=True)
user = relationship("User", viewonly=True)

View file

@ -10,6 +10,8 @@ class SaveRelationship(Base):
user_id=Column(Integer, ForeignKey("users.id"), primary_key=True)
submission_id=Column(Integer, ForeignKey("submissions.id"), primary_key=True)
Index('fki_save_relationship_submission_fkey', submission_id)
class CommentSaveRelationship(Base):
@ -18,3 +20,5 @@ class CommentSaveRelationship(Base):
user_id=Column(Integer, ForeignKey("users.id"), primary_key=True)
comment_id=Column(Integer, ForeignKey("comments.id"), primary_key=True)
Index('fki_comment_save_relationship_comment_fkey', comment_id)

View file

@ -20,6 +20,8 @@ class Sub(Base):
bannerurl = Column(String)
css = Column(String)
Index('subs_idx', name)
blocks = relationship("SubBlock", lazy="dynamic", primaryjoin="SubBlock.sub==Sub.name", viewonly=True)

View file

@ -8,5 +8,7 @@ class SubBlock(Base):
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
sub = Column(String, ForeignKey("subs.name"), primary_key=True)
Index('fki_sub_blocks_sub_fkey', sub)
def __repr__(self):
return f"<SubBlock(user_id={self.user_id}, sub={self.sub})>"

View file

@ -19,32 +19,32 @@ class Submission(Base):
__tablename__ = "submissions"
id = Column(Integer, primary_key=True)
author_id = Column(Integer, ForeignKey("users.id"))
edited_utc = Column(Integer, default=0)
created_utc = Column(Integer)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
edited_utc = Column(Integer, default=0, nullable=False)
created_utc = Column(Integer, nullable=False)
thumburl = Column(String)
is_banned = Column(Boolean, default=False)
is_banned = Column(Boolean, default=False, nullable=False)
bannedfor = Column(Boolean)
ghost = Column(Boolean, default=False)
views = Column(Integer, default=0)
deleted_utc = Column(Integer, default=0)
distinguish_level = Column(Integer, default=0)
ghost = Column(Boolean, default=False, nullable=False)
views = Column(Integer, default=0, nullable=False)
deleted_utc = Column(Integer, default=0, nullable=False)
distinguish_level = Column(Integer, default=0, nullable=False)
stickied = Column(String)
stickied_utc = Column(Integer)
sub = Column(String, ForeignKey("subs.name"))
is_pinned = Column(Boolean, default=False)
private = Column(Boolean, default=False)
club = Column(Boolean, default=False)
comment_count = Column(Integer, default=0)
is_pinned = Column(Boolean, default=False, nullable=False)
private = Column(Boolean, default=False, nullable=False)
club = Column(Boolean, default=False, nullable=False)
comment_count = Column(Integer, default=0, nullable=False)
is_approved = Column(Integer, ForeignKey("users.id"))
over_18 = Column(Boolean, default=False)
is_bot = Column(Boolean, default=False)
upvotes = Column(Integer, default=1)
downvotes = Column(Integer, default=0)
over_18 = Column(Boolean, default=False, nullable=False)
is_bot = Column(Boolean, default=False, nullable=False)
upvotes = Column(Integer, default=1, nullable=False)
downvotes = Column(Integer, default=0, nullable=False)
realupvotes = Column(Integer, default=1)
app_id=Column(Integer, ForeignKey("oauth_apps.id"))
title = Column(String)
title_html = Column(String)
title = Column(String, nullable=False)
title_html = Column(String, nullable=False)
url = Column(String)
body = Column(String)
body_html = Column(String)
@ -53,6 +53,18 @@ class Submission(Base):
embed_url = Column(String)
filter_state = Column(String)
Index('fki_submissions_approver_fkey', is_approved)
Index('post_app_id_idx', app_id)
Index('subimssion_binary_group_idx', is_banned, deleted_utc, over_18)
Index('submission_isbanned_idx', is_banned)
Index('submission_isdeleted_idx', deleted_utc)
Index('submission_new_sort_idx', is_banned, deleted_utc, created_utc.desc(), over_18)
Index('submission_pinned_idx', is_pinned)
Index('submissions_author_index', author_id)
Index('submissions_created_utc_asc_idx', created_utc.nullsfirst())
Index('submissions_created_utc_desc_idx', created_utc.desc())
Index('submissions_over18_index', over_18)
author = relationship("User", primaryjoin="Submission.author_id==User.id")
oauth_app = relationship("OauthApp", viewonly=True)
approved_by = relationship("User", uselist=False, primaryjoin="Submission.is_approved==User.id", viewonly=True)
@ -370,8 +382,6 @@ class Submission(Base):
body = self.body_html or ""
body = censor_slurs(body, v)
if v:
body = body.replace("old.reddit.com", v.reddit)
@ -436,8 +446,6 @@ class Submission(Base):
if not body: return ""
body = censor_slurs(body, v)
if v:
body = body.replace("old.reddit.com", v.reddit)
@ -457,8 +465,6 @@ class Submission(Base):
elif self.title_html: title = self.title_html
else: title = self.title
title = censor_slurs(title, v)
return title
@lazy
@ -468,8 +474,6 @@ class Submission(Base):
else: return f'{CC} MEMBERS ONLY'
else: title = self.title
title = censor_slurs(title, v)
return title
@property

View file

@ -7,6 +7,8 @@ class Subscription(Base):
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
Index('subscription_user_index', user_id)
user = relationship("User", uselist=False, viewonly=True)
def __init__(self, *args, **kwargs):

View file

@ -29,61 +29,68 @@ cardview = bool(int(environ.get("CARD_VIEW", 1)))
class User(Base):
__tablename__ = "users"
__table_args__ = (
UniqueConstraint('bannerurl', name='one_banner'),
UniqueConstraint('discord_id', name='one_discord_account'),
UniqueConstraint('id', name='uid_unique'),
UniqueConstraint('original_username', name='users_original_username_key'),
UniqueConstraint('username', name='users_username_key'),
)
id = Column(Integer, primary_key=True)
username = Column(String)
namecolor = Column(String, default=DEFAULT_COLOR)
username = Column(String(length=255), nullable=False)
namecolor = Column(String(length=6), default=DEFAULT_COLOR, nullable=False)
background = Column(String)
customtitle = Column(String)
customtitleplain = deferred(Column(String))
titlecolor = Column(String, default=DEFAULT_COLOR)
theme = Column(String, default=defaulttheme)
themecolor = Column(String, default=DEFAULT_COLOR)
cardview = Column(Boolean, default=cardview)
titlecolor = Column(String(length=6), default=DEFAULT_COLOR, nullable=False)
theme = Column(String, default=defaulttheme, nullable=False)
themecolor = Column(String, default=DEFAULT_COLOR, nullable=False)
cardview = Column(Boolean, default=cardview, nullable=False)
song = Column(String)
highres = Column(String)
profileurl = Column(String)
bannerurl = Column(String)
house = Column(String)
patron = Column(Integer, default=0)
patron_utc = Column(Integer, default=0)
patron = Column(Integer, default=0, nullable=False)
patron_utc = Column(Integer, default=0, nullable=False)
verified = Column(String)
verifiedcolor = Column(String)
marseyawarded = Column(Integer)
rehab = Column(Integer)
longpost = Column(Integer)
winnings = Column(Integer, default=0)
winnings = Column(Integer, default=0, nullable=False)
unblockable = Column(Boolean)
bird = Column(Integer)
email = deferred(Column(String))
css = deferred(Column(String))
profilecss = deferred(Column(String))
passhash = deferred(Column(String))
post_count = Column(Integer, default=0)
comment_count = Column(Integer, default=0)
received_award_count = Column(Integer, default=0)
created_utc = Column(Integer)
admin_level = Column(Integer, default=0)
coins_spent = Column(Integer, default=0)
lootboxes_bought = Column(Integer, default=0)
agendaposter = Column(Integer, default=0)
changelogsub = Column(Boolean, default=False)
is_activated = Column(Boolean, default=False)
passhash = deferred(Column(String, nullable=False))
post_count = Column(Integer, default=0, nullable=False)
comment_count = Column(Integer, default=0, nullable=False)
received_award_count = Column(Integer, default=0, nullable=False)
created_utc = Column(Integer, nullable=False)
admin_level = Column(Integer, default=0, nullable=False)
coins_spent = Column(Integer, default=0, nullable=False)
lootboxes_bought = Column(Integer, default=0, nullable=False)
agendaposter = Column(Integer, default=0, nullable=False)
changelogsub = Column(Boolean, default=False, nullable=False)
is_activated = Column(Boolean, default=False, nullable=False)
shadowbanned = Column(String)
over_18 = Column(Boolean, default=False)
hidevotedon = Column(Boolean, default=False)
highlightcomments = Column(Boolean, default=True)
slurreplacer = Column(Boolean, default=True)
over_18 = Column(Boolean, default=False, nullable=False)
hidevotedon = Column(Boolean, default=False, nullable=False)
highlightcomments = Column(Boolean, default=True, nullable=False)
slurreplacer = Column(Boolean, default=True, nullable=False)
flairchanged = Column(Integer)
newtab = Column(Boolean, default=False)
newtabexternal = Column(Boolean, default=True)
reddit = Column(String, default='old.reddit.com')
newtab = Column(Boolean, default=False, nullable=False)
newtabexternal = Column(Boolean, default=True, nullable=False)
reddit = Column(String, default='old.reddit.com', nullable=False)
nitter = Column(Boolean)
mute = Column(Boolean)
unmutable = Column(Boolean)
eye = Column(Boolean)
alt = Column(Boolean)
frontsize = Column(Integer, default=25)
controversial = Column(Boolean, default=False)
frontsize = Column(Integer, default=25, nullable=False)
controversial = Column(Boolean, default=False, nullable=False)
bio = deferred(Column(String))
bio_html = Column(String)
sig = deferred(Column(String))
@ -97,28 +104,49 @@ class User(Base):
friends_html = deferred(Column(String))
enemies = deferred(Column(String))
enemies_html = deferred(Column(String))
is_banned = Column(Integer, default=0)
unban_utc = Column(Integer, default=0)
is_banned = Column(Integer, default=0, nullable=False)
unban_utc = Column(Integer, default=0, nullable=False)
ban_reason = deferred(Column(String))
club_allowed = Column(Boolean)
login_nonce = Column(Integer, default=0)
login_nonce = Column(Integer, default=0, nullable=False)
reserved = deferred(Column(String))
coins = Column(Integer, default=0)
truecoins = Column(Integer, default=0)
procoins = Column(Integer, default=0)
coins = Column(Integer, default=0, nullable=False)
truecoins = Column(Integer, default=0, nullable=False)
procoins = Column(Integer, default=0, nullable=False)
mfa_secret = deferred(Column(String))
is_private = Column(Boolean, default=False)
stored_subscriber_count = Column(Integer, default=0)
defaultsortingcomments = Column(String, default="new")
defaultsorting = Column(String, default="new")
defaulttime = Column(String, default=defaulttimefilter)
is_nofollow = Column(Boolean, default=False)
is_private = Column(Boolean, default=False, nullable=False)
stored_subscriber_count = Column(Integer, default=0, nullable=False)
defaultsortingcomments = Column(String, default="new", nullable=False)
defaultsorting = Column(String, default="new", nullable=False)
defaulttime = Column(String, default=defaulttimefilter, nullable=False)
is_nofollow = Column(Boolean, default=False, nullable=False)
custom_filter_list = Column(String)
discord_id = Column(String)
ban_evade = Column(Integer, default=0)
ban_evade = Column(Integer, default=0, nullable=False)
original_username = deferred(Column(String))
referred_by = Column(Integer, ForeignKey("users.id"))
subs_created = Column(Integer, default=0)
subs_created = Column(Integer, default=0, nullable=False)
Index(
'users_original_username_trgm_idx',
original_username,
postgresql_using='gin',
postgresql_ops={'description':'gin_trgm_ops'}
)
Index(
'users_username_trgm_idx',
username,
postgresql_using='gin',
postgresql_ops={'description':'gin_trgm_ops'}
)
Index('discord_id_idx', discord_id)
Index('fki_user_referrer_fkey', referred_by)
Index('user_banned_idx', is_banned)
Index('user_private_idx', is_private)
Index('users_created_utc_index', created_utc)
Index('users_subs_idx', stored_subscriber_count)
Index('users_unbanutc_idx', unban_utc.desc())
badges = relationship("Badge", viewonly=True)
subscriptions = relationship("Subscription", viewonly=True)
@ -495,8 +523,7 @@ class User(Base):
@property
@lazy
def profile_url(self):
if self.agendaposter: return f"{SITE_FULL}/assets/images/astolfo.webp?v=1"
if self.profileurl:
if self.profileurl:
if self.profileurl.startswith('/'): return SITE_FULL + self.profileurl
return self.profileurl
return f"{SITE_FULL}/assets/images/default-profile-pic.webp?v=1008"

View file

@ -8,6 +8,8 @@ class UserBlock(Base):
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
target_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
Index('block_target_idx', target_id)
user = relationship("User", primaryjoin="User.id==UserBlock.user_id", viewonly=True)
target = relationship("User", primaryjoin="User.id==UserBlock.target_id", viewonly=True)

View file

@ -10,7 +10,9 @@ class ViewerRelationship(Base):
user_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
viewer_id = Column(Integer, ForeignKey('users.id'), primary_key=True)
last_view_utc = Column(Integer)
last_view_utc = Column(Integer, nullable=False)
Index('fki_view_viewer_fkey', viewer_id)
viewer = relationship("User", primaryjoin="ViewerRelationship.viewer_id == User.id", viewonly=True)

View file

@ -11,10 +11,13 @@ class Vote(Base):
submission_id = Column(Integer, ForeignKey("submissions.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = Column(Integer)
vote_type = Column(Integer, nullable=False)
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
real = Column(Boolean, default=True)
created_utc = Column(Integer)
real = Column(Boolean, default=True, nullable=False)
created_utc = Column(Integer, nullable=False)
Index('votes_type_index', vote_type)
Index('vote_user_index', user_id)
user = relationship("User", lazy="subquery", viewonly=True)
post = relationship("Submission", lazy="subquery", viewonly=True)
@ -52,10 +55,13 @@ class CommentVote(Base):
comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True)
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
vote_type = Column(Integer)
vote_type = Column(Integer, nullable=False)
app_id = Column(Integer, ForeignKey("oauth_apps.id"))
real = Column(Boolean, default=True)
created_utc = Column(Integer)
real = Column(Boolean, default=True, nullable=False)
created_utc = Column(Integer, nullable=False)
Index('cvote_user_index', user_id)
Index('commentvotes_comments_type_index', vote_type)
user = relationship("User", lazy="subquery")
comment = relationship("Comment", lazy="subquery", viewonly=True)

7
files/cli.py Normal file
View file

@ -0,0 +1,7 @@
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from .__main__ import app
import files.classes
db = SQLAlchemy(app)
migrate = Migrate(app, db)

View file

@ -28,76 +28,11 @@ AJ_REPLACEMENTS = {
}
SLURS = {
"retarded": "r-slurred",
"retard": "r-slur",
"gayfag": "gaystrag",
"poorfag": "poorstrag",
"richfag": "richstrag",
"newfag": "newstrag",
"oldfag": "oldstrag",
"faggotry": "cute twinkry",
"faggot": "cute twink",
"pedophile": "libertarian",
"kill yourself": "keep yourself safe",
"n1gger": "BIPOC",
"nlgger": "BIPOC",
"nigger": "BIPOC",
"steve akins": "penny verity oaken",
"trannie": "🚂🚃🚃",
"tranny": "🚂🚃🚃",
"troon": "🚂🚃🚃",
"nonewnormal": "HorseDewormerAddicts",
"kikery": "https://sciencedirect.com/science/article/abs/pii/S016028960600033X",
"kike": "https://sciencedirect.com/science/article/abs/pii/S016028960600033X",
"latinos": "latinx",
"latino": "latinx",
"latinas": "latinx",
"latina": "latinx",
"hispanics": "latinx",
"hispanic": "latinx",
"uss liberty incident": "tragic accident aboard the USS Liberty",
"lavon affair": "Lavon Misunderstanding",
"shylock": "Israeli friend",
"mohammad": "Prophet Mohammad (PBUH)",
"muhammad": "Prophet Mohammad (PBUH)",
"i hate marsey": "i love marsey",
"dancing israelis": "i love Israel",
"sodomite": "total dreamboat",
"pajeet": "sexy Indian dude",
"landlord": "landchad",
"tenant": "renthog",
"renter": "rentoid",
"autistic": "neurodivergent",
"holohoax": "i tried to claim the Holocaust didn't happen because I am a pencil-dicked imbecile and the word filter caught me lol",
"groomercord": "discord (actually a pretty cool service)",
"pedocord": "discord (actually a pretty cool service)",
"i hate carp": "i love Carp",
"manlet": "little king",
"gamer": "g*mer",
"journalist": "journ*list",
"journalism": "journ*lism",
"wuhan flu": "SARS-CoV-2 syndemic",
"china flu": "SARS-CoV-2 syndemic",
"china virus": "SARS-CoV-2 syndemic",
"kung flu": "SARS-CoV-2 syndemic",
"elon musk": "rocket daddy",
"fake and gay": "fake and straight",
" rapist ": " male feminist ",
" pedo ": " libertarian ",
" kys ": " keep yourself safe ",
" fag ": " cute twink ",
}
single_words = "|".join([slur.lower() for slur in SLURS.keys()])
LONGPOST_REPLIES = ('Wow, you must be a JP fan.', 'This is one of the worst posts I have EVER seen. Delete it.', "No, don't reply like this, please do another wall of unhinged rant please.", '# 😴😴😴', "Ma'am we've been over this before. You need to stop.", "I've known more coherent downies.", "Your pulitzer's in the mail", "That's great and all, but I asked for my burger without cheese.", 'That degree finally paying off', "That's nice sweaty. Why don't you have a seat in the time out corner with Pizzashill until you calm down, then you can have your Capri Sun.", "All them words won't bring your pa back.", "You had a chance to not be completely worthless, but it looks like you threw it away. At least you're consistent.", 'Some people are able to display their intelligence by going on at length on a subject and never actually saying anything. This ability is most common in trades such as politics, public relations, and law. You have impressed me by being able to best them all, while still coming off as an absolute idiot.', "You can type 10,000 characters and you decided that these were the one's that you wanted.", 'Have you owned the libs yet?', "I don't know what you said, because I've seen another human naked.", 'Impressive. Normally people with such severe developmental disabilities struggle to write much more than a sentence or two. He really has exceded our expectations for the writing portion. Sadly the coherency of his writing, along with his abilities in the social skills and reading portions, are far behind his peers with similar disabilities.', "This is a really long way of saying you don't fuck.", "Sorry ma'am, looks like his delusions have gotten worse. We'll have to admit him.", ':#marseywoah:', 'If only you could put that energy into your relationships', 'Posts like this is why I do Heroine.', 'still unemployed then?', 'K', 'look im gunna have 2 ask u 2 keep ur giant dumps in the toilet not in my replys 😷😷😷', "Mommy is soooo proud of you, sweaty. Let's put this sperg out up on the fridge with all your other failures.", "Good job bobby, here's a star", "That was a mistake. You're about to find out the hard way why.", f'You sat down and wrote all this shit. You could have done so many other things with your life. What happened to your life that made you decide writing novels of bullshit on {SITE} was the best option?', "I don't have enough spoons to read this shit", "All those words won't bring daddy back.", 'OUT!', "Damn, you're really mad over this, but thanks for the effort you put into typing that all out! Sadly I won't read it all.", "Jesse what the fuck are you talking about??", "▼you're fucking bananas if you think I'm reading all that, take my downvote and shut up idiot", "Are you feeling okay bud?")
AGENDAPOSTER_PHRASE = 'trans lives matter'
AGENDAPOSTER_MSG = """Hi @{username},\n\nYour {type} has been automatically removed because you forgot to include `{AGENDAPOSTER_PHRASE}`.\n\nDon't worry, we're here to help! We won't let you post or comment anything that doesn't express your love and acceptance towards the trans community. Feel free to resubmit your {type} with `{AGENDAPOSTER_PHRASE}` included. \n\n*This is an automated message; if you need help, you can message us [here](/contact).*"""
NOTIFICATIONS_ID = 1
AUTOJANNY_ID = 2
SNAPPY_ID = 3
@ -369,14 +304,6 @@ AWARDS = {
"color": "text-blue",
"price": 1500
},
"agendaposter": {
"kind": "agendaposter",
"title": "Chud",
"description": "Forces the chud theme on the recipient for 24 hours.",
"icon": "fas fa-snooze",
"color": "text-purple",
"price": 2500
},
"deflector": {
"kind": "deflector",
"title": "Deflector",
@ -591,38 +518,18 @@ emoji_regex3 = re.compile(f"(?<!\"):([!@{valid_username_chars}]{{1,31}}?):", fla
snappy_url_regex = re.compile('<a href=\"(https?:\/\/[a-z]{1,20}\.[\w:~,()\-.#&\/=?@%;+]{5,250})\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">([\w:~,()\-.#&\/=?@%;+]{5,250})<\/a>', flags=re.A)
email_regex = re.compile('([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,100})+', flags=re.A)
# Technically this allows stuff that is not a valid email address, but realistically
# we care "does this email go to the correct person" rather than "is this email
# address syntactically valid", so if we care we should be sending a confirmation
# link, and otherwise should be pretty liberal in what we accept here.
email_regex = re.compile('[^@]+@[^@]+\.[^@]+', flags=re.A)
utm_regex = re.compile('utm_[a-z]+=[a-z0-9_]+&', flags=re.A)
utm_regex2 = re.compile('[?&]utm_[a-z]+=[a-z0-9_]+', flags=re.A)
slur_regex = re.compile(f"({single_words})(?![^<]*>)", flags=re.I|re.A)
slur_regex_upper = re.compile(f"({single_words.upper()})(?![^<]*>)", flags=re.A)
torture_regex = re.compile('(^|\s)(i|me) ', flags=re.I|re.A)
torture_regex2 = re.compile("(^|\s)i'm ", flags=re.I|re.A)
def sub_matcher(match):
return SLURS[match.group(0).lower()]
def sub_matcher_upper(match):
return SLURS[match.group(0).lower()].upper()
def censor_slurs(body, logged_user):
if not logged_user or logged_user == 'chat' or logged_user.slurreplacer:
body = slur_regex_upper.sub(sub_matcher_upper, body)
body = slur_regex.sub(sub_matcher, body)
return body
def torture_ap(body, username):
for k, l in AJ_REPLACEMENTS.items():
body = body.replace(k, l)
body = torture_regex.sub(rf'\1@{username} ', body)
body = torture_regex2.sub(rf'\1@{username} is ', body)
return body
YOUTUBE_KEY = environ.get("YOUTUBE_KEY", "").strip()
ADMIGGERS = (37696,37697,37749,37833,37838)
ADMINISTRATORS = (37696, 37697, 37749, 37833, 37838)
proxies = {"http":"http://127.0.0.1:18080","https":"http://127.0.0.1:18080"}

View file

@ -49,4 +49,4 @@ def timestamp(timestamp):
@app.context_processor
def inject_constants():
return {"environ":environ, "SITE":SITE, "SITE_NAME":SITE_NAME, "SITE_FULL":SITE_FULL, "AUTOJANNY_ID":AUTOJANNY_ID, "NOTIFICATIONS_ID":NOTIFICATIONS_ID, "PUSHER_ID":PUSHER_ID, "CC":CC, "CC_TITLE":CC_TITLE, "listdir":listdir, "MOOSE_ID":MOOSE_ID, "AEVANN_ID":AEVANN_ID, "PIZZASHILL_ID":PIZZASHILL_ID, "config":app.config.get, "DEFAULT_COLOR":DEFAULT_COLOR, "COLORS":COLORS, "ADMIGGERS":ADMIGGERS}
return {"environ":environ, "SITE":SITE, "SITE_NAME":SITE_NAME, "SITE_FULL":SITE_FULL, "AUTOJANNY_ID":AUTOJANNY_ID, "NOTIFICATIONS_ID":NOTIFICATIONS_ID, "PUSHER_ID":PUSHER_ID, "CC":CC, "CC_TITLE":CC_TITLE, "listdir":listdir, "MOOSE_ID":MOOSE_ID, "AEVANN_ID":AEVANN_ID, "PIZZASHILL_ID":PIZZASHILL_ID, "config":app.config.get, "DEFAULT_COLOR":DEFAULT_COLOR, "COLORS":COLORS, "ADMIGGERS":ADMINISTRATORS}

View file

@ -191,26 +191,26 @@ def sanitize(sanitized, alert=False, comment=False, edit=False):
marseys_used = set()
emojis = list(emoji_regex.finditer(sanitized))
if len(emojis) > 20: edit = True
# emojis = list(emoji_regex.finditer(sanitized))
# if len(emojis) > 20: edit = True
captured = []
for i in emojis:
if i.group(0) in captured: continue
captured.append(i.group(0))
# captured = []
# for i in emojis:
# if i.group(0) in captured: continue
# captured.append(i.group(0))
old = i.group(0)
if 'marseylong1' in old or 'marseylong2' in old or 'marseyllama1' in old or 'marseyllama2' in old: new = old.lower().replace(">", " class='mb-0'>")
else: new = old.lower()
# old = i.group(0)
# if 'marseylong1' in old or 'marseylong2' in old or 'marseyllama1' in old or 'marseyllama2' in old: new = old.lower().replace(">", " class='mb-0'>")
# else: new = old.lower()
new = render_emoji(new, emoji_regex2, edit, marseys_used, True)
# new = render_emoji(new, emoji_regex2, edit, marseys_used, True)
sanitized = sanitized.replace(old, new)
# sanitized = sanitized.replace(old, new)
emojis = list(emoji_regex2.finditer(sanitized))
if len(emojis) > 20: edit = True
# emojis = list(emoji_regex2.finditer(sanitized))
# if len(emojis) > 20: edit = True
sanitized = render_emoji(sanitized, emoji_regex2, edit, marseys_used)
# sanitized = render_emoji(sanitized, emoji_regex2, edit, marseys_used)
for rd in ["://reddit.com", "://new.reddit.com", "://www.reddit.com", "://redd.it", "://libredd.it", "://teddit.net"]:
sanitized = sanitized.replace(rd, "://old.reddit.com")
@ -315,7 +315,7 @@ def filter_emojis_only(title, edit=False, graceful=False):
title = title.replace('','').replace('','').replace("\ufeff", "").replace("𒐪","").replace("\n", "").replace("\r", "").replace("\t", "").replace("&", "&amp;").replace('<','&lt;').replace('>','&gt;').replace('"', '&quot;').replace("'", "&#039;").strip()
title = render_emoji(title, emoji_regex3, edit)
# title = render_emoji(title, emoji_regex3, edit)
title = strikethrough_regex.sub(r'<del>\1</del>', title)

View file

@ -950,77 +950,6 @@ def admin_removed_comments(v):
)
@app.post("/agendaposter/<user_id>")
@admin_level_required(2)
def agendaposter(user_id, v):
user = g.db.query(User).filter_by(id=user_id).one_or_none()
days = request.values.get("days") or 30
expiry = float(days)
expiry = int(time.time() + expiry*60*60*24)
user.agendaposter = expiry
g.db.add(user)
for alt in user.alts:
if alt.admin_level: return {"error": "User is an admin!"}
alt.agendaposter = expiry
g.db.add(alt)
note = f"for {days} days"
ma = ModAction(
kind="agendaposter",
user_id=v.id,
target_user_id=user.id,
note = note
)
g.db.add(ma)
if not user.has_badge(28):
badge = Badge(user_id=user.id, badge_id=28)
g.db.add(badge)
g.db.flush()
send_notification(user.id, f"@AutoJanny has given you the following profile badge:\n\n![]({badge.path})\n\n{badge.name}")
send_repeatable_notification(user.id, f"@{v.username} has marked you as a chud ({note}).")
g.db.commit()
return redirect(user.url)
@app.post("/unagendaposter/<user_id>")
@admin_level_required(2)
def unagendaposter(user_id, v):
user = g.db.query(User).filter_by(id=user_id).one_or_none()
user.agendaposter = 0
g.db.add(user)
for alt in user.alts:
alt.agendaposter = 0
g.db.add(alt)
ma = ModAction(
kind="unagendaposter",
user_id=v.id,
target_user_id=user.id
)
g.db.add(ma)
badge = user.has_badge(28)
if badge: g.db.delete(badge)
send_repeatable_notification(user.id, f"@{v.username} has unmarked you as a chud.")
g.db.commit()
return {"message": "Chud theme disabled!"}
@app.post("/shadowban/<user_id>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(2)
@ -1538,9 +1467,6 @@ def api_unban_comment(c_id, v):
comment = g.db.query(Comment).filter_by(id=c_id).one_or_none()
if not comment: abort(404)
if comment.author.agendaposter and AGENDAPOSTER_PHRASE not in comment.body.lower():
return {"error": "You can't bypass the chud award!"}
if comment.is_banned:
ma=ModAction(
kind="unban_comment",

View file

@ -213,18 +213,6 @@ def award_post(pid, v):
cache.delete_memoized(frontlist)
else: post.stickied_utc = t
g.db.add(post)
elif kind == "agendaposter" and not (author.agendaposter and author.agendaposter == 0):
if author.marseyawarded:
return {"error": "This user is the under the effect of a conflicting award: Marsey award."}, 404
if author.agendaposter and time.time() < author.agendaposter: author.agendaposter += 86400
else: author.agendaposter = int(time.time()) + 86400
if not author.has_badge(28):
badge = Badge(user_id=author.id, badge_id=28)
g.db.add(badge)
g.db.flush()
send_notification(author.id, f"@AutoJanny has given you the following profile badge:\n\n![]({badge.path})\n\n{badge.name}")
elif kind == "flairlock":
new_name = note[:100].replace("𒐪","")
if not new_name and author.flairchanged:
@ -451,18 +439,6 @@ def award_comment(cid, v):
c.is_pinned_utc = None
else: c.is_pinned_utc = t
g.db.add(c)
elif kind == "agendaposter" and not (author.agendaposter and author.agendaposter == 0):
if author.marseyawarded:
return {"error": "This user is the under the effect of a conflicting award: Marsey award."}, 404
if author.agendaposter and time.time() < author.agendaposter: author.agendaposter += 86400
else: author.agendaposter = int(time.time()) + 86400
if not author.has_badge(28):
badge = Badge(user_id=author.id, badge_id=28)
g.db.add(badge)
g.db.flush()
send_notification(author.id, f"@AutoJanny has given you the following profile badge:\n\n![]({badge.path})\n\n{badge.name}")
elif kind == "flairlock":
new_name = note[:100].replace("𒐪","")
if not new_name and author.flairchanged:

View file

@ -55,7 +55,7 @@ def speak(data, v):
"namecolor": v.namecolor,
"text": text,
"text_html": text_html,
"text_censored": censor_slurs(text_html, 'chat'),
"text_censored": text,
"time": int(time.time())
}

View file

@ -7,7 +7,6 @@ from files.helpers.blackjack import *
from files.helpers.treasure import *
from files.classes import *
from files.routes.front import comment_idlist
from files.routes.static import marsey_list
from pusher_push_notifications import PushNotifications
from flask import *
from files.__main__ import app, limiter
@ -189,7 +188,7 @@ def api_comment(v):
with open(f"snappy_{SITE_NAME}.txt", "a", encoding="utf-8") as f:
f.write('\n{[para]}\n' + body)
if parent_post.id not in ADMIGGERS:
if parent_post.id not in ADMINISTRATORS:
if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
return {"error":"You have to type more than 280 characters!"}, 403
elif v.bird and len(body) > 140:
@ -243,59 +242,6 @@ def api_comment(v):
requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data={'files': [f"https://{request.host}/assets/images/badges/{badge.id}.webp"]}, timeout=5)
except Exception as e:
return {"error": str(e)}, 400
elif v.admin_level > 2 and parent_post.id == 37838:
try:
marsey = loads(body.lower())
name = marsey["name"]
if not marsey_regex.fullmatch(name): return {"error": "Invalid name!"}, 400
existing = g.db.query(Marsey.name).filter_by(name=name).one_or_none()
if existing: return {"error": "A marsey with this name already exists!"}, 403
tags = marsey["tags"]
if not tags_regex.fullmatch(tags): return {"error": "Invalid tags!"}, 400
if "author" in marsey: user = get_user(marsey["author"])
elif "author_id" in marsey: user = get_account(marsey["author_id"])
else: abort(400)
filename = f'files/assets/images/emojis/{name}.webp'
copyfile(oldname, filename)
process_image(filename, 200)
marsey = Marsey(name=name, author_id=user.id, tags=tags, count=0)
g.db.add(marsey)
g.db.flush()
all_by_author = g.db.query(Marsey.author_id).filter_by(author_id=user.id).count()
if all_by_author >= 10 and not user.has_badge(16):
new_badge = Badge(badge_id=16, user_id=user.id)
g.db.add(new_badge)
g.db.flush()
if v.id != user.id:
text = f"@AutoJanny has given you the following profile badge:\n\n![]({new_badge.path})\n\n{new_badge.name}"
send_notification(user.id, text)
elif all_by_author < 10 and not user.has_badge(17):
new_badge = Badge(badge_id=17, user_id=user.id)
g.db.add(new_badge)
g.db.flush()
if v.id != user.id:
text = f"@AutoJanny has given you the following profile badge:\n\n![]({new_badge.path})\n\n{new_badge.name}"
send_notification(user.id, text)
requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data={'files': [f"https://{request.host}/e/{name}.webp"]}, timeout=5)
cache.delete_memoized(marsey_list)
except Exception as e:
return {"error": str(e)}, 400
body += f"\n\n![]({image})"
elif file.content_type.startswith('video/'):
file.save("video.mp4")
@ -311,13 +257,9 @@ def api_comment(v):
body += f"\n\n{url}"
else: return {"error": "Image/Video files only"}, 400
if v.agendaposter and not v.marseyawarded and parent_post.id not in ADMIGGERS:
body = torture_ap(body, v.username)
body_html = sanitize(body, comment=True)
if parent_post.id not in ADMIGGERS and '!slots' not in body.lower() and '!blackjack' not in body.lower() and '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower():
if parent_post.id not in ADMINISTRATORS and '!slots' not in body.lower() and '!blackjack' not in body.lower() and '!wordle' not in body.lower():
existing = g.db.query(Comment.id).filter(Comment.author_id == v.id,
Comment.deleted_utc == 0,
Comment.parent_comment_id == parent_comment_id,
@ -331,7 +273,7 @@ def api_comment(v):
is_bot = bool(request.headers.get("Authorization"))
if '!slots' not in body.lower() and '!blackjack' not in body.lower() and '!wordle' not in body.lower() and parent_post.id not in ADMIGGERS and not is_bot and not v.marseyawarded and AGENDAPOSTER_PHRASE not in body.lower() and len(body) > 10:
if '!slots' not in body.lower() and '!blackjack' not in body.lower() and '!wordle' not in body.lower() and parent_post.id not in ADMINISTRATORS and not is_bot and not v.marseyawarded and len(body) > 10:
now = int(time.time())
cutoff = now - 60 * 60 * 24
@ -419,39 +361,7 @@ def api_comment(v):
g.db.add(c_choice)
if parent_post.id not in ADMIGGERS:
if v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in c.body.lower():
c.is_banned = True
c.ban_reason = "AutoJanny"
g.db.add(c)
body = AGENDAPOSTER_MSG.format(username=v.username, type='comment', AGENDAPOSTER_PHRASE=AGENDAPOSTER_PHRASE)
body_jannied_html = sanitize(body)
c_jannied = Comment(author_id=NOTIFICATIONS_ID,
parent_submission=parent_submission,
distinguish_level=6,
parent_comment_id=c.id,
level=level+1,
is_bot=True,
body_html=body_jannied_html,
top_comment_id=c.top_comment_id,
ghost=parent_post.ghost
)
g.db.add(c_jannied)
g.db.flush()
n = Notification(comment_id=c_jannied.id, user_id=v.id)
g.db.add(n)
if parent_post.id not in ADMINISTRATORS:
if not v.shadowbanned:
notify_users = NOTIFY_USERS(body, v)
@ -500,7 +410,7 @@ def api_comment(v):
check_for_blackjack_commands(body, v, c)
if not c.slots_result and not c.blackjack_result and v.marseyawarded and parent_post.id not in ADMIGGERS and marseyaward_body_regex.search(body_html):
if not c.slots_result and not c.blackjack_result and v.marseyawarded and parent_post.id not in ADMINISTRATORS and marseyaward_body_regex.search(body_html):
return {"error":"You can only type marseys!"}, 403
check_for_treasure(body, c)
@ -540,9 +450,6 @@ def edit_comment(cid, v):
elif v.bird and len(body) > 140:
return {"error":"You have to type less than 140 characters!"}, 403
if v.agendaposter and not v.marseyawarded:
body = torture_ap(body, v.username)
if not c.options:
for i in poll_regex.finditer(body):
body = body.replace(i.group(0), "")
@ -571,7 +478,7 @@ def edit_comment(cid, v):
body_html = sanitize(body, edit=True)
if '!slots' not in body.lower() and '!blackjack' not in body.lower() and '!wordle' not in body.lower() and AGENDAPOSTER_PHRASE not in body.lower():
if '!slots' not in body.lower() and '!blackjack' not in body.lower() and '!wordle' not in body.lower():
now = int(time.time())
cutoff = now - 60 * 60 * 24
@ -645,39 +552,6 @@ def edit_comment(cid, v):
notif = Notification(comment_id=c.id, user_id=CARP_ID)
g.db.add(notif)
if v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in c.body.lower() and not c.is_banned:
c.is_banned = True
c.ban_reason = "AutoJanny"
g.db.add(c)
body = AGENDAPOSTER_MSG.format(username=v.username, type='comment', AGENDAPOSTER_PHRASE=AGENDAPOSTER_PHRASE)
body_jannied_html = sanitize(body)
c_jannied = Comment(author_id=NOTIFICATIONS_ID,
parent_submission=c.parent_submission,
distinguish_level=6,
parent_comment_id=c.id,
level=c.level+1,
is_bot=True,
body_html=body_jannied_html,
top_comment_id=c.top_comment_id,
ghost=c.ghost
)
g.db.add(c_jannied)
g.db.flush()
n = Notification(comment_id=c_jannied.id, user_id=v.id)
g.db.add(n)
if int(time.time()) - c.created_utc > 60 * 3: c.edited_utc = int(time.time())
g.db.add(c)

View file

@ -227,14 +227,6 @@ def front_all(v, sub=None, subdomain=None):
g.db.add(v)
g.db.commit()
if v.agendaposter and v.agendaposter < time.time():
v.agendaposter = 0
send_repeatable_notification(v.id, "Your chud theme has expired!")
g.db.add(v)
badge = v.has_badge(28)
if badge: g.db.delete(badge)
g.db.commit()
if v.flairchanged and v.flairchanged < time.time():
v.flairchanged = None
send_repeatable_notification(v.id, "Your flair lock has expired. You can now change your flair!")

View file

@ -321,7 +321,7 @@ def sign_up_post(v):
session["history"] = []
else: admin_level=0
profileurl = '/e/' + random.choice(marseys_const) + '.webp'
profileurl = '/e/feather.webp'
new_user = User(
username=username,
@ -332,7 +332,7 @@ def sign_up_post(v):
referred_by=ref_id or None,
ban_evade = int(any((x.is_banned or x.shadowbanned) and not x.unban_utc for x in g.db.query(User).filter(User.id.in_(session.get("history", []))).all() if x)),
profileurl=profileurl
)
)
g.db.add(new_user)
g.db.flush()

View file

@ -434,7 +434,6 @@ def edit_post(pid, v):
return {"error":"You have to type less than 140 characters!"}, 403
if title != p.title:
if v.id == p.author_id and v.agendaposter and not v.marseyawarded: title = torture_ap(title, v.username)
title_html = filter_emojis_only(title, edit=True)
@ -467,8 +466,6 @@ def edit_post(pid, v):
else: return {"error": "Image/Video files only"}, 400
if body != p.body:
if v.id == p.author_id and v.agendaposter and not v.marseyawarded: body = torture_ap(body, v.username)
if not p.options:
for i in poll_regex.finditer(body):
body = body.replace(i.group(0), "")
@ -498,7 +495,6 @@ def edit_post(pid, v):
if v.id == p.author_id and v.marseyawarded and marseyaward_body_regex.search(body_html):
return {"error":"You can only type marseys!"}, 403
p.body = body
if blackjack and any(i in f'{p.body} {p.title} {p.url}'.lower() for i in blackjack.split()):
@ -510,37 +506,6 @@ def edit_post(pid, v):
p.body_html = body_html
if v.id == p.author_id and v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in f'{p.body}{p.title}'.lower() and not p.is_banned:
p.is_banned = True
p.ban_reason = "AutoJanny"
g.db.add(p)
body = AGENDAPOSTER_MSG.format(username=v.username, type='post', AGENDAPOSTER_PHRASE=AGENDAPOSTER_PHRASE)
body_jannied_html = sanitize(body)
c_jannied = Comment(author_id=NOTIFICATIONS_ID,
parent_submission=p.id,
level=1,
over_18=False,
is_bot=True,
app_id=None,
is_pinned='AutoJanny',
distinguish_level=6,
body_html=body_jannied_html,
ghost=p.ghost
)
g.db.add(c_jannied)
g.db.flush()
c_jannied.top_comment_id = c_jannied.id
n = Notification(comment_id=c_jannied.id, user_id=v.id)
g.db.add(n)
if not p.private and not p.ghost:
notify_users = NOTIFY_USERS(f'{p.title} {p.body}', v)
if notify_users:
@ -789,21 +754,10 @@ def submit_post(v, sub=None):
if v.is_suspended: return error("You can't perform this action while banned.")
if v.agendaposter and not v.marseyawarded: title = torture_ap(title, v.username)
title_html = filter_emojis_only(title, graceful=True)
if v.marseyawarded and not marseyaward_title_regex.fullmatch(title_html):
return error("You can only type marseys!")
if len(title_html) > 1500: return error("Rendered title is too big!")
if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
return error("You have to type more than 280 characters!")
elif v.bird and len(body) > 140:
return error("You have to type less than 140 characters!")
embed = None
if url:
@ -889,7 +843,6 @@ def submit_post(v, sub=None):
if not title:
return error("Please enter a better title.")
elif len(title) > 500:
return error("There's a 500 character limit for titles.")
@ -969,8 +922,6 @@ def submit_post(v, sub=None):
choices.append(i.group(1))
body = body.replace(i.group(0), "")
if v.agendaposter and not v.marseyawarded: body = torture_ap(body, v.username)
if request.files.get("file2") and request.headers.get("cf-ipcountry") != "T1":
files = request.files.getlist('file2')[:4]
for file in files:
@ -995,9 +946,6 @@ def submit_post(v, sub=None):
body_html = sanitize(body)
if v.marseyawarded and marseyaward_body_regex.search(body_html):
return error("You can only type marseys!")
if len(body_html) > 40000: return error("Submission body_html too long! (max 40k characters)")
club = bool(request.values.get("club",""))
@ -1006,11 +954,6 @@ def submit_post(v, sub=None):
is_bot = bool(request.headers.get("Authorization"))
if request.values.get("ghost") and v.coins >= 100:
v.coins -= 100
ghost = True
else: ghost = False
post = Submission(
private=bool(request.values.get("private","")),
club=club,
@ -1025,7 +968,7 @@ def submit_post(v, sub=None):
title=title[:500],
title_html=title_html,
sub=sub,
ghost=ghost,
ghost=False,
filter_state='filtered' if v.admin_level == 0 else 'normal'
)
@ -1105,9 +1048,6 @@ def submit_post(v, sub=None):
if not post.thumburl and post.url:
gevent.spawn(thumbnail_thread, post.id)
if not post.private and not post.ghost:
notify_users = NOTIFY_USERS(f'{title} {body}', v)
@ -1127,40 +1067,6 @@ def submit_post(v, sub=None):
if post.club and not user.paid_dues: continue
add_notif(cid, user.id)
if v.agendaposter and not v.marseyawarded and AGENDAPOSTER_PHRASE not in f'{post.body}{post.title}'.lower():
post.is_banned = True
post.ban_reason = "AutoJanny"
body = AGENDAPOSTER_MSG.format(username=v.username, type='post', AGENDAPOSTER_PHRASE=AGENDAPOSTER_PHRASE)
body_jannied_html = sanitize(body)
c_jannied = Comment(author_id=NOTIFICATIONS_ID,
parent_submission=post.id,
level=1,
over_18=False,
is_bot=True,
app_id=None,
is_pinned='AutoJanny',
distinguish_level=6,
body_html=body_jannied_html,
)
g.db.add(c_jannied)
g.db.flush()
c_jannied.top_comment_id = c_jannied.id
n = Notification(comment_id=c_jannied.id, user_id=v.id)
g.db.add(n)
v.post_count = g.db.query(Submission.id).filter_by(author_id=v.id, is_banned=False, deleted_utc=0).count()
g.db.add(v)

View file

@ -611,8 +611,6 @@ def settings_css_get(v):
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@auth_required
def settings_css(v):
if v.agendaposter: return {"error": "Agendapostered users can't edit css!"}
css = request.values.get("css").strip().replace('\\', '').strip()[:4000]
v.css = css
g.db.add(v)

View file

@ -252,12 +252,6 @@ def grassed(v):
return render_template("grassed.html", v=v, users=users)
@app.get("/agendaposters")
@auth_required
def agendaposters(v):
users = [x for x in g.db.query(User).filter(User.agendaposter > 0).order_by(User.username).all()]
return render_template("agendaposters.html", v=v, users=users)
@app.get("/@<username>/upvoters")
@auth_required
@ -581,8 +575,6 @@ def message2(v, username):
if not message: return {"error": "Message is empty!"}
if 'linkedin.com' in message: return {"error": "This domain 'linkedin.com' is banned."}, 403
body_html = sanitize(message)
existing = g.db.query(Comment.id).filter(Comment.author_id == v.id,
@ -639,8 +631,6 @@ def messagereply(v):
if not message and not request.files.get("file"): return {"error": "Message is empty!"}
if 'linkedin.com' in message: return {"error": "this domain 'linkedin.com' is banned"}
id = int(request.values.get("parent_id"))
parent = get_comment(id, v=v)
user_id = parent.author.id

View file

@ -94,7 +94,9 @@ def api_vote_post(post_id, new, v):
post.author.truecoins += coin_delta
g.db.add(post.author)
if new == 1 and (v.agendaposter or v.shadowbanned or (v.is_banned and not v.unban_utc) or (v.profile_url.startswith('/e/') and not v.customtitle and v.namecolor == DEFAULT_COLOR)): real = False
DEFAULT_IMAGE = '/assets/images/default-profile-pic.webp'
if new == 1 and (v.shadowbanned or (v.is_banned and not v.unban_utc) or (v.profile_url == DEFAULT_IMAGE and not v.customtitle and v.namecolor == DEFAULT_COLOR)): real = False
else: real = True
vote = Vote(user_id=v.id,
@ -162,7 +164,9 @@ def api_vote_comment(comment_id, new, v):
comment.author.truecoins += coin_delta
g.db.add(comment.author)
if new == 1 and (v.agendaposter or v.shadowbanned or (v.is_banned and not v.unban_utc) or (v.profile_url.startswith('/e/') and not v.customtitle and v.namecolor == DEFAULT_COLOR)): real = False
DEFAULT_IMAGE = '/assets/images/default-profile-pic.webp'
if new == 1 and (v.shadowbanned or (v.is_banned and not v.unban_utc) or (v.profile_url == DEFAULT_IMAGE and not v.customtitle and v.namecolor == DEFAULT_COLOR)): real = False
else: real = True
vote = CommentVote(user_id=v.id,

View file

@ -27,7 +27,6 @@
<li><a href="/admin/users">Users Feed</a></li>
<li><a href="/admin/shadowbanned">Shadowbanned Users</a></li>
<li><a href="/banned">Permabanned Users</a></li>
<li><a href="/agendaposters">Users with Chud Theme</a></li>
<li><a href="/grassed">Currently Grassed Users</a></li>
</ul>

View file

@ -17,25 +17,7 @@
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=250">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css %}
{% if v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
{% else %}

View file

@ -170,7 +170,6 @@
<span id="typing-indicator"></span>
<span id="loading-indicator" class="d-none"></span>
</div>
<i class="btn btn-secondary mr-2 fas fa-smile-beam" style="padding-top:0.65rem" onclick="loadEmojis('input-text')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-placement="bottom" title="Add Emoji"></i>
<textarea id="input-text" minlength="1" maxlength="1000" type="text" class="form-control" placeholder="Message" autocomplete="off" autofocus rows="1"></textarea>
<button id="chatsend" onclick="send()" class="btn btn-primary ml-3" type="submit">Send</button>
</div>
@ -190,9 +189,6 @@
<script src="/chat.js?v=16"></script>
{% include "emoji_modal.html" %}
{% include "expanded_image_modal.html" %}
<script src="/assets/js/lozad.js?v=242"></script>
<script src="/assets/js/lite-youtube.js?v=240"></script>

View file

@ -287,7 +287,7 @@
</style>
{% endif %}
<div id="comment-text-{{c.id}}" class="comment-text mb-0 {% if c.author.agendaposter %}agendaposter{% endif %}">
<div id="comment-text-{{c.id}}" class="comment-text mb-0">
{{c.realbody(v) | safe}}
</div>
{% if c.parent_submission %}
@ -310,8 +310,6 @@
<small class="btn btn-secondary format m-0" aria-hidden="true" onclick="commentForm('comment-edit-body-{{c.id}}');getGif()" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF"><span class="font-weight-bolder text-uppercase">GIF</span></small>
&nbsp;
<small class="btn btn-secondary format m-0" aria-hidden="true" onclick="loadEmojis('comment-edit-body-{{c.id}}')" 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></small>
&nbsp;
<label class="btn btn-secondary format m-0" for="file-edit-reply-{{c.id}}">
<div id="filename-edit-reply-{{c.id}}"><i class="far fa-image"></i></div>
@ -557,11 +555,7 @@
<label class="btn btn-secondary format m-0" for="gif-reply-btn-{{c.fullname}}" onclick="commentForm('reply-form-body-{{c.fullname}}');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">
<span id="gif-reply-btn-{{c.fullname}}" class="font-weight-bolder text-uppercase">GIF</span>
</label>
&nbsp;
<label class="btn btn-secondary format m-0" for="gif-reply-btn-{{c.fullname}}" onclick="loadEmojis('reply-form-body-{{c.fullname}}')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji">
<i id="emoji-reply-btn-{{c.fullname}}" class="fas fa-smile-beam"></i>
</label>
&nbsp;
<label class="btn btn-secondary format m-0" for="file-upload-reply-{{c.fullname}}">
<div id="filename-show-reply-{{c.fullname}}"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload-reply-{{c.fullname}}" type="file" multiple="multiple" name="file" accept="image/*, video/*" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename-show-reply-{{c.fullname}}','file-upload-reply-{{c.fullname}}')" hidden>
@ -598,9 +592,6 @@
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea required autocomplete="off" minlength="1" maxlength="10000" name="body" form="reply-to-t3_{{c.id}}" data-id="{{c.id}}" class="comment-box form-control rounded" id="reply-form-body-{{c.id}}" aria-label="With textarea" rows="3" oninput="markdown('reply-form-body-{{c.id}}', 'message-reply-{{c.id}}')"></textarea>
<div class="comment-format" id="comment-format-bar-{{c.id}}">
<label class="btn btn-secondary m-0 mt-3 mr-1" onclick="loadEmojis('reply-form-body-{{c.id}}')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji">
<i class="fas fa-smile-beam"></i>
</label>
{% if c.sentto == 2 %}
<label class="btn btn-secondary m-0 mt-3" for="file-upload">
@ -773,7 +764,6 @@
{% if not ajax %}
{% if v %}
{% include "gif_modal.html" %}
{% include "emoji_modal.html" %}
{% if v.admin_level > 1 %}
{% include "ban_modal.html" %}
{% endif %}
@ -844,7 +834,8 @@
{% endif %}
{% if v %}
<script src="/assets/js/marked.js?v=251"></script>
<script src="/assets/js/vendor/marked.js?v=251"></script>
<script src="/assets/js/marked.custom.js?v=251"></script>
<script src="/assets/js/comments_v.js?v=266"></script>
{% endif %}

View file

@ -31,9 +31,6 @@
<label for="input-message" class="mt-3">Your message</label>
<input type="hidden" name="formkey" value="{{v.formkey}}">
<textarea autocomplete="off" maxlength="10000" id="input-message" form="contactform" name="message" class="form-control" required></textarea>
<label class="btn btn-secondary format m-0 mt-3" onclick="loadEmojis('input-message')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji">
<i class="fas fa-smile-beam"></i>
</label>
<label class="btn btn-secondary m-0 mt-3" for="file-upload">
<div id="filename"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload" type="file" name="file" accept="image/*, video/*" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename','file-upload')" hidden>
@ -52,7 +49,4 @@
</pre>
{% include "emoji_modal.html" %}
{% endblock %}

View file

@ -10,25 +10,7 @@
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=250">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css %}
{% if v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
{% else %}

View file

@ -1,95 +0,0 @@
<div id="form" class="d-none"></div>
<div class="modal fade" id="emojiModal" tabindex="-1" role="dialog" aria-labelledby="emojiModalTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered p-2 py-5 emoji-modal" role="document">
<div class="modal-content" id="emojiTabs">
<div class="modal-header">
<div>
<ul class="nav nav-pills py-2">
<li class="nav-item">
<a class="nav-link active emojitab" data-bs-toggle="tab" href="#emoji-tab-favorite">Favorite</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-marsey">Marsey</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-marseyalphabet">Marsey Alphabet</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-platy">Platy</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-wolf">Zombie Wolf</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-tay">Tay</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-classic">Classic</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-rage">Rage</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-wojak">Wojak</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-flags">Flags</a>
</li>
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-misc">Misc</a>
</li>
</ul>
</div>
<button class="close" data-bs-dismiss="modal" aria-label="Close">
<i class="fal fa-times text-muted"></i>
</button>
</div>
<div class="px-3"><input autocomplete="off" class="form-control px-2" type="text" id="emoji_search" placeholder="Search.."></div>
<div style="overflow-y: scroll;">
<div class="modal-body p-0" id="emoji-modal-body">
<div id="emoji-tab-search"></div>
<div id="no-emojis-found"></div>
<div id="tab-content" class="tab-content">
<div class="tab-pane fade show active" id="emoji-tab-favorite">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_favorite"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-marsey">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_marsey"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-marseyalphabet">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_marseyalphabet"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-platy">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_platy"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-wolf">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_wolf"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-tay">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_tay"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-classic">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_classic"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-rage">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_rage"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-wojak">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_wojak"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-flags">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_flags"></div>
</div>
<div class="tab-pane fade" id="emoji-tab-misc">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_misc"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="/assets/js/emoji_modal.js?v=271"></script>

View file

@ -9,11 +9,11 @@
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseybrainlet:" loading="lazy" src="/e/marseybrainlet.webp">
<pre></pre>
<h1 class="h5">400 Bad Request</h1>
<p class="text-muted mb-5">That request was bad and you should feel bad.</p>
<div class="text-center px-3 my-8">
<h1>400</h1>
<h5>Bad Request</h5>
<p class="text-muted mb-5">That request was bad and you should feel bad</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>

View file

@ -9,12 +9,9 @@
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseydead:" loading="lazy" src="/e/marseydead.webp">
<pre></pre>
<h1 class="h5">401 Not Authorized</h1>
<div class="text-center px-3 my-8">
<h1>401</h1>
<h5>Not Authorized</h5>
<p class="text-muted mb-5">You need an account for this. Please make one!</p>
<div><a href="/signup" class="btn btn-primary mb-2">Create an account</a></div>
<div><a href="/login" class="text-muted text-small">Or sign in</a></div>

View file

@ -9,10 +9,9 @@
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseytroll:" loading="lazy" src="/e/marseytroll.webp">
<pre></pre>
<h1 class="h5">403 Forbidden</h1>
<div class="text-center px-3 my-8">
<h1>403</h1>
<h5>Forbidden</h5>
<p class="text-muted mb-5">{{description}}</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>

View file

@ -9,10 +9,9 @@
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyconfused" loading="lazy" src="/e/marseyconfused.webp">
<pre></pre>
<h1 class="h5">404 Page Not Found</h1>
<div class="text-center px-3 my-8">
<h1>404</h1>
<h5>Page Not Found</h5>
<p class="text-muted mb-5">That page doesn't exist. If you got here from a link on the website, please report this issue. Thanks!</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>

View file

@ -9,10 +9,9 @@
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyretard:" loading="lazy" src="/e/marseyretard.webp">
<pre></pre>
<h1 class="h5">405 Method Not Allowed</h1>
<div class="text-center px-3 my-8">
<h1>405</h1>
<h5>Method Not Allowed</h5>
<p class="text-muted mb-5">Something went wrong and it's probably my fault. If you can do it reliably, or it's causing problems for you, please report it!</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>

View file

@ -9,11 +9,11 @@
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyretard:" loading="lazy" src="/e/marseyretard.webp">
<pre></pre>
<h1 class="h5">Max file size is 8 MB.</h1>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
<div class="text-center px-3 my-8">
<h1>413</h1>
<h5>Payload Too Large</h5>
<p class="text-muted mb-5">Max file size is 8 MB</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>

View file

@ -9,10 +9,9 @@
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseyrentfree:" loading="lazy" src="/e/marseyrentfree.webp">
<pre></pre>
<h1 class="h5">429 Too Many Requests</h1>
<div class="text-center px-3 my-8">
<h1>429</h1>
<h5>Too Many Requests</h5>
<p class="text-muted mb-5">Are you hammering the site? Stop that, yo.</p>
</div>
</div>

View file

@ -10,12 +10,10 @@
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseydead:" loading="lazy" src="/e/marseydead.webp">
<pre></pre>
<h1 class="h5">500 Internal Server Error</h1>
<p class="text-muted mb-5">Something went wrong and it's probably my fault. If you can do it reliably, or it's causing problems for you, please report it!<3</p>
<div><a href="/" class="btn btn-primary">Go to the frontpage</a></div>
<h1>500</h1>
<h5>Internal Server Error</h5>
<p class="text-muted mb-5">Something went wrong and it's probably my fault. If you can do it reliably, or it's causing problems for you, please report it!</p>
<div><a href="/" class="btn btn-primary">Go to frontpage</a></div>
</div>
</div>
</div>

View file

@ -10,11 +10,10 @@
<div class="row justify-content-center">
<div class="col col-md-5">
<div class="text-center px-3 mt-5">
<img alt=":#marseytwerking:" loading="lazy" src="/e/marseytwerking.webp">
<h1 class="h5">Are you over 18?</h1>
<h1>NSFW</h1>
<h5>Are you over 18?</h5>
<p class="mb-5">This post is rated +18 (Adult-Only). You must be 18 or older to continue. Are you sure you want to proceed?</p>
<div class="btn-toolbar justify-content-center mb-4">
<form action="/allow_nsfw" method="post">
<input type="hidden" name="redir" value="{{request.path}}">
<input type="submit" class="btn btn-danger mr-2" value="Yes, I am +18">

View file

@ -7,14 +7,14 @@
{% block pagetype %}error-401{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="row justify-content-center">
<div class="col-10 col-md-5">
<div class="text-center px-3 my-8">
<img alt=":#marseymerchant:" loading="lazy" class="mb-2" src="/e/marseymerchant.webp">
<h1 class="h5">401 Not Authorized</h1>
<p class="text-muted">This page is only available to patrons:</p>
<a rel="nofollow noopener noreferrer" href="{{config('GUMROAD_LINK')}}">{{config('GUMROAD_LINK')}}</a>
</div>
<h1>401</h1>
<h5>Not Authorized</h5>
<p class="text-muted">This page is only available to patrons:</p>
<a rel="nofollow noopener noreferrer" href="{{config('GUMROAD_LINK')}}">{{config('GUMROAD_LINK')}}</a>
</div>
</div>
</div>
{% endblock %}

View file

@ -81,37 +81,6 @@ Text 2
<td>https://files.catbox.moe/v4om92.mp4</td>
<td><video controls preload="none" class="vid"><source referrerpolicy="no-referrer" src="https://files.catbox.moe/v4om92.mp4" type="video/mp4"></video></td>
</tr>
<tr>
<td>Emojis</td>
<td>:marseylove:</td>
<td><img loading="lazy" data-bs-toggle="tooltip" class="emoji" alt=":marseylove:" title=":marseylove:" height="30" src="/e/marseylove.webp"></td>
</tr>
<tr>
<td>Mirrored Emojis</td>
<td>:!marseylove:</td>
<td><img loading="lazy" data-bs-toggle="tooltip" class="emoji mirrored" alt=":!marseylove:" title=":!marseylove:" height="30" src="/e/marseylove.webp"></td>
</tr>
<tr>
<td>Large Emojis</td>
<td>:#marseylove:</td>
<td><img loading="lazy" data-bs-toggle="tooltip" b alt=":marseylove:" title=":marseylove:" src="/e/marseylove.webp"></td>
</tr>
<tr>
<td>Large Mirrored Emojis</td>
<td>:#!marseylove:</td>
<td><img loading="lazy" data-bs-toggle="tooltip" b alt=":!marseylove:" title=":!marseylove:" src="/e/marseylove.webp"></td>
</tr>
<tr>
<td>Random Marsey</td>
<td>:marseyrandom:</td>
<td>???</td>
</tr>
<tr>
<td>Random Fortune</td>
<td>#fortune</td>
<td>???</td>
</tr>
<tr>
<td>Poll Options (can select multiple options)</td>
<td>$$bussy$$ $$gussy$$</td>

View file

@ -8,25 +8,7 @@
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=250">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css %}
{% if v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
{% else %}
@ -58,10 +40,10 @@
<div class="dropdown dropdown-actions">
<button class="btn btn-secondary dropdown-toggle" role="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% if admin %}<img src="/@{{admin}}/pic" alt="avatar" width=20 height=20 class="rounded-circle mr-2">{{admin}}{% else %}<img src="/e/marseyjanny.webp" alt="avatar" width=20 height=20 class="rounded-circle mr-2">All{% endif %}
{% if admin %}<img src="/@{{admin}}/pic" alt="avatar" width=20 height=20 class="rounded-circle mr-2">{{admin}}{% else %}<img src="/assets/images/default-profile-pic.webp" alt="avatar" width=20 height=20 class="rounded-circle mr-2">All{% endif %}
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton" x-placement="bottom-start" style="position: absolute; will-change: transform; top: 0px; left: 0px; transform: translate3d(0px, 31px, 0px);">
<a class="dropdown-item" href="/log{% if type %}?kind={{type}}{% endif %}"><img src="/e/marseyjanny.webp" alt="avatar" width=20 height=20 class="rounded-circle mr-2">All</a>
<a class="dropdown-item" href="/log{% if type %}?kind={{type}}{% endif %}"><img src="/assets/images/default-profile-pic.webp" alt="avatar" width=20 height=20 class="rounded-circle mr-2">All</a>
{% for a in admins %}
<a class="dropdown-item" href="?admin={{a}}{% if type %}&kind={{type}}{% endif %}"><img loading="lazy" src="/@{{a}}/pic" alt="avatar" width=20 height=20 class="rounded-circle mr-2">{{a}}</a>
{% endfor %}

View file

@ -1,28 +0,0 @@
{% extends "default.html" %}
{% block content %}
<pre>
</pre>
<div class="overflow-x-auto"><table class="table table-striped mb-5">
<thead class="bg-primary text-white">
<tr>
<th>#</th>
<th>Name</th>
<th>Marsey</th>
<th><a href="?sort=usage">Usage</a></th>
</tr>
</thead>
<tbody id="followers-table">
{% for marsey in marseys %}
<tr>
<td>{{loop.index}}</td>
<td>{{marsey.name}}</td>
<td><img class="marsey" loading="lazy" data-bs-toggle="tooltip" alt=":{{marsey.name}}:" title=":{{marsey.name}}:" src="/e/{{marsey.name}}.webp"></td>
<td>{{marsey.count}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -36,25 +36,7 @@
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=250">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css and not request.path.startswith('/settings/css') %}
{% if v.css and not request.path.startswith('/settings/css') %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
</head>

View file

@ -362,8 +362,6 @@
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input maxlength=100 {% if v.flairchanged %}disabled{% endif %} autocomplete="off" id="customtitlebody" type="text" name="title" class="form-control" placeholder='Enter a flair here' value="{% if v.flairchanged %}Your flair has been locked until {{ti}}{% elif v.customtitleplain %}{{v.customtitleplain}}{% endif %}">
<div class="d-flex mt-2">
<a class="format" role="button"><i class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('customtitlebody')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></i></a>
&nbsp;&nbsp;&nbsp;
<small>Limit of 100 characters</small>
<input {% if v.flairchanged %}disabled{% endif %} autocomplete="off" class="btn btn-primary ml-auto" id="titleSave" type="submit" value="Change Flair">
</div>
@ -476,8 +474,6 @@
&nbsp;
<pre style="padding-top:0.7rem;line-height:1" class="btn btn-secondary format d-inline-block m-0 font-weight-bolder text-uppercase" onclick="commentForm('bio-text');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</pre>
&nbsp;
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('bio-text')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></pre>
&nbsp;
<label class="btn btn-secondary format d-inline-block m-0">
<div id="filename-show"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload" type="file" name="file" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} accept="image/*, video/*" onchange="changename('filename-show','file-upload')" hidden>
@ -554,8 +550,6 @@
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-quote-right" aria-hidden="true" onclick="makeQuote('sig-text')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Quote"></pre>
&nbsp;
<pre style="padding-top:0.7rem;line-height:1" class="btn btn-secondary format d-inline-block m-0 font-weight-bolder text-uppercase" onclick="commentForm('sig-text');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</pre>
&nbsp;
<pre style="padding-top:0.7rem" class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('sig-text')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></pre>
</div>
<pre></pre>
<div class="d-flex">
@ -620,7 +614,6 @@
<script src="/assets/js/settings_profile.js?v=251"></script>
{% include "emoji_modal.html" %}
{% include "gif_modal.html" %}
{% endblock %}

View file

@ -30,292 +30,6 @@
</script>
{% endif %}
{% if p.award_count("crab") %}
<script>
let audio = new Audio('/assets/crab.mp3');
audio.loop=true;
audio.play();
window.addEventListener('click', () => {
if (audio.paused) audio.play();
}, {once : true});
</script>
{% endif %}
{% if fart and not (v and v.has_badge(128)) %}
<script>
fart = Math.floor(Math.random() * 4) + 1
let audio = new Audio(`/assets/images/${fart}.webp`);
audio.play();
if (audio.paused) {
window.addEventListener('click', () => {
if (audio.paused) audio.play();
}, {once : true})
}
</script>
{% endif %}
{% if g.inferior_browser %}
{% if p.award_count("wholesome") %}
<style>
.sealimg {
width: 100px;
height: 89.5px;
}
@media (max-width: 992px) {
.sealimg {
width: 30px;
height: 27px;
}
}
.seal {
position:fixed;
z-index:9999;
pointer-events: none;
width: 100% !important;
height: 100% !important;
}
@keyframes moveX {
from { left: 0; } to { left: 98%; }
}
@keyframes moveY {
from { top: 0; } to { top: 98%; }
}
.seal1 {
animation: moveX 4s linear 0s infinite alternate, moveY 6.8s linear 0s infinite alternate !important;
animation-delay:0s;
}
</style>
<div class="seal seal1" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
</div>
{% if p.award_count("wholesome") > 1 %}
<style>
.seal2 {
animation: moveX 5s linear 0s infinite alternate, moveY 8s linear 0s infinite alternate !important;
animation-delay:1s;
}
</style>
<div class="seal seal2" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
</div>
{% endif %}
{% if p.award_count("wholesome") > 2 %}
<style>
.seal3 {
animation: moveX 4s linear 0s infinite alternate, moveY 5s linear 0s infinite alternate !important;
animation-delay:2s;
}
</style>
<div class="seal seal3" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
</div>
{% endif %}
{% if p.award_count("wholesome") > 3 %}
<style>
.seal4 {
animation: moveX 5s linear 0s infinite alternate, moveY 6.8s linear 0s infinite alternate !important;
animation-delay:3s;
}
</style>
<div class="seal seal4" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
</div>
{% endif %}
{% endif %}
{% else %}
{% if p.award_count("wholesome") %}
<style>
.seal {
position:fixed;
z-index:9999;
pointer-events: none;
width: 100% !important;
height: 100% !important;
}
.sealimg {
width: 100px;
height: 89.5px;
}
@media (max-width: 992px) {
.sealimg {
width: 30px;
height: 27px;
}
}
</style>
<div class="seal" height="100%" width="100%">
<marquee class="seal" scrollamount=10 behavior="alternate" direction="up" height="100%" width="100%">
<marquee direction="right" scrollamount=10 behavior="alternate" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
</marquee>
</marquee>
</div>
{% if p.award_count("wholesome") > 1 %}
<marquee class="seal" scrollamount=10 behavior="alternate" direction="down" height="100%">
<marquee direction="right" scrollamount=10 behavior="alternate" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
</marquee>
</marquee>
{% endif %}
{% if p.award_count("wholesome") > 2 %}
<marquee class="seal" scrollamount=10 behavior="alternate" direction="up" height="100%">
<marquee direction="left" scrollamount=10 behavior="alternate" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
</marquee>
</marquee>
{% endif %}
{% if p.award_count("wholesome") > 3 %}
<marquee class="seal" scrollamount=10 behavior="alternate" direction="down" height="100%">
<marquee direction="left" scrollamount=10 behavior="alternate" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
</marquee>
</marquee>
{% endif %}
{% endif %}
{% endif %}
{% if p.award_count("train") or p.award_count("scooter") %}
<style>
.train {
position:fixed;
z-index:9999;
pointer-events: none;
}
.train1 {
top: 10%
}
.train2 {
top: 35%
}
.train3 {
top: 60%
}
.train4 {
top: 85%
}
</style>
{% endif %}
{% if p.award_count("train") %}
<style>
.trainimg {
width: 100px;
height: 51px;
}
@media (max-width: 992px) {
.trainimg {
width: 40px;
height: 20px;
}
}
</style>
<marquee class="train train1" direction="left" scrollamount=10 width="100%">
<img alt=":#marseytrain:" class="trainimg mirrored" src="/e/marseytrain.webp">
</marquee>
{% if p.award_count("train") > 1 %}
<marquee class="train train2" direction="right" scrollamount=10 width="100%">
<img alt=":#marseytrain:" class="trainimg" src="/e/marseytrain.webp">
</marquee>
{% endif %}
{% if p.award_count("train") > 2 %}
<marquee class="train train3" direction="left" scrollamount=10 width="100%">
<img alt=":#marseytrain:" class="trainimg mirrored" src="/e/marseytrain.webp">
</marquee>
{% endif %}
{% if p.award_count("train") > 3 %}
<marquee class="train train4" direction="right" scrollamount=10 width="100%">
<img alt=":#marseytrain:" class="trainimg" src="/e/marseytrain.webp">
</marquee>
{% endif %}
{% endif %}
{% if p.award_count("scooter") %}
<style>
.scooterimg {
width: 100px;
height: 135px;
}
@media (max-width: 992px) {
.scooterimg {
width: 40px;
height: 54px;
}
}
</style>
<marquee class="train train3" direction="right" scrollamount=10 width="100%">
<img alt=":#marseyscooter:" class="scooterimg" src="/e/marseyscooter.webp">
</marquee>
{% if p.award_count("scooter") > 1 %}
<marquee class="train train4" direction="left" scrollamount=10 width="100%">
<img alt=":#marseyscooter:" class="scooterimg mirrored" src="/e/marseyscooter.webp">
</marquee>
{% endif %}
{% if p.award_count("scooter") > 2 %}
<marquee class="train train1" direction="right" scrollamount=10 width="100%">
<img alt=":#marseyscooter:" class="scooterimg" src="/e/marseyscooter.webp">
</marquee>
{% endif %}
{% if p.award_count("scooter") > 3 %}
<marquee class="train train2" direction="left" scrollamount=10 width="100%">
<img alt=":#marseyscooter:" class="scooterimg mirrored" src="/e/marseyscooter.webp">
</marquee>
{% endif %}
{% endif %}
{% if p.award_count("tilt") %}
<style>
@keyframes post-tilt {
0% {transform: rotate(0deg);}
25% {transform: rotate({{p.award_count("tilt")}}deg);}
75% {transform: rotate(-{{p.award_count("tilt")}}deg);}
100% {transform: rotate(0deg);}
}
@media (max-width: 720px) {
@keyframes post-tilt {
0% {transform: rotate(0deg);}
25% {transform: rotate({{p.award_count("tilt")/4}}deg);}
75% {transform: rotate(-{{p.award_count("tilt")/4}}deg);}
100% {transform: rotate(0deg);}
}
}
#post-root {
animation-name: post-tilt !important;
animation-duration: 60s !important;
animation-iteration-count: infinite !important;
animation-direction: alternate !important;
animation-timing-function: linear !important;
}
</style>
{% endif %}
<meta charset="utf-8">
<meta property="og:type" content="article">
@ -495,13 +209,13 @@
</div>
{% endif %}
{% if p.realurl(v) %}
<h1 id="post-title" class="card-title post-title text-left mb-md-3 {% if p.author.agendaposter %}agendaposter{% endif %}"><a {% if not v or v.newtabexternal %}target="_blank"{% endif %} rel="nofollow noopener noreferrer" href="{{p.realurl(v)}}">
<h1 id="post-title" class="card-title post-title text-left mb-md-3"><a {% if not v or v.newtabexternal %}target="_blank"{% endif %} rel="nofollow noopener noreferrer" href="{{p.realurl(v)}}">
{% if p.club %}<span class="patron font-weight-bolder mr-1" style="background-color:red; font-size:12px; line-height:2;">{{CC}}</span>{% endif %}
{% if p.flair %}<span class="patron font-weight-bolder mr-1" style="background-color:var(--primary); font-size:12px; line-height:2;">{{p.flair | safe}}</span>{% endif %}
{{p.realtitle(v) | safe}}
</a></h1>
{% else %}
<h1 id="post-title" class="card-title post-title text-left mb-md-3 {% if p.author.agendaposter %}agendaposter{% endif %}">
<h1 id="post-title" class="card-title post-title text-left mb-md-3">
{% if p.club %}<span class="patron font-weight-bolder mr-1" style="background-color:red; font-size:12px; line-height:2;">{{CC}}</span>{% endif %}
{% if p.flair %}<span class="patron font-weight-bolder mr-1" style="background-color:var(--primary); font-size:12px; line-height:2;">{{p.flair | safe}}</span>{% endif %}
{{p.realtitle(v) | safe}}
@ -535,7 +249,7 @@
{% endif %}
<div id="post-text" {% if p.author.agendaposter %}class="agendaposter"{% endif %}>
<div id="post-text">
{% if p.is_image %}
<div class="row no-gutters">
<div class="col">
@ -580,8 +294,6 @@
<a class="format btn btn-secondary" role="button"><i class="fas fa-italic" aria-hidden="true" onclick="makeItalics('post-edit-box-{{p.id}}')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Italicize"></i></a>
<a class="format btn btn-secondary" role="button"><i class="fas fa-quote-right" aria-hidden="true" onclick="makeQuote('post-edit-box-{{p.id}}')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Quote"></i></a>
<a class="format btn btn-secondary" role="button"><span class="font-weight-bolder text-uppercase" onclick="commentForm('post-edit-box-{{p.id}}');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</span></a>
<a class="format btn btn-secondary" role="button"><i class="fas fa-smile-beam" onclick="loadEmojis('post-edit-box-{{p.id}}')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></i></a>
<label class="format btn btn-secondary m-0 ml-1 {% if v %}d-inline-block{% else %}d-none{% endif %}" for="file-upload-edit-{{p.id}}">
<div id="filename-show-edit-{{p.id}}"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload-edit-{{p.id}}" type="file" multiple="multiple" name="file" accept="image/*, video/*" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename-show-edit-{{p.id}}','file-upload-edit-{{p.id}}')" hidden>
@ -777,10 +489,6 @@
<label class="btn btn-secondary format d-inline-block m-0" for="gif-reply-btn-{{p.fullname}}">
<span id="gif-reply-btn-{{p.fullname}}" class="font-weight-bolder text-uppercase" onclick="commentForm('reply-form-body-{{p.fullname}}');getGif()" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</span>
</label>
&nbsp;
<label class="btn btn-secondary format d-inline-block m-0" for="emoji-reply-btn-{{p.fullname}}">
<div id="emoji-reply-btn-{{p.fullname}}" onclick="loadEmojis('reply-form-body-{{p.fullname}}')" 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>
</label>
<label class="format btn btn-secondary m-0 ml-1 {% if v %}d-inline-block{% else %}d-none{% endif %}" for="file-upload-reply-{{p.fullname}}">
<div id="filename-show-reply-{{p.fullname}}"><i class="far fa-image"></i></div>
<input autocomplete="off" id="file-upload-reply-{{p.fullname}}" type="file" multiple="multiple" name="file" accept="image/*, video/*" {% if request.headers.get('cf-ipcountry')=="T1" %}disabled{% endif %} onchange="changename('filename-show-reply-{{p.fullname}}','file-upload-reply-{{p.fullname}}')" hidden>

View file

@ -114,9 +114,7 @@
<div class="card-header bg-transparent border-0 d-flex flex-row flex-nowrap pl-2 pl-md-0 p-0 mr-md-2">
<div style="z-index: 3;">
{% if p.club and not (v and (v.paid_dues or v.id == p.author_id)) %}
<img alt="post thumnail" loading="lazy" src="/e/marseyglow.webp" class="post-img">
{% elif not p.url %}
{% if not p.url %}
<a {% if v and v.newtab and not g.webview %}target="_blank"{% endif %} href="{{p.permalink}}">
<img alt="post thumnail" loading="lazy" src="{{p.thumb_url}}" class="post-img">
</a>
@ -207,7 +205,7 @@
</div>
<h5 class="card-title post-title text-left w-lg-95 mb-0 pb-0 pb-md-1">
<a id="{{p.id}}-title" {% if v and v.newtab and not g.webview %}target="_blank"{% endif %} href="{{p.permalink}}" class="{% if voted and v.id == AEVANN_ID %}visited{% endif %} stretched-link {% if p.author.agendaposter %}agendaposter{% endif %}">
<a id="{{p.id}}-title" {% if v and v.newtab and not g.webview %}target="_blank"{% endif %} href="{{p.permalink}}" class="{% if voted and v.id == AEVANN_ID %}visited{% endif %} stretched-link">
{% if p.club %}<span class="patron font-weight-bolder mr-1" style="background-color:red; font-size:12px; line-height:2;">{{CC}}</span>{% endif %}
{% if p.flair %}<span class="patron font-weight-bolder mr-1" style="background-color:var(--primary); font-size:12px; line-height:2;">{{p.flair | safe}}</span>{% endif %}
{{p.realtitle(v) | safe}}
@ -345,7 +343,7 @@
{% if not p.club or v and (v.paid_dues or v.id == p.author_id) %}
{% if p.realbody(v) %}
<div class="d-none card rounded border pt-3 pb-2 my-2 {% if p.author.agendaposter %}agendaposter{% endif %}" id="post-text-{{p.id}}">
<div class="d-none card rounded border pt-3 pb-2 my-2" id="post-text-{{p.id}}">
{{p.realbody(v) | safe}}
</div>
{% endif %}

View file

@ -24,25 +24,7 @@
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=250">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=49">
{% if v.agendaposter %}
<style>
html {
cursor:url('/assets/images/dildo.webp?v=1008'), auto;
}
.nav-item .text-small.font-weight-bold::before {
content: "((("
}
.nav-item .text-small.font-weight-bold::after {
content: ")))"
}
.nav-item .text-small-extra.text-primary {
font-size: 0 !important
}
.nav-item .text-small-extra.text-primary i {
font-size: 11px !important
}
</style>
{% elif v.css %}
{% if v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
{% else %}
@ -79,10 +61,6 @@
<input autocomplete="off" class="form-control" id="post-title" aria-describedby="titleHelpRegister" type="text" name="title" placeholder="Required" value="{{title}}" minlength="1" maxlength="500" required oninput="checkForRequired();savetext()">
<label class="btn btn-secondary format d-inline-block m-0" for="emoji-reply-btn-2">
<div id="emoji-reply-btn-2" onclick="loadEmojis('post-title')" 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>
</label>
<div id="urlblock">
<label for="URL" class="mt-3">URL</label>
<input autocomplete="off" class="form-control" id="post-url" aria-describedby="URLHelp" name="url" type="url" placeholder="Optional if you have text." value="{{request.values.get('url','')}}" required oninput="checkForRequired();hide_image();savetext();checkRepost(this);autoSuggestTitle()">
@ -129,9 +107,6 @@
&nbsp;
<small class="btn btn-secondary format d-inline-block m-0"><span class="font-weight-bolder text-uppercase" aria-hidden="true" onclick="getGif();commentForm('post-text')" data-bs-toggle="modal" data-bs-target="#gifModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add GIF">GIF</span></small>
&nbsp;
<label class="btn btn-secondary format d-inline-block m-0" for="emoji-reply-btn">
<div id="emoji-reply-btn" onclick="loadEmojis('post-text')" 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>
</label>
<label class="format btn btn-secondary m-0 ml-1 {% if v %}d-inline-block{% else %}d-none{% endif %}" for="file-upload-submit">
<div id="filename-show-submit"><i class="far fa-image"></i></div>
@ -206,10 +181,11 @@
</script>
{% endif %}
<script src="/assets/js/marked.js?v=251"></script>
<script src="/assets/js/vendor/marked.js?v=251"></script>
<script src="/assets/js/marked.custom.js?v=251"></script>
<script src="/assets/js/formatting.js?v=240"></script>
<script src="/assets/js/submit.js?v=255"></script>
{% include "emoji_modal.html" %}
{% include "gif_modal.html" %}
</body>

View file

@ -189,8 +189,6 @@
&nbsp;
<pre class="btn btn-secondary format d-inline-block m-0 fas fa-quote-right" aria-hidden="true" onclick="makeQuote('input-message')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Quote"></pre>
&nbsp;
<pre class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" 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"></pre>
&nbsp;
<input type="submit" value="Submit" class="btn btn-primary">
</form>
@ -214,8 +212,6 @@
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input maxlength=100 autocomplete="off" id="customtitlebody" type="text" name="title" class="form-control" placeholder='Enter a flair here' value="{% if u.customtitleplain %}{{u.customtitleplain}}{% endif %}">
<div class="d-flex mt-2">
<a class="format" role="button"><i class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('customtitlebody')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></i></a>
&nbsp;&nbsp;&nbsp;
<div class="custom-control custom-checkbox">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="locked" name="locked" {% if u.flairchanged %}checked{% endif %}>
<label class="custom-control-label" for="locked">locked</label>
@ -248,14 +244,6 @@
</form>
{% endif %}
<pre></pre>
<form id="agendaposter1" class="{% if u.agendaposter %}d-none{% endif %}" action="/agendaposter/{{u.id}}" method="post">
<input type="hidden" name="formkey", value="{{v.formkey}}">
<input autocomplete="off" type="number" step="any" name="days" class="form-control" placeholder="Days (0 or blank = permanent)">
<input type="submit" class="btn btn-danger" value="Lock Chud Theme">
</form>
<a id="unagendaposter" class="{% if not u.agendaposter %}d-none{% endif %} btn btn-success" role="button" onclick="post_toast2(this,'/unagendaposter/{{u.id}}','agendaposter1','unagendaposter')">Disable Chud Theme</a>
<pre></pre>
<a id="shadowban" class="{% if u.shadowbanned %}d-none{% endif %} btn btn-danger" role="button" onclick="post_toast2(this,'/shadowban/{{u.id}}','shadowban','unshadowban')">Shadowban</a>
@ -476,8 +464,6 @@
&nbsp;
<pre class="btn btn-secondary format d-inline-block m-0 fas fa-quote-right" aria-hidden="true" onclick="makeQuote('input-message-mobile')" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Quote"></pre>
&nbsp;
<pre class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('input-message-mobile')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></pre>
&nbsp;
<input type="submit" value="Submit" class="btn btn-primary">
</form>
@ -497,8 +483,6 @@
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input maxlength=100 autocomplete="off" id="customtitlebody-mobile" type="text" name="title" class="form-control" placeholder='Enter a flair here' value="{% if u.customtitleplain %}{{u.customtitleplain}}{% endif %}">
<div class="d-flex mt-2">
<a class="format" role="button"><i class="btn btn-secondary format d-inline-block m-0 fas fa-smile-beam" onclick="loadEmojis('customtitlebody-mobile')" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"></i></a>
&nbsp;&nbsp;&nbsp;
<div class="custom-control custom-checkbox">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="locked-mobile" name="locked" {% if u.flairchanged %}checked{% endif %}>
<label class="custom-control-label" for="locked-mobile">locked</label>
@ -533,14 +517,6 @@
</form>
{% endif %}
<pre></pre>
<form id="agendaposter2" class="{% if u.agendaposter %}d-none{% endif %}" action="/agendaposter/{{u.id}}" method="post">
<input type="hidden" name="formkey", value="{{v.formkey}}">
<input autocomplete="off" type="number" step="any" name="days" class="form-control" placeholder="Days (0 or blank = permanent)">
<input type="submit" class="btn btn-danger" value="Lock Chud Theme">
</form>
<a id="unagendaposter2" class="{% if not u.agendaposter %}d-none{% endif %} btn btn-success" role="button" onclick="post_toast2(this,'/unagendaposter/{{u.id}}','agendaposter2','unagendaposter2')">Disable Chud Theme</a>
<pre></pre>
<a id="shadowban2" class="{% if u.shadowbanned %}d-none{% endif %} btn btn-danger" role="button" onclick="post_toast2(this,'/shadowban/{{u.id}}','shadowban2','unshadowban2')">Shadowban</a>
@ -721,12 +697,8 @@
</nav>
{% endif %}
<script src="/assets/js/marked.js?v=251"></script>
{% if v and v.id != u.id and '/comments' not in request.path %}
{% include "emoji_modal.html" %}
{% endif %}
<script src="/assets/js/vendor/marked.js?v=251"></script>
<script src="/assets/js/marked.custom.js?v=251"></script>
{% endblock %}

View file

@ -10,7 +10,8 @@ def test_rules():
assert response.text.startswith("<!DOCTYPE html>")
def test_signup():
def test_signup_and_post():
print("\nTesting signup and posting flow")
client = app.test_client()
with client: # this keeps the session between requests, which we need
signup_get_response = client.get("/signup")
@ -35,6 +36,20 @@ def test_signup():
assert "error" not in signup_post_response.location
# we should now be logged in and able to post
submit_get_response = client.get("/submit")
assert submit_get_response.status_code == 200
submit_post_response = client.post("/submit", data={
"title": "my_cool_post",
"body": "hey_guys",
})
assert submit_post_response.status_code == 302
post_render_result = client.get(submit_post_response.location)
assert "my_cool_post" in post_render_result.text
assert "hey_guys" in post_render_result.text

View file

@ -0,0 +1,78 @@
import inspect
import migrations.versions
import os
import subprocess
from files.__main__ import app
APP_PATH = app.root_path
BASE_PATH = os.path.join(*os.path.split(APP_PATH)[:-1])
VERSIONS_PATH = migrations.versions.__path__._path[0];
def test_migrations_up_to_date():
def get_versions():
all_versions = [f.path for f in os.scandir(VERSIONS_PATH)]
filtered_versions = []
for entry in all_versions:
if not os.path.isfile(entry):
continue
*dir_parts, filename = os.path.split(entry)
base, ext = os.path.splitext(filename)
if ext == '.py':
filtered_versions.append(entry)
return filtered_versions
def get_method_body_lines(method):
method_lines, _ = inspect.getsourcelines(method)
return [l.strip() for l in method_lines if not l.strip().startswith('#')][1:]
versions_before = get_versions()
result = subprocess.run(
[
'python3',
'-m',
'flask',
'db',
'revision',
'--autogenerate',
'--rev-id=ci_verify_empty_revision',
'--message=should_be_empty',
],
cwd=BASE_PATH,
env={
**os.environ,
'FLASK_APP': 'files/cli:app',
},
capture_output=True,
text=True,
check=True
)
versions_after = get_versions()
new_versions = [v for v in versions_after if v not in versions_before]
try:
for version in new_versions:
filename = os.path.split(version)[-1]
base, ext = os.path.splitext(filename)
__import__(f'migrations.versions.{base}')
migration = getattr(migrations.versions, base)
upgrade_lines = get_method_body_lines(migration.upgrade)
assert ["pass"] == upgrade_lines, "\n".join([
"",
"Expected upgrade script to be empty (pass) but got",
*[f"\t>\t{l}" for l in upgrade_lines],
"To fix this issue, please run",
"\t$ flask db revision --autogenerate --message='short description of schema changes'",
"to generate a candidate migration, and make any necessary changes to that candidate migration (e.g. naming foreign key constraints)",
])
downgrade_lines = get_method_body_lines(migration.downgrade)
assert ["pass"] == downgrade_lines, "\n".join([
"",
"Expected downgrade script to be empty (pass) but got",
*[f"\t>{l}" for l in downgrade_lines],
"To fix this issue, please run",
"\tflask db revision --autogenerate --message='short description of schema changes'",
"to generate a candidate migration, and make any necessary changes to that candidate migration (e.g. naming foreign key constraints)",
])
finally:
for version in new_versions:
os.remove(version)

1
migrations/README Normal file
View file

@ -0,0 +1 @@
Single-database configuration for Flask.

51
migrations/alembic.ini Normal file
View file

@ -0,0 +1,51 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d_%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s
timezone = UTC
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic,flask_migrate
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[logger_flask_migrate]
level = INFO
handlers =
qualname = flask_migrate
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

95
migrations/env.py Normal file
View file

@ -0,0 +1,95 @@
from __future__ import with_statement
from files.classes import *
import logging
from logging.config import fileConfig
from flask import current_app
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
connection_string = current_app.config.get('DATABASE_URL')
config = context.config
config.set_main_option('sqlalchemy.url', connection_string)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
from files.__main__ import Base
target_metadata = Base.metadata
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.get_engine().url).replace(
'%', '%%'))
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = current_app.extensions['migrate'].db.get_engine()
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Normal file
View file

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View file

@ -0,0 +1,28 @@
"""create empty first revision
Revision ID: 0aef77162269
Revises:
Create Date: 2022-05-12 02:54:34.564536+00:00
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '0aef77162269'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View file

@ -0,0 +1,38 @@
"""update db to match models at fork time
Revision ID: 4a1f7859151b
Revises: 0aef77162269
Create Date: 2022-05-12 03:08:32.417479+00:00
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '4a1f7859151b'
down_revision = '0aef77162269'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('submissions', sa.Column('bump_utc', sa.Integer(), server_default=sa.schema.FetchedValue(), nullable=True))
op.create_foreign_key('comments_app_id_fkey', 'comments', 'oauth_apps', ['app_id'], ['id'])
op.create_foreign_key('commentvotes_app_id_fkey', 'commentvotes', 'oauth_apps', ['app_id'], ['id'])
op.create_foreign_key('modactions_user_id_fkey', 'modactions', 'users', ['user_id'], ['id'])
op.create_foreign_key('submissions_app_id_fkey', 'submissions', 'oauth_apps', ['app_id'], ['id'])
op.create_foreign_key('votes_app_id_fkey', 'votes', 'oauth_apps', ['app_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('votes_app_id_fkey', 'votes', type_='foreignkey')
op.drop_constraint('submissions_app_id_fkey', 'submissions', type_='foreignkey')
op.drop_constraint('modactions_user_id_fkey', 'modactions', type_='foreignkey')
op.drop_constraint('commentvotes_app_id_fkey', 'commentvotes', type_='foreignkey')
op.drop_constraint('comments_app_id_fkey', 'comments', type_='foreignkey')
op.drop_column('submissions', 'bump_utc')
# ### end Alembic commands ###

View file

@ -0,0 +1,46 @@
"""add usernotes constraints
Revision ID: 16d6335dd9a3
Revises: 4a1f7859151b
Create Date: 2022-05-16 19:42:28.708577+00:00
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '16d6335dd9a3'
down_revision = '4a1f7859151b'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('usernotes', 'note',
existing_type=sa.VARCHAR(length=10000),
nullable=False)
op.alter_column('usernotes', 'tag',
existing_type=sa.VARCHAR(length=10),
nullable=False)
op.create_foreign_key('usernotes_author_id_fkey', 'usernotes', 'users', ['author_id'], ['id'])
op.create_foreign_key('usernotes_reference_comment_fkey', 'usernotes', 'comments', ['reference_comment'], ['id'], ondelete='SET NULL')
op.create_foreign_key('usernotes_reference_post_fkey', 'usernotes', 'submissions', ['reference_post'], ['id'], ondelete='SET NULL')
op.create_foreign_key('usernotes_reference_user_fkey', 'usernotes', 'users', ['reference_user'], ['id'], ondelete='CASCADE')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('usernotes_reference_user_fkey', 'usernotes', type_='foreignkey')
op.drop_constraint('usernotes_reference_post_fkey', 'usernotes', type_='foreignkey')
op.drop_constraint('usernotes_reference_comment_fkey', 'usernotes', type_='foreignkey')
op.drop_constraint('usernotes_author_id_fkey', 'usernotes', type_='foreignkey')
op.alter_column('usernotes', 'tag',
existing_type=sa.VARCHAR(length=10),
nullable=True)
op.alter_column('usernotes', 'note',
existing_type=sa.VARCHAR(length=10000),
nullable=True)
# ### end Alembic commands ###

View file

@ -5,6 +5,7 @@ Flask-Caching
Flask-Compress
Flask-Limiter
Flask-Mail
Flask-Migrate
Flask-Socketio
gevent
gevent-websocket

View file

@ -33,10 +33,15 @@ subprocess.run([
# run the test
print("Running test . . .")
result = subprocess.run([
"docker",
"docker-compose",
"exec",
"themotte",
"bash", "-c", "cd service && python3 -m pytest -s"
'-T',
"files",
"bash", "-c", ' && '.join([
"cd service",
"FLASK_APP=files/cli:app python3 -m flask db upgrade",
"python3 -m pytest -s",
])
])
if not was_running:

File diff suppressed because one or more lines are too long

View file

@ -5,7 +5,7 @@ logfile=/tmp/supervisord.log
[program:service]
directory=/service
command=gunicorn files.__main__:app -k gevent -w 1 --reload -b 0.0.0.0:80 --max-requests 1000 --max-requests-jitter 500
command=sh -c 'FLASK_APP=files/cli:app python3 -m flask db upgrade && gunicorn files.__main__:app -k gevent -w 1 --reload -b 0.0.0.0:80 --max-requests 1000 --max-requests-jitter 500'
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr