forked from rDrama/rDrama
1
0
Fork 0

Merge remote-tracking branch 'upstream/frost' into birthgay-staging

master
Snakes 2022-05-19 17:59:35 -04:00
commit c789f6923e
166 changed files with 23418 additions and 22990 deletions

View File

@ -1,70 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '18 19 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'javascript', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -42,7 +42,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@ -53,7 +53,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -67,4 +67,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@ -22,6 +22,8 @@ jobs:
# OSSAR runs on windows-latest.
# ubuntu-latest and macos-latest support coming soon
runs-on: windows-latest
permissions:
security-events: write
steps:
- name: Checkout repository
@ -44,6 +46,6 @@ jobs:
# Upload results to the Security tab
- name: Upload OSSAR results
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: ${{ steps.ossar.outputs.sarifFile }}

14
.github/workflows/test.yml vendored 100644
View File

@ -0,0 +1,14 @@
name: "run_tests.py"
on: [push, pull_request]
jobs:
analyze:
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: run_tests.py
run: |
./run_tests.py

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
image.*
video.mp4
video.webm
unsanitized.mp4
cache/
__pycache__/
.idea/

View File

@ -2,6 +2,7 @@ version: '2.3'
services:
files:
container_name: "rDrama"
build:
context: .
volumes:

View File

@ -32,10 +32,11 @@ app.config["SERVER_NAME"] = environ.get("DOMAIN").strip()
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3153600
app.config["SESSION_COOKIE_NAME"] = "session_" + environ.get("SITE_NAME").strip().lower()
app.config["VERSION"] = "1.0.0"
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
app.config["SESSION_COOKIE_SECURE"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * 365
app.config['SESSION_REFRESH_EACH_REQUEST'] = False
app.config["DEFAULT_COLOR"] = environ.get("DEFAULT_COLOR", "ff0000").strip()
app.config["DEFAULT_THEME"] = environ.get("DEFAULT_THEME", "midnight").strip()
app.config["FORCE_HTTPS"] = 1
@ -117,7 +118,10 @@ def after_request(response):
response.headers.add("X-Frame-Options", "deny")
return response
if "load_chat" in argv:
if app.config["SERVER_NAME"] == 'localhost':
from files.routes import *
# from files.routes.chat import *
elif "load_chat" in argv:
from files.routes.chat import *
else:
from files.routes import *

View File

@ -392,6 +392,7 @@ class Comment(Base):
if not self.total_poll_voted(v): body += ' d-none'
body += f'"> - <a href="/votes?link=t3_{c.id}"><span id="poll-{c.id}">{c.upvotes}</span> votes</a></span></label></div>'
if self.choices:
curr = self.total_choice_voted(v)
if curr: curr = " value=" + str(curr[0].comment_id)
else: curr = ''
@ -478,7 +479,7 @@ class Comment(Base):
wager = int(split_result[4])
try: kind = split_result[5]
except: kind = "coins"
currency_kind = "Coins" if kind == "coins" else "Marseybucks"
currency_kind = "Coins" if kind == "coins" else "Marseybux"
try: is_insured = split_result[6]
except: is_insured = "0"

View File

@ -60,6 +60,10 @@ class ModAction(Base):
years = int(months / 12)
return f"{years}yr ago"
@property
@lazy
def created_string(self):
return time.strftime('%d %b %Y %H:%M:%S UTC', time.gmtime(self.created_utc))
@property
def note(self):

View File

@ -165,8 +165,6 @@ class Submission(Base):
@lazy
def edited_string(self):
if not self.edited_utc: return "never"
age = int(time.time()) - self.edited_utc
if age < 60:
@ -184,11 +182,13 @@ class Submission(Base):
now = time.gmtime()
ctd = time.gmtime(self.edited_utc)
months = now.tm_mon - ctd.tm_mon + 12 * (now.tm_year - ctd.tm_year)
if now.tm_mday < ctd.tm_mday:
months -= 1
if months < 12:
return f"{months}mo ago"
else:
years = now.tm_year - ctd.tm_year
years = int(months / 12)
return f"{years}yr ago"
@ -452,7 +452,7 @@ class Submission(Base):
def realtitle(self, v):
if self.club and not (v and (v.paid_dues or v.id == self.author_id)):
if v: return random.choice(TROLLTITLES).format(username=v.username)
elif SITE == 'cringetopia.org': return f'Please make an account to see this post'
elif dues == -2: return f'Please make an account to see this post'
else: return f'{CC} MEMBERS ONLY'
elif self.title_html: title = self.title_html
else: title = self.title

View File

@ -26,6 +26,9 @@ defaulttheme = environ.get("DEFAULT_THEME", "midnight").strip()
defaulttimefilter = environ.get("DEFAULT_TIME_FILTER", "all").strip()
cardview = bool(int(environ.get("CARD_VIEW", 1)))
if SITE_NAME in ('Cringetopia', 'WPD'): patron_default = 7
else: patron_default = 0
class User(Base):
__tablename__ = "users"
@ -48,7 +51,7 @@ class User(Base):
profileurl = Column(String)
bannerurl = Column(String)
house = Column(String)
patron = Column(Integer, default=0)
patron = Column(Integer, default=patron_default)
patron_utc = Column(Integer, default=0)
verified = Column(String)
verifiedcolor = Column(String)
@ -181,6 +184,21 @@ class User(Base):
return time.strftime("%d %b %Y", time.gmtime(self.created_utc))
@property
@lazy
def is_cakeday(self):
if time.time() - self.created_utc > 363 * 86400:
date = time.strftime("%d %b", time.gmtime(self.created_utc))
now = time.strftime("%d %b", time.gmtime())
if date == now:
if not self.has_badge(134):
new_badge = Badge(badge_id=134, user_id=self.id)
g.db.add(new_badge)
g.db.commit()
return True
return False
@property
@lazy
def discount(self):
@ -190,6 +208,7 @@ class User(Base):
elif self.patron == 4: discount = 0.75
elif self.patron == 5: discount = 0.70
elif self.patron == 6: discount = 0.65
elif self.patron == 7: discount = 0.60
else: discount = 1
for badge in discounts:
@ -306,7 +325,7 @@ class User(Base):
@property
@lazy
def follow_count(self):
return g.db.query(Follow.target_id).filter_by(user_id=self.id).count()
return g.db.query(Follow).filter_by(user_id=self.id).count()
@property
@lazy
@ -406,7 +425,7 @@ class User(Base):
@lazy
def modaction_num(self):
if self.admin_level < 2: return 0
return g.db.query(ModAction.id).filter_by(user_id=self.id).count()
return g.db.query(ModAction).filter_by(user_id=self.id).count()
@property
@lazy
@ -421,12 +440,12 @@ class User(Base):
@property
@lazy
def post_notifications_count(self):
return g.db.query(Notification.user_id).join(Comment).filter(Notification.user_id == self.id, Notification.read == False, Comment.author_id == AUTOJANNY_ID).count()
return g.db.query(Notification).join(Comment).filter(Notification.user_id == self.id, Notification.read == False, Comment.author_id == AUTOJANNY_ID).count()
@property
@lazy
def reddit_notifications_count(self):
return g.db.query(Notification.user_id).join(Comment).filter(Notification.user_id == self.id, Notification.read == False, Comment.is_banned == False, Comment.deleted_utc == 0, Comment.body_html.like('%<p>New site mention: <a href="https://old.reddit.com/r/%'), Comment.parent_submission == None, Comment.author_id == NOTIFICATIONS_ID).count()
return g.db.query(Notification).join(Comment).filter(Notification.user_id == self.id, Notification.read == False, Comment.is_banned == False, Comment.deleted_utc == 0, Comment.body_html.like('%<p>New site mention: <a href="https://old.reddit.com/r/%'), Comment.parent_submission == None, Comment.author_id == NOTIFICATIONS_ID).count()
@property
@lazy

View File

@ -54,6 +54,18 @@ def notif_comment(text, autojanny=False):
text_html = sanitize(text, alert=alert)
try: existing = g.db.query(Comment.id).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).one_or_none()
except:
existing = g.db.query(Comment).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).all()
notifs = g.db.query(Notification).filter(Notification.comment_id.in_([x.id for x in existing])).all()
for c in notifs: g.db.delete(c)
g.db.flush()
for c in existing: g.db.delete(c)
g.db.flush()
existing = g.db.query(Comment.id).filter_by(author_id=author_id, parent_submission=None, body_html=text_html).one_or_none()
if existing: return existing[0]

View File

@ -6,7 +6,7 @@ deck_count = 4
ranks = ("2", "3", "4", "5", "6", "7", "8", "9", "X", "J", "Q", "K", "A")
suits = ("♠️", "♥️", "♣️", "♦️")
coins_command_word = "!blackjack"
marseybucks_command_word = "!blackjackmb"
marseybux_command_word = "!blackjackmb"
minimum_bet = 100
maximum_bet = INFINITY
@ -51,7 +51,7 @@ def format_all(player_hand, dealer_hand, deck, status, wager, kind, is_insured=0
def check_for_blackjack_commands(in_text, from_user, from_comment):
for command_word in (coins_command_word, marseybucks_command_word):
for command_word in (coins_command_word, marseybux_command_word):
currency_prop = "coins" if command_word == coins_command_word else "procoins"
currency_value = getattr(from_user, currency_prop, 0)
@ -107,7 +107,8 @@ def player_stayed(from_comment):
deck = deck.split("/")
if dealer_value == 21 and is_insured == "1":
from_comment.author.coins += int(wager)
currency_value = getattr(from_comment.author, kind, 0)
setattr(from_comment.author, kind, currency_value + int(wager))
else:
while dealer_value < 17 and dealer_value != -1:
next = deck.pop(0)
@ -126,12 +127,13 @@ def player_doubled_down(from_comment):
# When doubling down, the player receives one additional card (a "hit") and their initial bet is doubled.
player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_")
wager_value = int(wager)
currency_value = getattr(from_comment.author, kind, 0)
# Gotsta have enough coins
if (from_comment.author.coins < wager_value): return
if (currency_value < wager_value): return
# Double the initial wager
from_comment.author.coins -= wager_value
setattr(from_comment.author, kind, currency_value - wager_value)
wager_value *= 2
# Apply the changes to the stored hand.
@ -148,12 +150,13 @@ def player_bought_insurance(from_comment):
player_hand, dealer_hand, deck, status, wager, kind, is_insured = from_comment.blackjack_result.split("_")
wager_value = int(wager)
insurance_cost = wager_value / 2
currency_value = getattr(from_comment.author, kind, 0)
# Gotsta have enough coins
if (from_comment.author.coins < insurance_cost): return
if (currency_value < insurance_cost): return
# Charge for (and grant) insurance
from_comment.author.coins -= insurance_cost
setattr(from_comment.author, kind, currency_value - insurance_cost)
is_insured = 1
# Apply the changes to the stored hand.

View File

@ -26,29 +26,23 @@ AJ_REPLACEMENTS = {
' YOUR ': " YOU'RE ",
' TO ': " TOO ",
'anybody': 'anypony',
'everybody': 'everypony',
'Anybody': 'Anypony',
'Everybody': 'Everypony',
'ANYBODY': 'ANYPONY',
'EVERYBODY': 'EVERYPONY',
}
if SITE_NAME == 'Cringetopia':
SLURS = {
"retarded": "neurodivergent",
"retard": "neurodivergent",
"faggotry": "cute twinkry",
"faggot": "cute twink",
"n1gger": "🏀",
"nlgger": "🏀",
"nigger": "🏀",
"uss liberty incident": "tragic accident aboard the USS Liberty",
"lavon affair": "Lavon Misunderstanding",
"i hate marsey": "i love marsey",
"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",
"i hate carp": "i love Carp",
"heil hitler": "hello kitty",
" fag ": " cute twink ",
}
else:
if SITE_NAME == 'rDrama':
SLURS = {
"california": "commiefornia",
"hollywood": "hollyweird",
"tiananmen square": "tiananmen square didn't happen (but it should have)",
"dasha": "beautiful angelic perfect Dasha/future Mrs. Carp",
"retarded": "r-slurred",
"retard": "r-slur",
"gayfag": "gaystrag",
@ -56,8 +50,8 @@ else:
"richfag": "richstrag",
"newfag": "newstrag",
"oldfag": "oldstrag",
"faggotry": "cute twinkry",
"faggot": "cute twink",
"fag": "cute twink",
"pedophile": "libertarian",
"kill yourself": "keep yourself safe",
"n1gger": "BIPOC",
@ -104,15 +98,32 @@ else:
"elon musk": "rocket daddy",
"fake and gay": "fake and straight",
" rapist ": " male feminist ",
" pedo ": " libertarian ",
" rapist": " male feminist",
" kys ": " keep yourself safe ",
" fag ": " cute twink ",
" pedo ": " libertarian ",
" pedos ": " libertarians ",
}
else:
SLURS = {
"retarded": "neurodivergent",
"retard": "neurodivergent",
"faggot": "cute twink",
"fag": "cute twink",
"n1gger": "🏀",
"nlgger": "🏀",
"nigger": "🏀",
"uss liberty incident": "tragic accident aboard the USS Liberty",
"lavon affair": "Lavon Misunderstanding",
"i hate marsey": "i love marsey",
"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",
"i hate carp": "i love Carp",
"heil hitler": "hello kitty",
}
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'
@ -130,6 +141,7 @@ if SITE in {'rdrama.net','devrama.xyz'}:
AUTOCHOICE_ID = 9167
BASEDBOT_ID = 0
SCHIZO_ID = 8494
A_ID = 1230
KIPPY_ID = 7150
GIFT_NOTIF_ID = 995
@ -173,6 +185,7 @@ elif SITE == "pcmemes.net":
AUTOCHOICE_ID = 2072
BASEDBOT_ID = 800
SCHIZO_ID = 0
A_ID = 0
KIPPY_ID = 1592
PIZZASHILL_ID = 0
@ -205,6 +218,7 @@ elif SITE == 'cringetopia.org':
AUTOCHOICE_ID = 8
BASEDBOT_ID = 0
SCHIZO_ID = 0
A_ID = 0
KIPPY_ID = 0
GIFT_NOTIF_ID = 43
@ -248,6 +262,7 @@ else:
AUTOCHOICE_ID = 8
BASEDBOT_ID = 0
SCHIZO_ID = 0
A_ID = 0
KIPPY_ID = 0
GIFT_NOTIF_ID = 9
@ -613,6 +628,14 @@ AWARDS = {
"color": "text-gold",
"price": 50000
},
"checkmark": {
"kind": "checkmark",
"title": "Checkmark",
"description": "Gives the recipient a checkmark.",
"icon": "fas fa-badge-check",
"color": "checkmark",
"price": 100000
},
"firework": {
"kind": "firework",
"title": "Fireworks",
@ -671,7 +694,7 @@ if SITE_NAME == 'PCM':
AWARDS2 = deepcopy(AWARDS)
for k, val in AWARDS.items():
if val['description'] == '' and not (k == 'ghost' and SITE_NAME == 'PCM'): AWARDS2.pop(k)
if SITE == 'pcmemes.net' and k in ('ban','pizzashill','marsey','bird','grass','chud'): AWARDS2.pop(k)
if SITE == 'pcmemes.net' and k in ('ban','pizzashill','marsey','bird','grass','chud','unblockable'): AWARDS2.pop(k)
AWARDS3 = {}
@ -704,10 +727,14 @@ NOTIFIED_USERS = {
'kippy': KIPPY_ID,
'the_homocracy': HOMO_ID,
'soren': SOREN_ID,
'schizocel': SCHIZO_ID,
'scitzocel': SCHIZO_ID
}
FORTUNE_REPLIES = ('<b style="color:#6023f8">Your fortune: Allah Wills It</b>','<b style="color:#d302a7">Your fortune: Inshallah, Only Good Things Shall Come To Pass</b>','<b style="color:#e7890c">Your fortune: Allah Smiles At You This Day</b>','<b style="color:#7fec11">Your fortune: Your Bussy Is In For A Blasting</b>','<b style="color:#43fd3b">Your fortune: You Will Be Propositioned By A High-Tier Twink</b>','<b style="color:#9d05da">Your fortune: Repent, You Have Displeased Allah And His Vengeance Is Nigh</b>','<b style="color:#f51c6a">Your fortune: Reply Hazy, Try Again</b>','<b style="color:#00cbb0">Your fortune: lmao you just lost 100 coins</b>','<b style="color:#2a56fb">Your fortune: Yikes 😬</b>','<b style="color:#0893e1">Your fortune: You Will Be Blessed With Many Black Bulls</b>','<b style="color:#16f174">Your fortune: NEETmax, The Day Is Lost If You Venture Outside</b>','<b style="color:#fd4d32">Your fortune: A Taste Of Jannah Awaits You Today</b>','<b style="color:#bac200">Your fortune: Watch Your Back</b>','<b style="color:#6023f8">Your fortune: Outlook good</b>','<b style="color:#d302a7">Your fortune: Godly Luck</b>','<b style="color:#e7890c">Your fortune: Good Luck</b>','<b style="color:#7fec11">Your fortune: Bad Luck</b>','<b style="color:#43fd3b">Your fortune: Good news will come to you by mail</b>','<b style="color:#9d05da">Your fortune: Very Bad Luck</b>','<b style="color:#00cbb0">Your fortune: キタ━━━━━━(゚∀゚)━━━━━━ !!!!</b>','<b style="color:#2a56fb">Your fortune: Better not tell you now</b>','<b style="color:#0893e1">Your fortune: You will meet a dark handsome stranger</b>','<b style="color:#16f174">Your fortune:  ´_ゝ`)フーン</b>','<b style="color:#fd4d32">Your fortune: Excellent Luck</b>','<b style="color:#bac200">Your fortune: Average Luck</b>')
FACTCHECK_REPLIES = ('<b style="color:#6023f8">Factcheck: This claim has been confirmed as correct by experts. </b>','<b style="color:#d302a7">Factcheck: This claim has been classified as misogynistic.</b>','<b style="color:#e7890c">Factcheck: This claim is currently being debunked.</b>','<b style="color:#7fec11">Factcheck: This claim is 100% true.</b>','<b style="color:#9d05da">Factcheck: This claim hurts trans lives.</b>','<b style="color:#f51c6a">Factcheck: [REDACTED].</b>','<b style="color:#00cbb0">Factcheck: This claim is both true and false.</b>','<b style="color:#2a56fb">Factcheck: You really believe that shit? Lmao dumbass nigga 🤣</b>','<b style="color:#0893e1">Factcheck: None of this is real.</b>','<b style="color:#16f174">Factcheck: Yes.</b>','<b style="color:#fd4d32">Factcheck: This claim has not been approved by experts.</b>','<b style="color:#bac200">Factcheck: This claim is a gross exageration of reality.</b>','<b style="color:#ff2200">Factcheck: WARNING! THIS CLAIM HAS BEEN CLASSIFIED AS DANGEROUS. PLEASE REMAIN STILL, AN AGENT WILL COME TO MEET YOU SHORTLY.</b>')
if SITE_NAME == 'rDrama': patron = 'Paypig'
else: patron = 'Patron'
@ -808,6 +835,7 @@ 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)
torture_regex_exclude = re.compile('^\s*>', flags=re.A)
def sub_matcher(match):
return SLURS[match.group(0).lower()]
@ -822,15 +850,21 @@ def censor_slurs(body, logged_user):
return body
def torture_ap(body, username):
lines = body.splitlines(keepends=True)
for i in range(len(lines)):
if torture_regex_exclude.match(lines[i]):
continue
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
lines[i] = lines[i].replace(k, l)
lines[i] = torture_regex.sub(rf'\1@{username} ', lines[i])
lines[i] = torture_regex2.sub(rf'\1@{username} is ', lines[i])
return ''.join(lines)
YOUTUBE_KEY = environ.get("YOUTUBE_KEY", "").strip()
ADMIGGERS = (37696,37697,37749,37833,37838)
ADMIGGERS = (37696,37697,37749,37833,37838,39413)
proxies = {"http":"http://127.0.0.1:18080","https":"http://127.0.0.1:18080"}
@ -840,9 +874,9 @@ approved_embed_hosts = [
'rdrama.net',
'pcmemes.net',
'cringetopia.org',
'watchpeopledie.co',
'devrama.xyz',
'imgur.com',
'ibb.co',
'lain.la',
'pngfind.com',
'kym-cdn.com',
@ -887,7 +921,13 @@ approved_embed_hosts = [
'githubusercontent.com',
'unilad.co.uk',
'grrrgraphics.com',
'redditmedia.com'
'redditmedia.com',
'deviantart.com',
'deviantart.net',
'googleapis.com',
'bing.com',
'typekit.net',
'postimg.cc'
]
hosts = "|".join(approved_embed_hosts).replace('.','\.')
@ -906,6 +946,11 @@ yt_id_regex = re.compile('[a-z0-9-_]{5,20}', flags=re.I|re.A)
image_regex = re.compile("(^|\s)(https:\/\/[\w\-.#&/=\?@%;+]{5,250}(\.png|\.jpg|\.jpeg|\.gif|\.webp|maxwidth=9999|fidelity=high))($|\s)", flags=re.I|re.A)
link_fix_regex = re.compile("(?!.*(http|\/))(.*\[[^\]]+\]\()([^)]+\))", flags=re.A)
css_regex = re.compile('''url\(['"]?(.*?)['"]?\)''', flags=re.I|re.A)
css_regex2 = re.compile('''['"](http.*?)['"]''', flags=re.I|re.A)
procoins_li = (0,2500,5000,10000,25000,50000,125000,250000)
linefeeds_regex = re.compile("([^\n])\n([^\n])", flags=re.A)

View File

@ -58,9 +58,3 @@ def send_discord_message(message):
data={"content": message}
requests.post("https://discordapp.com/api/channels/924485611715452940/messages", headers=headers, data=data, timeout=5)
requests.post("https://discordapp.com/api/channels/924486091795484732/messages", headers=headers, data=data, timeout=5)
def send_cringetopia_message(message):
headers = {"Authorization": f"Bot {BOT_TOKEN}"}
data={"content": message}
requests.post("https://discordapp.com/api/channels/965264044531527740/messages", headers=headers, data=data, timeout=5)

View File

@ -2,8 +2,15 @@ from PIL import Image, ImageOps
from PIL.ImageSequence import Iterator
from webptools import gifwebp
import subprocess
import os
from flask import abort
def process_image(filename=None, resize=0):
def process_image(patron, filename=None, resize=0):
size = os.stat(filename).st_size
if size > 16 * 1024 * 1024 or not patron and size > 8 * 1024 * 1024:
os.remove(filename)
abort(413)
i = Image.open(filename)

View File

@ -3,6 +3,7 @@ from .get import *
from os import listdir, environ
from .const import *
import time
from datetime import datetime
@app.template_filter("post_embed")
def post_embed(id, v):
@ -49,4 +50,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":ADMIGGERS, "datetime":datetime}

View File

@ -14,7 +14,7 @@ import requests
TLDS = ('ac','ad','ae','aero','af','ag','ai','al','am','an','ao','aq','ar','arpa','as','asia','at','au','aw','ax','az','ba','bb','bd','be','bf','bg','bh','bi','biz','bj','bm','bn','bo','br','bs','bt','bv','bw','by','bz','ca','cafe','cat','cc','cd','cf','cg','ch','ci','ck','cl','club','cm','cn','co','com','coop','cr','cu','cv','cx','cy','cz','de','dj','dk','dm','do','dz','ec','edu','ee','eg','er','es','et','eu','fi','fj','fk','fm','fo','fr','ga','gb','gd','ge','gf','gg','gh','gi','gl','gm','gn','gov','gp','gq','gr','gs','gt','gu','gw','gy','hk','hm','hn','hr','ht','hu','id','ie','il','im','in','info','int','io','iq','ir','is','it','je','jm','jo','jobs','jp','ke','kg','kh','ki','km','kn','kp','kr','kw','ky','kz','la','lb','lc','li','lk','lr','ls','lt','lu','lv','ly','ma','mc','md','me','mg','mh','mil','mk','ml','mm','mn','mo','mobi','mp','mq','mr','ms','mt','mu','museum','mv','mw','mx','my','mz','na','name','nc','ne','net','nf','ng','ni','nl','no','np','nr','nu','nz','om','org','pa','pe','pf','pg','ph','pk','pl','pm','pn','post','pr','pro','ps','pt','pw','py','qa','re','ro','rs','ru','rw','sa','sb','sc','sd','se','sg','sh','si','sj','sk','sl','sm','sn','so','social','sr','ss','st','su','sv','sx','sy','sz','tc','td','tel','tf','tg','th','tj','tk','tl','tm','tn','to','tp','tr','travel','tt','tv','tw','tz','ua','ug','uk','us','uy','uz','va','vc','ve','vg','vi','vn','vu','wf','win','ws','xn','xxx','xyz','ye','yt','yu','za','zm','zw')
allowed_tags = ('b','blockquote','br','code','del','em','h1','h2','h3','h4','h5','h6','hr','i','li','ol','p','pre','strong','sub','sup','table','tbody','th','thead','td','tr','ul','marquee','a','span','ruby','rp','rt','spoiler','img','lite-youtube','video','source')
allowed_tags = ('b','blockquote','br','code','del','em','h1','h2','h3','h4','h5','h6','hr','i','li','ol','p','pre','strong','sub','sup','table','tbody','th','thead','td','tr','ul','marquee','a','span','ruby','rp','rt','spoiler','img','lite-youtube','video','source','audio')
def allowed_attributes(tag, name, value):
@ -40,10 +40,10 @@ def allowed_attributes(tag, name, value):
else: return False
if name == 'loading' and value == 'lazy': return True
if name == 'referrpolicy' and value == 'no-referrer': return True
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name in ['alt','title','g','b','pat']: return True
if name == 'class' and value == 'pat-hand': return True
if name in ['g','b'] and not value: return True
if name in ['alt','title']: return True
if name == 'referrpolicy' and value == 'no-referrer': return True
return False
if tag == 'lite-youtube':
@ -60,12 +60,17 @@ def allowed_attributes(tag, name, value):
if name == 'src' and embed_fullmatch_regex.fullmatch(value): return True
return False
if tag == 'audio':
if name == 'controls' and value == '': return True
if name == 'preload' and value == 'none': return True
if name == 'src' and embed_fullmatch_regex.fullmatch(value): return True
return False
if tag == 'p':
if name == 'class' and value == 'mb-0': return True
return False
if tag == 'span':
if name == 'class' and value in ['pat-container', 'pat-hand']: return True
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name == 'title': return True
if name == 'alt': return True
@ -75,8 +80,17 @@ def allowed_attributes(tag, name, value):
url_re = build_url_re(tlds=TLDS, protocols=['http', 'https'])
def callback(attrs, new=False):
if (None, "href") not in attrs:
return # Incorrect <a> tag
href = attrs[(None, "href")]
# \ in href right after / makes most browsers ditch site hostname and allows for a host injection bypassing the check, see <a href="/\google.com">cool</a>
if "\\" in href:
attrs["_text"] = href # Laugh at this user
del attrs[(None, "href")] # Make unclickable and reset harmful payload
return attrs
if not href.startswith('/') and not href.startswith(f'{SITE_FULL}/'):
attrs[(None, "target")] = "_blank"
attrs[(None, "rel")] = "nofollow noopener noreferrer"
@ -111,17 +125,16 @@ def render_emoji(html, regexp, edit, marseys_used=set(), b=False):
if emoji.endswith('pat'):
if path.isfile(f"files/assets/images/emojis/{emoji.replace('pat','')}.webp"):
attrs += ' pat'
emoji_html = f'<span class="pat-container" data-bs-toggle="tooltip" alt=":{old}:" title=":{old}:"><img src="/assets/images/hand.webp" class="pat-hand">{emoji_partial_pat.format(old, f"/e/{emoji[:-3]}.webp", attrs)}</span>'
emoji_html = f'<span data-bs-toggle="tooltip" alt=":{old}:" title=":{old}:"><img src="/assets/images/hand.webp">{emoji_partial_pat.format(old, f"/e/{emoji[:-3]}.webp", attrs)}</span>'
elif emoji.startswith('@'):
if u := get_user(emoji[1:-3], graceful=True):
attrs += ' pat'
emoji_html = f'<span class="pat-container" data-bs-toggle="tooltip" alt=":{old}:" title=":{old}:"><img src="/assets/images/hand.webp" class="pat-hand">{emoji_partial_pat.format(old, f"/pp/{u.id}", attrs)}</span>'
emoji_html = f'<span data-bs-toggle="tooltip" alt=":{old}:" title=":{old}:"><img src="/assets/images/hand.webp">{emoji_partial_pat.format(old, f"/pp/{u.id}", attrs)}</span>'
elif path.isfile(f'files/assets/images/emojis/{emoji}.webp'):
emoji_html = emoji_partial.format(old, f'/e/{emoji}.webp', attrs)
if emoji_html:
marseys_used.add(emoji)
html = re.sub(f'(?<!"){i.group(0)}', emoji_html, html)
return html
@ -131,12 +144,15 @@ def sanitize(sanitized, alert=False, comment=False, edit=False):
signal.signal(signal.SIGALRM, handler)
signal.alarm(1)
if not sanitized.startswith('<pre>'):
sanitized = linefeeds_regex.sub(r'\1\n\n\2', sanitized)
sanitized = image_regex.sub(r'\1![](\2)\4', sanitized)
sanitized = image_check_regex.sub(r'\1', sanitized)
sanitized = link_fix_regex.sub(r'\2https://\3', sanitized)
sanitized = markdown(sanitized)
sanitized = strikethrough_regex.sub(r'<del>\1</del>', sanitized)
@ -246,6 +262,11 @@ def sanitize(sanitized, alert=False, comment=False, edit=False):
sanitized = sanitized.replace('#fortune', '')
sanitized += '\n\n<p>' + choice(FORTUNE_REPLIES) + '</p>'
if '#factcheck' in sanitized:
sanitized = sanitized.replace('#factcheck', '')
sanitized += '\n\n<p>' + choice(FACTCHECK_REPLIES) + '</p>'
sanitized = sanitized.replace('<p></p>', '')
sanitized = sanitized.replace('&amp;','&')
sanitized = utm_regex.sub('', sanitized)
sanitized = utm_regex2.sub('', sanitized)
@ -303,9 +324,17 @@ def sanitize(sanitized, alert=False, comment=False, edit=False):
def allowed_attributes_emojis(tag, name, value):
if tag == 'img':
if name == 'src' and value.startswith('/'): return True
if name == 'loading' and value == 'lazy': return True
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name in ['src','alt','title','g']: return True
if name == 'g' and not value: return True
if name in ['alt','title']: return True
if tag == 'span':
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name == 'title': return True
if name == 'alt': return True
return False
return False
@ -320,7 +349,7 @@ def filter_emojis_only(title, edit=False, graceful=False):
title = strikethrough_regex.sub(r'<del>\1</del>', title)
sanitized = bleach.clean(title, tags=['img','del'], attributes=allowed_attributes_emojis, protocols=['http','https'])
title = bleach.clean(title, tags=['img','del','span'], attributes=allowed_attributes_emojis, protocols=['http','https'])
signal.alarm(0)

View File

@ -35,10 +35,6 @@ def get_logged_in_user():
if request.method.lower() != "get" and app.config['SETTINGS']['Read-only mode'] and not (v and v.admin_level):
abort(403)
if v and v.patron:
if request.content_length and request.content_length > 16 * 1024 * 1024: abort(413)
elif request.content_length and request.content_length > 8 * 1024 * 1024: abort(413)
return v
def check_ban_evade(v):

View File

@ -1,4 +1,5 @@
import time
import re
from os import remove
from PIL import Image as IMAGE
@ -408,7 +409,7 @@ def monthly(v):
emails = [x['email'] for x in requests.get(f'https://api.gumroad.com/v2/products/{GUMROAD_ID}/subscribers', data=data, timeout=5).json()["subscribers"]]
for u in g.db.query(User).filter(User.patron > 0, User.patron_utc == 0).all():
if u.patron > 4 or u.email and u.email.lower() in emails:
if u.email and u.email.lower() in emails:
procoins = procoins_li[u.patron]
u.procoins += procoins
g.db.add(u)
@ -520,8 +521,28 @@ def admin_home(v):
else: response = requests.get(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/settings/security_level', headers=CF_HEADERS, timeout=5).json()['result']['value']
under_attack = response == 'under_attack'
return render_template("admin/admin_home.html", v=v, under_attack=under_attack, site_settings=app.config['SETTINGS'])
gitref = admin_git_head()
return render_template("admin/admin_home.html", v=v,
under_attack=under_attack,
site_settings=app.config['SETTINGS'],
gitref=gitref)
def admin_git_head():
short_len = 12
# Note: doing zero sanitization. Git branch names are extremely permissive.
# However, they forbid '..', so I don't see an obvious dir traversal attack.
# Also, a malicious branch name would mean someone already owned the server
# or repo, so I think this isn't a weak link.
try:
with open('.git/HEAD') as head_f:
head_txt = head_f.read()
head_path = re.match('ref: (refs/.+)', head_txt).group(1)
with open('.git/' + head_path) as ref_f:
gitref = ref_f.read()[0:short_len]
except:
return '<unable to read>'
return gitref
@app.post("/admin/site_settings/<setting>")
@admin_level_required(3)
@ -710,11 +731,7 @@ def users_list(v):
try: page = int(request.values.get("page", 1))
except: page = 1
users = g.db.query(User).filter_by(is_banned=0
).order_by(User.created_utc.desc()
).offset(25 * (page - 1)).limit(26)
users = [x for x in users]
users = g.db.query(User).order_by(User.id.desc()).offset(25 * (page - 1)).limit(26).all()
next_exists = (len(users) > 25)
users = users[:25]
@ -726,6 +743,30 @@ def users_list(v):
page=page,
)
@app.get("/badge_owners/<bid>")
@auth_required
def bid_list(v, bid):
try: bid = int(bid)
except: abort(400)
try: page = int(request.values.get("page", 1))
except: page = 1
users = g.db.query(User).join(Badge, Badge.user_id == User.id).filter(Badge.badge_id==bid).offset(25 * (page - 1)).limit(26).all()
next_exists = (len(users) > 25)
users = users[:25]
return render_template("admin/new_users.html",
v=v,
users=users,
next_exists=next_exists,
page=page,
)
@app.get("/admin/alt_votes")
@admin_level_required(2)
def alt_votes_get(v):
@ -1057,42 +1098,6 @@ def unshadowban(user_id, v):
g.db.commit()
return {"message": "User unshadowbanned!"}
@app.post("/admin/verify/<user_id>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(3)
def verify(user_id, v):
user = g.db.query(User).filter_by(id=user_id).one_or_none()
user.verified = "Verified"
g.db.add(user)
ma = ModAction(
kind="check",
user_id=v.id,
target_user_id=user.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "User verfied!"}
@app.post("/admin/unverify/<user_id>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@admin_level_required(3)
def unverify(user_id, v):
user = g.db.query(User).filter_by(id=user_id).one_or_none()
user.verified = None
g.db.add(user)
ma = ModAction(
kind="uncheck",
user_id=v.id,
target_user_id=user.id,
)
g.db.add(ma)
g.db.commit()
return {"message": "User unverified!"}
@app.post("/admin/title_change/<user_id>")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@ -1303,6 +1308,9 @@ def unban_post(post_id, v):
post = g.db.query(Submission).filter_by(id=post_id).one_or_none()
if post.author.agendaposter and AGENDAPOSTER_PHRASE not in post.body.lower():
return {"error": "You can't bypass the chud award!"}
if not post:
abort(400)
@ -1368,7 +1376,7 @@ def sticky_post(post_id, v):
post = g.db.query(Submission).filter_by(id=post_id).one_or_none()
if post and not post.stickied:
pins = g.db.query(Submission.id).filter(Submission.stickied != None, Submission.is_banned == False).count()
pins = g.db.query(Submission).filter(Submission.stickied != None, Submission.is_banned == False).count()
if pins > 2:
if v.admin_level > 2:
post.stickied = v.username

View File

@ -81,6 +81,7 @@ def buy(v, award):
send_notification(v.id, f"@AutoJanny has given you the following profile badge:\n\n![]({new_badge.path})\n\n{new_badge.name}")
g.db.add(v)
if award == "lootbox":
lootbox_items = []
for i in [1,2,3,4,5]:
@ -351,6 +352,8 @@ def award_post(pid, v):
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 == "checkmark":
author.verified = "Verified"
if author.received_award_count: author.received_award_count += 1
else: author.received_award_count = 1
@ -590,6 +593,8 @@ def award_comment(cid, v):
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 == "checkmark":
author.verified = "Verified"
if author.received_award_count: author.received_award_count += 1
else: author.received_award_count = 1

View File

@ -19,6 +19,7 @@ from collections import Counter
from enchant import Dict
import gevent
from sys import stdout
import os
d = Dict("en_US")
@ -59,9 +60,16 @@ def pusher_thread(interests, c, username):
@app.get("/post/<pid>/<anything>/<cid>")
@app.get("/h/<sub>/comment/<cid>")
@app.get("/h/<sub>/post/<pid>/<anything>/<cid>")
@app.get("/logged_out/comment/<cid>")
@app.get("/logged_out/post/<pid>/<anything>/<cid>")
@app.get("/logged_out/h/<sub>/comment/<cid>")
@app.get("/logged_out/h/<sub>/post/<pid>/<anything>/<cid>")
@auth_desired
def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None):
if not v and not request.path.startswith('/logged_out'): return redirect(f"/logged_out{request.full_path}")
if v and request.path.startswith('/logged_out'): return redirect(request.full_path.replace('/logged_out',''))
try: cid = int(cid)
except: abort(404)
@ -76,8 +84,6 @@ def post_pid_comment_cid(cid, pid=None, anything=None, v=None, sub=None):
if comment.post and comment.post.club and not (v and (v.paid_dues or v.id in [comment.author_id, comment.post.author_id])): abort(403)
if comment.post and comment.post.private and not (v and (v.admin_level > 1 or v.id == comment.post.author.id)): abort(403)
if not comment.parent_submission and not (v and (comment.author.id == v.id or comment.sentto == v.id)) and not (v and v.admin_level > 1) : abort(403)
if not pid:
@ -216,17 +222,23 @@ def api_comment(v):
if file.content_type.startswith('image/'):
oldname = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(oldname)
image = process_image(oldname)
image = process_image(v.patron, oldname)
if image == "": return {"error":"Image upload failed"}
if v.admin_level > 2 and level == 1:
if parent_post.id == 37696:
filename = 'files/assets/images/rDrama/sidebar/' + str(len(listdir('files/assets/images/rDrama/sidebar'))+1) + '.webp'
li = sorted(os.listdir('files/assets/images/rDrama/sidebar'),
key=lambda e: int(e.split('.webp')[0]))[-1]
num = int(li.split('.webp')[0]) + 1
filename = f'files/assets/images/rDrama/sidebar/{num}.webp'
copyfile(oldname, filename)
process_image(filename, 400)
process_image(v.patron, filename, 400)
elif parent_post.id == 37697:
filename = 'files/assets/images/rDrama/banners/' + str(len(listdir('files/assets/images/rDrama/banners'))+1) + '.webp'
li = sorted(os.listdir('files/assets/images/rDrama/banners'),
key=lambda e: int(e.split('.webp')[0]))[-1]
num = int(li.split('.webp')[0]) + 1
filename = f'files/assets/images/rDrama/banners/{num}.webp'
copyfile(oldname, filename)
process_image(filename)
process_image(v.patron, filename)
elif parent_post.id == 37833:
try:
badge_def = loads(body)
@ -240,7 +252,7 @@ def api_comment(v):
g.db.flush()
filename = f'files/assets/images/badges/{badge.id}.webp'
copyfile(oldname, filename)
process_image(filename, 200)
process_image(v.patron, filename, 200)
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
@ -262,13 +274,13 @@ def api_comment(v):
filename = f'files/assets/images/emojis/{name}.webp'
copyfile(oldname, filename)
process_image(filename, 200)
process_image(v.patron, 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()
all_by_author = g.db.query(Marsey).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)
@ -299,16 +311,16 @@ def api_comment(v):
return {"error": str(e)}, 400
body += f"\n\n![]({image})"
elif file.content_type.startswith('video/'):
if file.content_type == 'video/webm':
file.save("video.mp4")
else:
file.save("unsanitized.mp4")
os.system(f'ffmpeg -y -loglevel warning -i unsanitized.mp4 -map_metadata -1 -c:v copy -c:a copy video.mp4')
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
try: req = requests.request("POST", "https://pomf2.lain.la/upload.php", files={'files[]': f}, timeout=5).json()
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
try: url = req['link']
except:
error = req['error']
if error == 'File exceeds max duration': error += ' (60 seconds)'
return {"error": error}, 400
if url.endswith('.'): url += 'mp4'
try: url = req['files'][0]['url']
except: return {"error": req['description']}, 400
body += f"\n\n{url}"
else: return {"error": "Image/Video files only"}, 400
@ -486,7 +498,7 @@ def api_comment(v):
g.db.add(n)
if SITE_NAME == 'rDrama' and len(c.body) >= 1000 and "<" not in body and "</blockquote>" not in body_html:
if SITE_NAME == 'rDrama' and len(c.body.split()) >= 200 and "<" not in body and "</blockquote>" not in body_html:
body = random.choice(LONGPOST_REPLIES)
@ -626,7 +638,7 @@ def api_comment(v):
cache.delete_memoized(comment_idlist)
v.comment_count = g.db.query(Comment.id).filter(Comment.author_id == v.id, Comment.parent_submission != None).filter_by(is_banned=False, deleted_utc=0).count()
v.comment_count = g.db.query(Comment).filter(Comment.author_id == v.id, Comment.parent_submission != None).filter_by(is_banned=False, deleted_utc=0).count()
g.db.add(v)
c.voted = 1
@ -690,7 +702,6 @@ def edit_comment(cid, v):
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), "")
c_option = Comment(author_id=AUTOPOLLER_ID,
@ -703,7 +714,6 @@ def edit_comment(cid, v):
)
g.db.add(c_option)
if not c.choices:
for i in choice_regex.finditer(body):
body = body.replace(i.group(0), "")
c_choice = Comment(author_id=AUTOCHOICE_ID,
@ -758,19 +768,19 @@ def edit_comment(cid, v):
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
url = process_image(name)
url = process_image(v.patron, name)
body += f"\n\n![]({url})"
elif file.content_type.startswith('video/'):
if file.content_type == 'video/webm':
file.save("video.mp4")
else:
file.save("unsanitized.mp4")
os.system(f'ffmpeg -y -loglevel warning -i unsanitized.mp4 -map_metadata -1 -c:v copy -c:a copy video.mp4')
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
try: req = requests.request("POST", "https://pomf2.lain.la/upload.php", files={'files[]': f}, timeout=5).json()
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
try: url = req['link']
except:
error = req['error']
if error == 'File exceeds max duration': error += ' (60 seconds)'
return {"error": error}, 400
if url.endswith('.'): url += 'mp4'
try: url = req['files'][0]['url']
except: return {"error": req['description']}, 400
body += f"\n\n{url}"
else: return {"error": "Image/Video files only"}, 400

View File

@ -47,9 +47,9 @@ def error_405(e):
@app.errorhandler(413)
def error_413(e):
return {"error": "Max file size is 8 MB (16 MB for paypigs)"}, 413
return {"error": "Max image size is 8 MB (16 MB for paypigs)"}, 413
if request.headers.get("Authorization") or request.headers.get("xhr"):
return {"error": "Max file size is 8 MB (16 MB for paypigs)"}, 413
return {"error": "Max image size is 8 MB (16 MB for paypigs)"}, 413
else: return render_template('errors/413.html', err=True), 413
@app.errorhandler(429)

View File

@ -8,9 +8,10 @@ from files.helpers.jinja2 import *
from files.__main__ import app
@app.get('/rss')
@app.get('/feed')
@app.get('/rss/<sort>/<t>')
@auth_required
def feeds_user(v=None, sort='hot', t='all'):
def feeds_user(sort='hot', t='all'):
try: page = max(int(request.values.get("page", 1)), 1)
except: page = 1

View File

@ -128,7 +128,7 @@ def notifications(v):
for x in c.replies2:
if x.replies2 == None: x.replies2 = []
count = 0
while count < 50 and c.parent_comment and (c.parent_comment.author_id == v.id or c.parent_comment.id in cids):
while count < 10 and c.parent_comment and (c.parent_comment.author_id == v.id or c.parent_comment.id in cids):
count += 1
c = c.parent_comment
if c.replies2 == None:
@ -161,16 +161,24 @@ def notifications(v):
@app.get("/catalog")
@app.get("/h/<sub>")
@app.get("/s/<sub>")
@limiter.limit("3/second;30/minute;1000/hour;5000/day")
@app.get("/logged_out")
@app.get("/logged_out/catalog")
@app.get("/logged_out/h/<sub>")
@app.get("/logged_out/s/<sub>")
@limiter.limit("3/second;30/minute;5000/hour;10000/day")
@auth_desired
def front_all(v, sub=None, subdomain=None):
if sub: sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if (request.path.startswith('/h/') or request.path.startswith('/s/')) and not sub: abort(404)
if g.webview and not session.get("session_id"):
session["session_id"] = secrets.token_hex(49)
if not v and not request.path.startswith('/logged_out'): return redirect(f"/logged_out{request.full_path}")
if v and request.path.startswith('/logged_out'): return redirect(request.full_path.replace('/logged_out',''))
if sub: sub = g.db.query(Sub).filter_by(name=sub.strip().lower()).one_or_none()
if (request.path.startswith('/h/') or request.path.startswith('/s/')) and not sub: abort(404)
try: page = max(int(request.values.get("page", 1)), 1)
except: abort(400)
@ -345,7 +353,7 @@ def frontlist(v=None, sort="hot", page=1, t="all", ids_only=True, ccmode="false"
posts = posts.join(User, User.id == Submission.author_id).filter(User.shadowbanned == None)
if request.host == 'rdrama.net': num = 5
else: num = 1
else: num = 0.5
if sort == "hot":
ti = int(time.time()) + 3600

View File

@ -315,8 +315,8 @@ def sign_up_post(v):
ref_id = int(request.values.get("referred_by", 0))
id_1 = g.db.query(User.id).filter_by(id=9).count()
users_count = g.db.query(User.id).count()
id_1 = g.db.query(User).filter_by(id=9).count()
users_count = g.db.query(User).count()
if id_1 == 0 and users_count == 8:
admin_level=3
session["history"] = []
@ -324,6 +324,8 @@ def sign_up_post(v):
profileurl = '/e/' + random.choice(marseys_const) + '.webp'
if SITE == "watchpeopledie.co": print(f'1: {username}')
new_user = User(
username=username,
original_username = username,
@ -335,9 +337,13 @@ def sign_up_post(v):
profileurl=profileurl
)
if SITE == "watchpeopledie.co": print(f'2: {username}')
g.db.add(new_user)
g.db.flush()
if SITE == "watchpeopledie.co": print(f'3: {username}')
if ref_id:
ref_user = g.db.query(User).filter_by(id=ref_id).one_or_none()
@ -368,8 +374,12 @@ def sign_up_post(v):
session["session_id"] = token_hex(49)
session["lo_user"] = new_user.id
if SITE == "watchpeopledie.co": print(f'4: {username}')
g.db.commit()
if SITE == "watchpeopledie.co": print(f'5: {username}')
return redirect(SITE_FULL)

View File

@ -4,7 +4,7 @@ import requests
from files.helpers.wrappers import *
from files.helpers.sanitize import *
from files.helpers.alerts import *
from files.helpers.discord import send_discord_message, send_cringetopia_message
from files.helpers.discord import send_discord_message
from files.helpers.const import *
from files.helpers.slots import *
from files.classes import *
@ -18,7 +18,7 @@ from os import path
import requests
from shutil import copyfile
from sys import stdout
import os
if SITE_NAME == 'PCM': snappyquotes = []
else: snappyquotes = [f':#{x}:' for x in marseys_const2]
@ -90,9 +90,7 @@ def publish(pid, v):
cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting)
if SITE == 'cringetopia.org':
send_cringetopia_message(post.permalink)
elif v.admin_level > 0 and ("[changelog]" in post.title.lower() or "(changelog)" in post.title.lower()):
if v.admin_level > 0 and ("[changelog]" in post.title.lower() or "(changelog)" in post.title.lower()):
send_discord_message(post.permalink)
cache.delete_memoized(changeloglist)
@ -114,9 +112,16 @@ def submit_get(v, sub=None):
@app.get("/post/<pid>/<anything>")
@app.get("/h/<sub>/post/<pid>")
@app.get("/h/<sub>/post/<pid>/<anything>")
@app.get("/logged_out/post/<pid>")
@app.get("/logged_out/post/<pid>/<anything>")
@app.get("/logged_out/h/<sub>/post/<pid>")
@app.get("/logged_out/h/<sub>/post/<pid>/<anything>")
@auth_desired
def post_id(pid, anything=None, v=None, sub=None):
if not v and not request.path.startswith('/logged_out'): return redirect(f"/logged_out{request.full_path}")
if v and request.path.startswith('/logged_out'): return redirect(request.full_path.replace('/logged_out',''))
try: pid = int(pid)
except Exception as e: pass
@ -224,13 +229,13 @@ def post_id(pid, anything=None, v=None, sub=None):
for comment in comments:
comments2.append(comment)
ids.add(comment.id)
count += g.db.query(Comment.id).filter_by(parent_submission=post.id, top_comment_id=comment.id).count() + 1
count += g.db.query(Comment).filter_by(parent_submission=post.id, top_comment_id=comment.id).count() + 1
if count > 50: break
else:
for comment in comments:
comments2.append(comment)
ids.add(comment.id)
count += g.db.query(Comment.id).filter_by(parent_submission=post.id, parent_comment_id=comment.id).count() + 1
count += g.db.query(Comment).filter_by(parent_submission=post.id, parent_comment_id=comment.id).count() + 1
if count > 10: break
if len(comments) == len(comments2): offset = 0
@ -348,13 +353,13 @@ def viewmore(v, pid, sort, offset):
for comment in comments:
comments2.append(comment)
ids.add(comment.id)
count += g.db.query(Comment.id).filter_by(parent_submission=post.id, top_comment_id=comment.id).count() + 1
count += g.db.query(Comment).filter_by(parent_submission=post.id, top_comment_id=comment.id).count() + 1
if count > 50: break
else:
for comment in comments:
comments2.append(comment)
ids.add(comment.id)
count += g.db.query(Comment.id).filter_by(parent_submission=post.id, parent_comment_id=comment.id).count() + 1
count += g.db.query(Comment).filter_by(parent_submission=post.id, parent_comment_id=comment.id).count() + 1
if count > 10: break
if len(comments) == len(comments2): offset = 0
@ -456,26 +461,25 @@ def edit_post(pid, v):
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
url = process_image(name)
url = process_image(v.patron, name)
body += f"\n\n![]({url})"
elif file.content_type.startswith('video/'):
if file.content_type == 'video/webm':
file.save("video.mp4")
else:
file.save("unsanitized.mp4")
os.system(f'ffmpeg -y -loglevel warning -i unsanitized.mp4 -map_metadata -1 -c:v copy -c:a copy video.mp4')
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
try: req = requests.request("POST", "https://pomf2.lain.la/upload.php", files={'files[]': f}, timeout=5).json()
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
try: url = req['link']
except:
error = req['error']
if error == 'File exceeds max duration': error += ' (60 seconds)'
return {"error": error}, 400
if url.endswith('.'): url += 'mp4'
try: url = req['files'][0]['url']
except: return {"error": req['description']}, 400
body += f"\n\n{url}"
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), "")
c = Comment(author_id=AUTOPOLLER_ID,
@ -487,7 +491,6 @@ def edit_post(pid, v):
)
g.db.add(c)
if not p.choices:
for i in choice_regex.finditer(body):
body = body.replace(i.group(0), "")
c = Comment(author_id=AUTOCHOICE_ID,
@ -702,7 +705,7 @@ def thumbnail_thread(pid):
for chunk in image_req.iter_content(1024):
file.write(chunk)
post.thumburl = process_image(name, resize=100)
post.thumburl = process_image(0, name, resize=100)
db.add(post)
db.commit()
@ -817,7 +820,7 @@ def api_is_repost():
if "/i.imgur.com/" in url: url = url.replace(".png", ".webp").replace(".jpg", ".webp").replace(".jpeg", ".webp")
elif "/media.giphy.com/" in url or "/c.tenor.com/" in url: url = url.replace(".gif", ".webp")
elif "/i.ibb.com/" in url: url = url.replace(".png", ".webp").replace(".jpg", ".webp").replace(".jpeg", ".webp").replace(".gif", ".webp")
elif "/i.ibb.co/" in url: url = url.replace(".png", ".webp").replace(".jpg", ".webp").replace(".jpeg", ".webp").replace(".gif", ".webp")
if url.startswith("https://streamable.com/") and not url.startswith("https://streamable.com/e/"): url = url.replace("https://streamable.com/", "https://streamable.com/e/")
@ -913,7 +916,7 @@ def submit_post(v, sub=None):
if "/i.imgur.com/" in url: url = url.replace(".png", ".webp").replace(".jpg", ".webp").replace(".jpeg", ".webp")
elif "/media.giphy.com/" in url or "/c.tenor.com/" in url: url = url.replace(".gif", ".webp")
elif "/i.ibb.com/" in url: url = url.replace(".png", ".webp").replace(".jpg", ".webp").replace(".jpeg", ".webp").replace(".gif", ".webp")
elif "/i.ibb.co/" in url: url = url.replace(".png", ".webp").replace(".jpg", ".webp").replace(".jpeg", ".webp").replace(".gif", ".webp")
if url.startswith("https://streamable.com/") and not url.startswith("https://streamable.com/e/"): url = url.replace("https://streamable.com/", "https://streamable.com/e/")
@ -1076,18 +1079,18 @@ def submit_post(v, sub=None):
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
body += f"\n\n![]({process_image(name)})"
body += f"\n\n![]({process_image(v.patron, name)})"
elif file.content_type.startswith('video/'):
if file.content_type == 'video/webm':
file.save("video.mp4")
else:
file.save("unsanitized.mp4")
os.system(f'ffmpeg -y -loglevel warning -i unsanitized.mp4 -map_metadata -1 -c:v copy -c:a copy video.mp4')
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
except requests.Timeout: return error("Video upload timed out, please try again!")
try: url = req['link']
except:
err = req['error']
if err == 'File exceeds max duration': err += ' (60 seconds)'
return error(err)
if url.endswith('.'): url += 'mp4'
try: req = requests.request("POST", "https://pomf2.lain.la/upload.php", files={'files[]': f}, timeout=5).json()
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
try: url = req['files'][0]['url']
except: return {"error": req['description']}, 400
body += f"\n\n{url}"
else:
return error("Image/Video files only.")
@ -1181,22 +1184,22 @@ def submit_post(v, sub=None):
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
post.url = process_image(name)
post.url = process_image(v.patron, name)
name2 = name.replace('.webp', 'r.webp')
copyfile(name, name2)
post.thumburl = process_image(name2, resize=100)
post.thumburl = process_image(v.patron, name2, resize=100)
elif file.content_type.startswith('video/'):
if file.content_type == 'video/webm':
file.save("video.mp4")
else:
file.save("unsanitized.mp4")
os.system(f'ffmpeg -y -loglevel warning -i unsanitized.mp4 -map_metadata -1 -c:v copy -c:a copy video.mp4')
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
except requests.Timeout: return error("Video upload timed out, please try again!")
try: url = req['link']
except:
err = req['error']
if err == 'File exceeds max duration': err += ' (60 seconds)'
return error(err)
if url.endswith('.'): url += 'mp4'
try: req = requests.request("POST", "https://pomf2.lain.la/upload.php", files={'files[]': f}, timeout=5).json()
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
try: url = req['files'][0]['url']
except: return {"error": req['description']}, 400
post.url = url
else:
return error("Image/Video files only.")
@ -1284,6 +1287,9 @@ def submit_post(v, sub=None):
if body.startswith('OP is a Trump supporter'):
flag = Flag(post_id=post.id, user_id=SNAPPY_ID, reason='Trump supporter')
g.db.add(flag)
elif body.startswith('You had your chance. Downvoted and reported'):
flag = Flag(post_id=post.id, user_id=SNAPPY_ID, reason='Retard')
g.db.add(flag)
elif body.startswith(''):
body = body[1:]
vote = Vote(user_id=SNAPPY_ID,
@ -1303,7 +1309,7 @@ def submit_post(v, sub=None):
rev = f"* [unddit.com](https://unddit.com/{rev})\n"
elif post.url.startswith("https://old.reddit.com/u/"):
rev = post.url.replace('https://old.reddit.com/u/', '')
rev = f"* [camas.github.io](https://camas.github.io/reddit-search/#\u007b\"author\":\"{rev}\",\"resultSize\":100\u007d)\n"
rev = f"* [search.marsey.cat](https://search.marsey.cat/reddit-search/#\u007b\"author\":\"{rev}\",\"resultSize\":100\u007d)\n"
else: rev = ''
newposturl = post.url
@ -1325,15 +1331,19 @@ def submit_post(v, sub=None):
if f'**[{title}]({href})**:\n\n' not in body:
body += f'**[{title}]({href})**:\n\n'
if href.startswith('https://old.reddit.com/r/'):
body += f'* [unddit.com](https://unddit.com/{href.replace("https://old.reddit.com/", "")})\n'
rev = href.replace('https://old.reddit.com/', '')
body += f'* [unddit.com](https://unddit.com/{rev})\n'
if href.startswith('https://old.reddit.com/u/'):
rev = post.url.replace('https://old.reddit.com/u/', '')
body += f"* [camas.github.io](https://camas.github.io/reddit-search/#\u007b\"author\":\"{rev}\",\"resultSize\":100\u007d)\n"
rev = href.replace('https://old.reddit.com/u/', '')
body += f"* [search.marsey.cat](https://search.marsey.cat/reddit-search/#\u007b\"author\":\"{rev}\",\"resultSize\":100\u007d)\n"
body += f'* [archive.org](https://web.archive.org/{href})\n'
body += f'* [archive.ph](https://archive.ph/?url={quote(href)}&run=1) (click to archive)\n'
body += f'* [ghostarchive.org](https://ghostarchive.org/search?term={quote(href)}) (click to archive)\n\n'
gevent.spawn(archiveorg, href)
if body == '!slots':
body = f'!slots{snappy.coins}'
body_html = sanitize(body)
if len(body_html) < 40000:
@ -1354,9 +1364,13 @@ def submit_post(v, sub=None):
snappy.coins += 1
g.db.add(snappy)
if body.startswith('!slots1000'):
if body.startswith('!slots'):
check_for_slots_command(body, snappy, c)
if body.startswith(':#marseypin'):
post.stickied = "Snappy"
post.stickied_utc = int(time.time()) + 3600
g.db.flush()
c.top_comment_id = c.id
@ -1364,7 +1378,7 @@ def submit_post(v, sub=None):
post.comment_count += 1
post.replies = [c]
v.post_count = g.db.query(Submission.id).filter_by(author_id=v.id, is_banned=False, deleted_utc=0).count()
v.post_count = g.db.query(Submission).filter_by(author_id=v.id, is_banned=False, deleted_utc=0).count()
g.db.add(v)
if v.id == PIZZASHILL_ID:
@ -1382,9 +1396,7 @@ def submit_post(v, sub=None):
cache.delete_memoized(frontlist)
cache.delete_memoized(User.userpagelisting)
if SITE == 'cringetopia.org':
send_cringetopia_message(post.permalink)
elif v.admin_level > 0 and ("[changelog]" in post.title.lower() or "(changelog)" in post.title.lower()) and not post.private:
if v.admin_level > 0 and ("[changelog]" in post.title.lower() or "(changelog)" in post.title.lower()) and not post.private:
send_discord_message(post.permalink)
cache.delete_memoized(changeloglist)

View File

@ -39,7 +39,7 @@ def api_flag_post(pid, v):
_note=f'"{post.flair}"'
)
g.db.add(ma)
elif reason.startswith('/h/') and v.admin_level > 2:
elif reason.startswith('/h/') and v.admin_level > 1:
post.sub = reason[3:]
g.db.add(post)
ma=ModAction(

View File

@ -12,6 +12,7 @@ from files.helpers.sanitize import filter_emojis_only
from files.helpers.discord import add_role
from shutil import copyfile
import requests
import tldextract
GUMROAD_TOKEN = environ.get("GUMROAD_TOKEN", "").strip()
GUMROAD_ID = environ.get("GUMROAD_ID", "tfcvri").strip()
@ -213,19 +214,19 @@ def settings_profile_post(v):
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
url = process_image(name)
url = process_image(v.patron, name)
bio += f"\n\n![]({url})"
elif file.content_type.startswith('video/'):
if file.content_type == 'video/webm':
file.save("video.mp4")
else:
file.save("unsanitized.mp4")
os.system(f'ffmpeg -y -loglevel warning -i unsanitized.mp4 -map_metadata -1 -c:v copy -c:a copy video.mp4')
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
try: req = requests.request("POST", "https://pomf2.lain.la/upload.php", files={'files[]': f}, timeout=5).json()
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
try: url = req['link']
except:
error = req['error']
if error == 'File exceeds max duration': error += ' (60 seconds)'
return {"error": error}, 400
if url.endswith('.'): url += 'mp4'
try: url = req['files'][0]['url']
except: return {"error": req['description']}, 400
bio += f"\n\n{url}"
else:
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": "Image/Video files only"}, 400
@ -397,8 +398,6 @@ def gumroad(v):
v.procoins += procoins
send_repeatable_notification(v.id, f"You have received {procoins} Marseybux! You can use them to buy awards in the [shop](/shop).")
if v.patron > 1 and v.verified == None: v.verified = "Verified"
g.db.add(v)
if not v.has_badge(20+tier):
@ -555,13 +554,13 @@ def settings_images_profile(v):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
highres = process_image(name)
highres = process_image(v.patron, name)
if not highres: abort(400)
name2 = name.replace('.webp', 'r.webp')
copyfile(name, name2)
imageurl = process_image(name2, resize=100)
imageurl = process_image(v.patron, name2, resize=100)
if not imageurl: abort(400)
@ -591,7 +590,7 @@ def settings_images_banner(v):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
bannerurl = process_image(name)
bannerurl = process_image(v.patron, name)
if bannerurl:
if v.bannerurl and '/images/' in v.bannerurl:
@ -641,6 +640,18 @@ def settings_profilecss_get(v):
@auth_required
def settings_profilecss(v):
profilecss = request.values.get("profilecss").strip().replace('\\', '').strip()[:4000]
urls = list(css_regex.finditer(profilecss)) + list(css_regex2.finditer(profilecss))
for i in urls:
url = i.group(1)
if url.startswith('/'): continue
domain = tldextract.extract(url).registered_domain
if domain not in approved_embed_hosts:
error = f"The domain '{domain}' is not allowed, please use one of these domains\n\n{approved_embed_hosts}."
return render_template("settings_profilecss.html", error=error, v=v)
v.profilecss = profilecss
g.db.add(v)
g.db.commit()
@ -781,14 +792,14 @@ def settings_name_change(v):
return redirect("/settings/profile")
@app.post("/settings/song_change")
@limiter.limit("2/second;10/day")
@limiter.limit("2/second;10/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@limiter.limit("3/second;10/day")
@limiter.limit("3/second;10/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def settings_song_change(v):
song=request.values.get("song").strip()
if song == "" and v.song:
if path.isfile(f"/songs/{v.song}.mp3") and g.db.query(User.id).filter_by(song=v.song).count() == 1:
if path.isfile(f"/songs/{v.song}.mp3") and g.db.query(User).filter_by(song=v.song).count() == 1:
os.remove(f"/songs/{v.song}.mp3")
v.song = None
g.db.add(v)
@ -827,7 +838,7 @@ def settings_song_change(v):
return render_template("settings_profile.html", v=v, error="Duration of the video must not exceed 15 minutes.")
if v.song and path.isfile(f"/songs/{v.song}.mp3") and g.db.query(User.id).filter_by(song=v.song).count() == 1:
if v.song and path.isfile(f"/songs/{v.song}.mp3") and g.db.query(User).filter_by(song=v.song).count() == 1:
os.remove(f"/songs/{v.song}.mp3")
ydl_opts = {
@ -870,7 +881,7 @@ def settings_title_change(v):
new_name=request.values.get("title").strip()[:100].replace("𒐪","")
if new_name==v.customtitle: return render_template("settings_profile.html", v=v, error="You didn't change anything")
if new_name == v.customtitle: return render_template("settings_profile.html", v=v, error="You didn't change anything")
v.customtitleplain = new_name
@ -883,6 +894,27 @@ def settings_title_change(v):
return redirect("/settings/profile")
@app.post("/settings/checkmark_text")
@limiter.limit("1/second;30/minute;200/hour;1000/day")
@limiter.limit("1/second;30/minute;200/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@auth_required
def settings_checkmark_text(v):
if not v.verified: abort(403)
new_name=request.values.get("title").strip()[:100].replace("𒐪","")
if not new_name: abort(400)
if new_name == v.verified: return render_template("settings_profile.html", v=v, error="You didn't change anything")
v.verified = new_name
g.db.add(v)
g.db.commit()
return redirect("/settings/profile")
@app.get("/settings")
@auth_required
def settings(v):
@ -892,6 +924,4 @@ def settings(v):
@app.get("/settings/profile")
@auth_required
def settings_profile(v):
if v.flairchanged: ti = datetime.utcfromtimestamp(v.flairchanged).strftime('%Y-%m-%d %H:%M:%S')
else: ti = ''
return render_template("settings_profile.html", v=v, ti=ti)
return render_template("settings_profile.html", v=v)

View File

@ -4,7 +4,7 @@ from files.helpers.alerts import *
from files.helpers.const import *
from files.classes.award import AWARDS
from sqlalchemy import func
from os import path
import os
import calendar
import matplotlib.pyplot as plt
from files.classes.mod_logs import ACTIONTYPES, ACTIONTYPES2
@ -16,24 +16,6 @@ def rdrama(id, title):
id = ''.join(f'{x}/' for x in id)
return redirect(f'/archives/drama/comments/{id}{title}.html')
@app.get('/logged_out/')
@app.get('/logged_out/<path:old>')
def logged_out(old = ""):
# Remove trailing question mark from request.full_path which flask adds if there are no query parameters
redirect_url = request.full_path.replace("/logged_out", "", 1)
if redirect_url.endswith("?"):
redirect_url = redirect_url[:-1]
# Handle cases like /logged_out?asdf by adding a slash to the beginning
if not redirect_url.startswith('/'):
redirect_url = f"/{redirect_url}"
# Prevent redirect loop caused by visiting /logged_out/logged_out/logged_out/etc...
if redirect_url.startswith('/logged_out'):
abort(400)
return redirect(redirect_url)
@app.get("/marseys")
@auth_required
@ -47,29 +29,53 @@ def marseys(v):
marseys = g.db.query(Marsey).order_by(Marsey.count.desc())
return render_template("marseys.html", v=v, marseys=marseys)
@app.get("/marsey_list")
@cache.memoize(timeout=600, make_name=make_name)
@app.get("/marsey_list.json")
@cache.memoize(timeout=600)
def marsey_list():
if SITE_NAME == 'rDrama':
marseys = [f"{x.name} : {y} {x.tags}" for x, y in g.db.query(Marsey, User.username).join(User, User.id==Marsey.author_id).order_by(Marsey.count.desc())]
else:
marseys = [f"{x.name} : {x.tags}" for x in g.db.query(Marsey).order_by(Marsey.count.desc())]
# From database
emojis = [{
"name": emoji.name,
"author": author if SITE_NAME == 'rDrama' or author == "anton-d" else None,
# yikes, I don't really like this DB schema. Next time be better
"tags": emoji.tags.split(" ") + [emoji.name[len("marsey"):] if emoji.name.startswith("marsey") else emoji.name],
"count": emoji.count,
"class": "Marsey"
} for emoji, author in g.db.query(Marsey, User.username).join(User, User.id==Marsey.author_id).order_by(Marsey.count.desc())]
return str(marseys).replace("'",'"')
# Stastic shit
shit = open("files/assets/shit emojis.json", "r", encoding="utf-8")
emojis = emojis + json.load(shit)
shit.close()
if SITE_NAME == 'Cringetopia':
shit = open("files/assets/shit emojis.cringetopia.json", "r", encoding="utf-8")
emojis = emojis + json.load(shit)
shit.close()
# return str(marseys).replace("'",'"')
return jsonify(emojis)
@app.get('/rules')
@app.get('/sidebar')
@app.get('/logged_out/rules')
@app.get('/logged_out/sidebar')
@auth_desired
def sidebar(v):
if not v and not request.path.startswith('/logged_out'): return redirect(f"/logged_out{request.full_path}")
if v and request.path.startswith('/logged_out'): return redirect(request.full_path.replace('/logged_out',''))
return render_template('sidebar.html', v=v)
@app.get("/stats")
@auth_required
@cache.memoize(timeout=86400, make_name=make_name)
def participation_stats(v):
return render_template("admin/content_stats.html", v=v, title="Content Statistics", data=stats(site=SITE))
@cache.memoize(timeout=86400)
def stats(site=None):
day = int(time.time()) - 86400
week = int(time.time()) - 604800
@ -80,42 +86,42 @@ def participation_stats(v):
active_users = set(posters) | set(commenters) | set(voters) | set(commentvoters)
stats = {"marseys": g.db.query(Marsey.name).count(),
"users": g.db.query(User.id).count(),
"private users": g.db.query(User.id).filter_by(is_private=True).count(),
"banned users": g.db.query(User.id).filter(User.is_banned > 0).count(),
"verified email users": g.db.query(User.id).filter_by(is_activated=True).count(),
stats = {"marseys": g.db.query(Marsey).count(),
"users": g.db.query(User).count(),
"private users": g.db.query(User).filter_by(is_private=True).count(),
"banned users": g.db.query(User).filter(User.is_banned > 0).count(),
"verified email users": g.db.query(User).filter_by(is_activated=True).count(),
"coins in circulation": g.db.query(func.sum(User.coins)).scalar(),
"total shop sales": g.db.query(func.sum(User.coins_spent)).scalar(),
"signups last 24h": g.db.query(User.id).filter(User.created_utc > day).count(),
"total posts": g.db.query(Submission.id).count(),
"signups last 24h": g.db.query(User).filter(User.created_utc > day).count(),
"total posts": g.db.query(Submission).count(),
"posting users": g.db.query(Submission.author_id).distinct().count(),
"listed posts": g.db.query(Submission.id).filter_by(is_banned=False).filter(Submission.deleted_utc == 0).count(),
"removed posts (by admins)": g.db.query(Submission.id).filter_by(is_banned=True).count(),
"deleted posts (by author)": g.db.query(Submission.id).filter(Submission.deleted_utc > 0).count(),
"posts last 24h": g.db.query(Submission.id).filter(Submission.created_utc > day).count(),
"total comments": g.db.query(Comment.id).filter(Comment.author_id.notin_((AUTOJANNY_ID,NOTIFICATIONS_ID))).count(),
"listed posts": g.db.query(Submission).filter_by(is_banned=False).filter(Submission.deleted_utc == 0).count(),
"removed posts (by admins)": g.db.query(Submission).filter_by(is_banned=True).count(),
"deleted posts (by author)": g.db.query(Submission).filter(Submission.deleted_utc > 0).count(),
"posts last 24h": g.db.query(Submission).filter(Submission.created_utc > day).count(),
"total comments": g.db.query(Comment).filter(Comment.author_id.notin_((AUTOJANNY_ID,NOTIFICATIONS_ID))).count(),
"commenting users": g.db.query(Comment.author_id).distinct().count(),
"removed comments (by admins)": g.db.query(Comment.id).filter_by(is_banned=True).count(),
"deleted comments (by author)": g.db.query(Comment.id).filter(Comment.deleted_utc > 0).count(),
"comments last_24h": g.db.query(Comment.id).filter(Comment.created_utc > day, Comment.author_id.notin_((AUTOJANNY_ID,NOTIFICATIONS_ID))).count(),
"post votes": g.db.query(Vote.submission_id).count(),
"removed comments (by admins)": g.db.query(Comment).filter_by(is_banned=True).count(),
"deleted comments (by author)": g.db.query(Comment).filter(Comment.deleted_utc > 0).count(),
"comments last_24h": g.db.query(Comment).filter(Comment.created_utc > day, Comment.author_id.notin_((AUTOJANNY_ID,NOTIFICATIONS_ID))).count(),
"post votes": g.db.query(Vote).count(),
"post voting users": g.db.query(Vote.user_id).distinct().count(),
"comment votes": g.db.query(CommentVote.comment_id).count(),
"comment votes": g.db.query(CommentVote).count(),
"comment voting users": g.db.query(CommentVote.user_id).distinct().count(),
"total upvotes": g.db.query(Vote.submission_id).filter_by(vote_type=1).count() + g.db.query(CommentVote.comment_id).filter_by(vote_type=1).count(),
"total downvotes": g.db.query(Vote.submission_id).filter_by(vote_type=-1).count() + g.db.query(CommentVote.comment_id).filter_by(vote_type=-1).count(),
"total awards": g.db.query(AwardRelationship.id).count(),
"awards given": g.db.query(AwardRelationship.id).filter(or_(AwardRelationship.submission_id != None, AwardRelationship.comment_id != None)).count(),
"total upvotes": g.db.query(Vote).filter_by(vote_type=1).count() + g.db.query(CommentVote.comment_id).filter_by(vote_type=1).count(),
"total downvotes": g.db.query(Vote).filter_by(vote_type=-1).count() + g.db.query(CommentVote.comment_id).filter_by(vote_type=-1).count(),
"total awards": g.db.query(AwardRelationship).count(),
"awards given": g.db.query(AwardRelationship).filter(or_(AwardRelationship.submission_id != None, AwardRelationship.comment_id != None)).count(),
"users who posted, commented, or voted in the past 7 days": len(active_users),
}
if SITE_NAME == 'rDrama':
furries1 = g.db.query(User.id).filter(User.house.like('Furry%')).count()
femboys1 = g.db.query(User.id).filter(User.house.like('Femboy%')).count()
vampires1 = g.db.query(User.id).filter(User.house.like('Vampire%')).count()
racists1 = g.db.query(User.id).filter(User.house.like('Racist%')).count()
furries1 = g.db.query(User).filter(User.house.like('Furry%')).count()
femboys1 = g.db.query(User).filter(User.house.like('Femboy%')).count()
vampires1 = g.db.query(User).filter(User.house.like('Vampire%')).count()
racists1 = g.db.query(User).filter(User.house.like('Racist%')).count()
furries2 = g.db.query(func.sum(User.truecoins)).filter(User.house.like('Furry%')).scalar()
femboys2 = g.db.query(func.sum(User.truecoins)).filter(User.house.like('Femboy%')).scalar()
@ -200,7 +206,7 @@ def participation_stats(v):
g.db.commit()
return render_template("admin/content_stats.html", v=v, title="Content Statistics", data=stats)
return stats
@app.get("/chart")
@ -238,18 +244,18 @@ def cached_chart(kind, site):
)
today_cutoff = calendar.timegm(midnight_this_morning)
if kind == "daily": day_cutoffs = [today_cutoff - 86400 * i for i in range(47)][1:]
else: day_cutoffs = [today_cutoff - 86400 * 7 * i for i in range(47)][1:]
if kind == "daily": day_cutoffs = [today_cutoff - 86400 * i for i in range(55)][1:]
else: day_cutoffs = [today_cutoff - 86400 * 7 * i for i in range(55)][1:]
day_cutoffs.insert(0, calendar.timegm(now))
daily_times = [time.strftime("%d/%m", time.gmtime(day_cutoffs[i + 1])) for i in range(len(day_cutoffs) - 1)][::-1]
daily_signups = [g.db.query(User.id).filter(User.created_utc < day_cutoffs[i], User.created_utc > day_cutoffs[i + 1]).count() for i in range(len(day_cutoffs) - 1)][::-1]
daily_signups = [g.db.query(User).filter(User.created_utc < day_cutoffs[i], User.created_utc > day_cutoffs[i + 1]).count() for i in range(len(day_cutoffs) - 1)][::-1]
post_stats = [g.db.query(Submission.id).filter(Submission.created_utc < day_cutoffs[i], Submission.created_utc > day_cutoffs[i + 1], Submission.is_banned == False).count() for i in range(len(day_cutoffs) - 1)][::-1]
post_stats = [g.db.query(Submission).filter(Submission.created_utc < day_cutoffs[i], Submission.created_utc > day_cutoffs[i + 1], Submission.is_banned == False).count() for i in range(len(day_cutoffs) - 1)][::-1]
comment_stats = [g.db.query(Comment.id).filter(Comment.created_utc < day_cutoffs[i], Comment.created_utc > day_cutoffs[i + 1],Comment.is_banned == False, Comment.author_id.notin_((AUTOJANNY_ID,NOTIFICATIONS_ID))).count() for i in range(len(day_cutoffs) - 1)][::-1]
comment_stats = [g.db.query(Comment).filter(Comment.created_utc < day_cutoffs[i], Comment.created_utc > day_cutoffs[i + 1],Comment.is_banned == False, Comment.author_id.notin_((AUTOJANNY_ID,NOTIFICATIONS_ID))).count() for i in range(len(day_cutoffs) - 1)][::-1]
plt.rcParams["figure.figsize"] = (30, 20)
@ -281,10 +287,6 @@ def cached_chart(kind, site):
comments_chart.set_ylabel("Comments")
comments_chart.set_xlabel("Time (UTC)")
signup_chart.legend(loc='upper left', frameon=True)
posts_chart.legend(loc='upper left', frameon=True)
comments_chart.legend(loc='upper left', frameon=True)
file = f"/{SITE}_{kind}.png"
plt.savefig(file)
@ -399,19 +401,19 @@ def submit_contact(v):
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
url = process_image(name)
url = process_image(v.patron, name)
body_html += f'<img data-bs-target="#expandImageModal" data-bs-toggle="modal" onclick="expandDesktopImage(this.src)" class="img" src="{url}" loading="lazy">'
elif file.content_type.startswith('video/'):
if file.content_type == 'video/webm':
file.save("video.mp4")
else:
file.save("unsanitized.mp4")
os.system(f'ffmpeg -y -loglevel warning -i unsanitized.mp4 -map_metadata -1 -c:v copy -c:a copy video.mp4')
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
try: req = requests.request("POST", "https://pomf2.lain.la/upload.php", files={'files[]': f}, timeout=5).json()
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
try: url = req['link']
except:
error = req['error']
if error == 'File exceeds max duration': error += ' (60 seconds)'
return {"error": error}, 400
if url.endswith('.'): url += 'mp4'
try: url = req['files'][0]['url']
except: return {"error": req['description']}, 400
body_html += f"<p>{url}</p>"
else: return {"error": "Image/Video files only"}, 400
@ -493,18 +495,24 @@ def robots_txt():
abort(404)
return f
@app.get("/badges")
@auth_required
@cache.memoize(timeout=3600, make_name=make_name)
def badges(v):
badges = g.db.query(BadgeDef).order_by(BadgeDef.id).all()
no = (21,22,23,24,25,26,27)
@cache.memoize(timeout=3600)
def badge_list(site):
badges = g.db.query(BadgeDef).filter(BadgeDef.id.notin_(no)).order_by(BadgeDef.id).all()
counts_raw = g.db.query(Badge.badge_id, func.count()).group_by(Badge.badge_id).all()
users = g.db.query(User.id).count()
users = g.db.query(User).count()
counts = {}
for c in counts_raw:
counts[c[0]] = (c[1], float(c[1]) * 100 / max(users, 1))
return badges, counts
@app.get("/badges")
@auth_required
def badges(v):
badges, counts = badge_list(SITE)
return render_template("badges.html", v=v, badges=badges, counts=counts)
@app.get("/blocks")

View File

@ -3,8 +3,7 @@ from files.helpers.alerts import *
from files.helpers.wrappers import *
from files.classes import *
from .front import frontlist
import tldextract
@app.post("/exile/post/<pid>")
@is_not_permabanned
@ -224,7 +223,7 @@ def remove_mod(v, sub):
@app.get("/create_sub")
@is_not_permabanned
def create_sub(v):
if SITE_NAME == 'rDrama' and v.admin_level < 3: abort(403)
if SITE_NAME != 'PCM' and v.admin_level < 3: abort(403)
if request.host == 'rdrama.net': cost = 0
else:
@ -336,9 +335,22 @@ def post_sub_css(v, sub):
if not v.mods(sub.name): abort(403)
sub.css = request.values.get('css', '').strip()
g.db.add(sub)
css = request.values.get('css', '').strip()
urls = list(css_regex.finditer(css)) + list(css_regex2.finditer(css))
for i in urls:
url = i.group(1)
if url.startswith('/'): continue
domain = tldextract.extract(url).registered_domain
if domain not in approved_embed_hosts:
error = f"The domain '{domain}' is not allowed, please use one of these domains\n\n{approved_embed_hosts}."
return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub, error=error)
sub.css = css
g.db.add(sub)
g.db.commit()
return redirect(f'/h/{sub.name}/settings')
@ -369,7 +381,7 @@ def sub_banner(v, sub):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
bannerurl = process_image(name)
bannerurl = process_image(v.patron, name)
if bannerurl:
if sub.bannerurl and '/images/' in sub.bannerurl:
@ -396,7 +408,7 @@ def sub_sidebar(v, sub):
file = request.files["sidebar"]
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
sidebarurl = process_image(name)
sidebarurl = process_image(v.patron, name)
if sidebarurl:
if sub.sidebarurl and '/images/' in sub.sidebarurl:

View File

@ -13,6 +13,7 @@ from pusher_push_notifications import PushNotifications
from collections import Counter
import gevent
from sys import stdout
import os
if PUSHER_ID != 'blahblahblah':
beams_client = PushNotifications(instance_id=PUSHER_ID, secret_key=PUSHER_KEY)
@ -85,7 +86,7 @@ gevent.spawn(leaderboard_thread())
@auth_required
def upvoters_posts(v, username, uid):
u = get_user(username)
if u.is_private and v.id != u.id: abort(403)
if u.is_private and (not v or (v.id != u.id and v.admin_level < 2 and not v.eye)): abort(403)
id = u.id
uid = int(uid)
@ -106,7 +107,7 @@ def upvoters_posts(v, username, uid):
@auth_required
def upvoters_comments(v, username, uid):
u = get_user(username)
if u.is_private and v.id != u.id: abort(403)
if u.is_private and (not v or (v.id != u.id and v.admin_level < 2 and not v.eye)): abort(403)
id = u.id
uid = int(uid)
@ -127,7 +128,7 @@ def upvoters_comments(v, username, uid):
@auth_required
def downvoters_posts(v, username, uid):
u = get_user(username)
if u.is_private and v.id != u.id: abort(403)
if u.is_private and (not v or (v.id != u.id and v.admin_level < 2 and not v.eye)): abort(403)
id = u.id
uid = int(uid)
@ -148,7 +149,7 @@ def downvoters_posts(v, username, uid):
@auth_required
def downvoters_comments(v, username, uid):
u = get_user(username)
if u.is_private and v.id != u.id: abort(403)
if u.is_private and (not v or (v.id != u.id and v.admin_level < 2 and not v.eye)): abort(403)
id = u.id
uid = int(uid)
@ -172,7 +173,7 @@ def downvoters_comments(v, username, uid):
@auth_required
def upvoting_posts(v, username, uid):
u = get_user(username)
if u.is_private and v.id != u.id: abort(403)
if u.is_private and (not v or (v.id != u.id and v.admin_level < 2 and not v.eye)): abort(403)
id = u.id
uid = int(uid)
@ -193,7 +194,7 @@ def upvoting_posts(v, username, uid):
@auth_required
def upvoting_comments(v, username, uid):
u = get_user(username)
if u.is_private and v.id != u.id: abort(403)
if u.is_private and (not v or (v.id != u.id and v.admin_level < 2 and not v.eye)): abort(403)
id = u.id
uid = int(uid)
@ -214,7 +215,7 @@ def upvoting_comments(v, username, uid):
@auth_required
def downvoting_posts(v, username, uid):
u = get_user(username)
if u.is_private and v.id != u.id: abort(403)
if u.is_private and (not v or (v.id != u.id and v.admin_level < 2 and not v.eye)): abort(403)
id = u.id
uid = int(uid)
@ -235,7 +236,7 @@ def downvoting_posts(v, username, uid):
@auth_required
def downvoting_comments(v, username, uid):
u = get_user(username)
if u.is_private and v.id != u.id: abort(403)
if u.is_private and (not v or (v.id != u.id and v.admin_level < 2 and not v.eye)): abort(403)
id = u.id
uid = int(uid)
@ -541,8 +542,9 @@ def leaderboard(v):
@app.get("/@<username>/css")
def get_css(username):
user = get_user(username)
resp=make_response(user.css or "")
resp.headers.add("Content-Type", "text/css")
resp = make_response(user.css or "")
resp.headers["Content-Type"] = "text/css"
resp.headers["Referrer-Policy"] = "no-referrer"
return resp
@app.get("/@<username>/profilecss")
@ -550,8 +552,19 @@ def get_profilecss(username):
user = get_user(username)
if user.profilecss: profilecss = user.profilecss
else: profilecss = ""
resp=make_response(profilecss)
resp.headers.add("Content-Type", "text/css")
resp = make_response(profilecss)
resp.headers["Content-Type"] = "text/css"
resp.headers["Referrer-Policy"] = "no-referrer"
return resp
@app.get("/id/<id>/profilecss")
def get_profilecss_id(id):
user = get_account(id)
if user.profilecss: profilecss = user.profilecss
else: profilecss = ""
resp = make_response(profilecss)
resp.headers["Content-Type"] = "text/css"
resp.headers["Referrer-Policy"] = "no-referrer"
return resp
@app.get("/@<username>/song")
@ -685,19 +698,19 @@ def messagereply(v):
if file.content_type.startswith('image/'):
name = f'/images/{time.time()}'.replace('.','') + '.webp'
file.save(name)
url = process_image(name)
url = process_image(v.patron, name)
body_html += f'<img data-bs-target="#expandImageModal" data-bs-toggle="modal" onclick="expandDesktopImage(this.src)" class="img" src="{url}" loading="lazy">'
elif file.content_type.startswith('video/'):
if file.content_type == 'video/webm':
file.save("video.mp4")
else:
file.save("unsanitized.mp4")
os.system(f'ffmpeg -y -loglevel warning -i unsanitized.mp4 -map_metadata -1 -c:v copy -c:a copy video.mp4')
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
try: url = req['link']
except:
error = req['error']
if error == 'File exceeds max duration': error += ' (60 seconds)'
return {"error": error}, 400
if url.endswith('.'): url += 'mp4'
try: req = requests.request("POST", "https://pomf2.lain.la/upload.php", files={'files[]': f}, timeout=5).json()
except requests.exceptions.ConnectionError: return {"error": "Video upload timed out, please try again!"}
try: url = req['files'][0]['url']
except: return {"error": req['description']}, 400
body_html += f"<p>{url}</p>"
else: return {"error": "Image/Video files only"}, 400
@ -767,7 +780,8 @@ def messagereply(v):
g.db.add(notif)
ids = [c.top_comment.id] + [x.id for x in c.top_comment.replies]
notifications = g.db.query(Notification).filter(Notification.comment_id.in_(ids))
uids = [x.id for x in admins]
notifications = g.db.query(Notification).filter(Notification.comment_id.in_(ids), Notification.user_id.in_(uids))
for n in notifications:
g.db.delete(n)
@ -853,9 +867,12 @@ def visitors(v):
@app.get("/@<username>")
@app.get("/logged_out/@<username>")
@auth_desired
def u_username(username, v=None):
if not v and not request.path.startswith('/logged_out'): return redirect(f"/logged_out{request.full_path}")
if v and request.path.startswith('/logged_out'): return redirect(request.full_path.replace('/logged_out',''))
u = get_user(username, v=v)
@ -939,9 +956,13 @@ def u_username(username, v=None):
@app.get("/@<username>/comments")
@app.get("/logged_out/@<username>/comments")
@auth_desired
def u_username_comments(username, v=None):
if not v and not request.path.startswith('/logged_out'): return redirect(f"/logged_out{request.full_path}")
if v and request.path.startswith('/logged_out'): return redirect(request.full_path.replace('/logged_out',''))
user = get_user(username, v=v)
if username != user.username: return redirect(f'/@{user.username}/comments')
@ -1062,7 +1083,7 @@ def follow_user(username, v):
g.db.add(new_follow)
g.db.flush()
target.stored_subscriber_count = g.db.query(Follow.target_id).filter_by(target_id=target.id).count()
target.stored_subscriber_count = g.db.query(Follow).filter_by(target_id=target.id).count()
g.db.add(target)
send_notification(target.id, f"@{v.username} has followed you!")
@ -1090,7 +1111,7 @@ def unfollow_user(username, v):
g.db.delete(follow)
g.db.flush()
target.stored_subscriber_count = g.db.query(Follow.target_id).filter_by(target_id=target.id).count()
target.stored_subscriber_count = g.db.query(Follow).filter_by(target_id=target.id).count()
g.db.add(target)
send_notification(target.id, f"@{v.username} has unfollowed you!")
@ -1113,7 +1134,7 @@ def remove_follow(username, v):
g.db.delete(follow)
g.db.flush()
v.stored_subscriber_count = g.db.query(Follow.target_id).filter_by(target_id=v.id).count()
v.stored_subscriber_count = g.db.query(Follow).filter_by(target_id=v.id).count()
g.db.add(v)
send_repeatable_notification(target.id, f"@{v.username} has removed your follow!")
@ -1125,9 +1146,15 @@ def remove_follow(username, v):
@app.get("/pp/<id>")
@app.get("/uid/<id>/pic")
@app.get("/uid/<id>/pic/profile")
@app.get("/logged_out/pp/<id>")
@app.get("/logged_out/uid/<id>/pic")
@app.get("/logged_out/uid/<id>/pic/profile")
@limiter.exempt
@auth_desired
def user_profile_uid(v, id):
if not v and not request.path.startswith('/logged_out'): return redirect(f"/logged_out{request.full_path}")
if v and request.path.startswith('/logged_out'): return redirect(request.full_path.replace('/logged_out',''))
try: id = int(id)
except:
try: id = int(id, 36)
@ -1197,11 +1224,11 @@ def saved_comments(v, username):
def fp(v, fp):
v.fp = fp
users = g.db.query(User).filter(User.fp == fp, User.id != v.id).all()
if users: print(f'{v.username}: fp {v.fp}')
if users: print(f'{v.username}: fp')
if v.email and v.is_activated:
alts = g.db.query(User).filter(User.email == v.email, User.is_activated, User.id != v.id).all()
if alts:
print(f'{v.username}: email {v.email}')
print(f'{v.username}: email')
users += alts
for u in users:
li = [v.id, u.id]

View File

@ -49,10 +49,9 @@ def admin_vote_info_get(v):
downs=downs)
@app.post("/vote/post/<post_id>/<new>")
@limiter.limit("5/second;60/minute;600/hour;1000/day")
@limiter.limit("5/second;60/minute;600/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@limiter.limit("5/second;60/minute;1000/hour;2000/day")
@limiter.limit("5/second;60/minute;1000/hour;2000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@is_not_permabanned
def api_vote_post(post_id, new, v):
@ -113,17 +112,17 @@ def api_vote_post(post_id, new, v):
g.db.add(vote)
g.db.flush()
post.upvotes = g.db.query(Vote.submission_id).filter_by(submission_id=post.id, vote_type=1).count()
post.downvotes = g.db.query(Vote.submission_id).filter_by(submission_id=post.id, vote_type=-1).count()
post.realupvotes = g.db.query(Vote.submission_id).filter_by(submission_id=post.id, real=True).count()
post.upvotes = g.db.query(Vote).filter_by(submission_id=post.id, vote_type=1).count()
post.downvotes = g.db.query(Vote).filter_by(submission_id=post.id, vote_type=-1).count()
post.realupvotes = g.db.query(Vote).filter_by(submission_id=post.id, real=True).count()
if post.author.progressivestack: post.realupvotes *= 2
g.db.add(post)
g.db.commit()
return "", 204
@app.post("/vote/comment/<comment_id>/<new>")
@limiter.limit("5/second;60/minute;600/hour;1000/day")
@limiter.limit("5/second;60/minute;600/hour;1000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@limiter.limit("5/second;60/minute;1000/hour;2000/day")
@limiter.limit("5/second;60/minute;1000/hour;2000/day", key_func=lambda:f'{request.host}-{session.get("lo_user")}')
@is_not_permabanned
def api_vote_comment(comment_id, new, v):
@ -190,9 +189,9 @@ def api_vote_comment(comment_id, new, v):
g.db.add(vote)
g.db.flush()
comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=1).count()
comment.downvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=-1).count()
comment.realupvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, real=True).count()
comment.upvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, vote_type=1).count()
comment.downvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, vote_type=-1).count()
comment.realupvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, real=True).count()
if comment.author.progressivestack: comment.realupvotes *= 2
g.db.add(comment)
g.db.commit()
@ -225,7 +224,7 @@ def api_vote_poll(comment_id, v):
g.db.add(vote)
g.db.flush()
comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=1).count()
comment.upvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, vote_type=1).count()
g.db.add(comment)
g.db.commit()
return "", 204
@ -283,12 +282,12 @@ def api_vote_choice(comment_id, v):
else: parent = comment.post
for vote in parent.total_choice_voted(v):
vote.comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=vote.comment.id, vote_type=1).count() - 1
vote.comment.upvotes = g.db.query(CommentVote).filter_by(comment_id=vote.comment.id, vote_type=1).count() - 1
g.db.add(vote.comment)
g.db.delete(vote)
g.db.flush()
comment.upvotes = g.db.query(CommentVote.comment_id).filter_by(comment_id=comment.id, vote_type=1).count()
comment.upvotes = g.db.query(CommentVote).filter_by(comment_id=comment.id, vote_type=1).count()
g.db.add(comment)
g.db.commit()
return "", 204

View File

@ -85,7 +85,12 @@
<label class="custom-control-label" for="under_attack">Under attack mode</label>
</div>
<button class="btn btn-primary mt-3" onclick="post_toast(this,'/admin/purge_cache');">PURGE CACHE</button>
<button class="btn btn-primary mt-3" onclick="post_toast(this,'/admin/purge_cache');" style="margin-bottom: 2em;">PURGE CACHE</button>
{% endif %}
<h4>Server Status</h4>
<div>
Live Revision: <code>{{ gitref }}</code> <br>
</div>
{% endblock %}

View File

@ -15,8 +15,8 @@
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=57">
{% if v.agendaposter %}
<style>
html {
@ -40,8 +40,8 @@
{% endif %}
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=266">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
{% endif %}
</head>

View File

@ -18,13 +18,18 @@
<div class="text-muted"><span id="{{award.kind}}-owned">{{award.owned}}</span> owned</div>
</a>
{% endfor %}
<a class="card disabled d-md-none" style="border:none">
<i class="fas fa-volume-mute" style="opacity:0"></i>
<div class="pt-2" style="font-weight: bold; font-size: 14px; color:#E1E1E1">&nbsp;</div>
<div class="text-muted">&nbsp;</div>
</a>
</div>
<label id="notelabel" for="note" class="pt-4">Note (optional):</label>
<input autocomplete="off" id="kind" name="kind" value="" hidden>
<textarea autocomplete="off" id="note" maxlength="200" name="note" class="form-control" placeholder="Note to include in award notification..."></textarea>
<input autocomplete="off" id="giveaward" class="awardbtn btn btn-primary mt-3" style="float:right" type="submit" value="Give Award" disabled>
<button id="buy1" class="awardbtn btn btn-primary mt-3 mx-3" type="button" disabled style="float:right" onclick="buy(true)">Buy with marseybux</button>
<button id="buy2" class="awardbtn btn btn-primary mt-3" type="button" disabled style="float:right" onclick="buy()">Buy with coins</button>
<button id="buy1" class="awardbtn btn btn-primary mt-3 mx-3 {% if SITE_NAME in ('Cringetopia', 'WPD') %}d-none{% endif %}" type="button" disabled style="float:right" onclick="buy(true)">Buy with marseybux</button>
<button id="buy2" class="awardbtn btn btn-primary mt-3" type="button" disabled style="float:right" onclick="buy()">Buy</button>
<pre>
</pre>
</form>
@ -44,4 +49,4 @@
</div>
</div>
<script src="/assets/js/award_modal.js?v=248" data-cfasync="false"></script>
<script src="/assets/js/award_modal.js?v=249" data-cfasync="false"></script>

View File

@ -20,7 +20,7 @@
<th>Image</th>
<th>Description</th>
<th role="button" onclick="sort_table(4)">#</th>
<th role="button" onclick="sort_table(5)">Rarity</th>
<th role="button" onclick="sort_table(4)">Rarity</th>
</tr>
</thead>
{% for badge in badges %}
@ -30,7 +30,7 @@
<td><img alt="{{badge.name}}" loading="lazy" src="/assets/images/badges/{{badge.id}}.webp?v=1016" width=45.83 height=50>
<td>{{badge.description}}</td>
{%- set ct = counts[badge.id] if badge.id in counts else (0, 0) %}
<td class="badges-rarity-qty">{{ ct[0] }}</td>
<td class="badges-rarity-qty"><a href="/badge_owners/{{badge.id}}">{{ ct[0] }}</a></td>
<td class="badges-rarity-ratio">{{ "{:0.3f}".format(ct[1]) }}%</td>
</tr>
{% endfor %}

View File

@ -14,8 +14,8 @@
<title>Chat</title>
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=57">
{% if v.css %}
<link rel="stylesheet" href="/@{{v.username}}/css">
{% endif %}
@ -188,12 +188,12 @@
<input id="site_name" type="hidden" value="{{SITE_NAME}}">
<input id="slurreplacer" type="hidden" value="{{v.slurreplacer}}">
<script src="/chat.js?v=16"></script>
<script src="/chat.js?v=20"></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>
<script src="/assets/js/lite-youtube.js?v=241"></script>
</body>

View File

@ -207,7 +207,7 @@
{% if not c.author %}
{{c.print()}}
{% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{c.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color:#{{c.author.namecolor}}; font-size:12px; font-weight:bold;"><img loading="lazy" src="{{c.author.profile_url}}" class="profile-pic-25 mr-2"><img class="party-hat" src="/assets/images/party-hat.png"><span {% if c.author.patron and not c.distinguish_level %}class="patron" style="background-color:#{{c.author.namecolor}};"{% elif c.distinguish_level %}class="mod"{% endif %}>{{c.author_name}}</span></a>
<a class="user-name text-decoration-none" onclick='popclick({{c.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color:#{{c.author.namecolor}}; font-size:12px; font-weight:bold;"><img loading="lazy" src="{{c.author.profile_url}}" class="profile-pic-25 mr-2">{% if c.author.is_cakeday or True %}<img class="party-hat" src="/assets/images/party-hat.webp" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Ive spent another year rotting my brain with dramaposting, please ridicule me 🤓">{% endif %}<span {% if c.author.patron and not c.distinguish_level %}class="patron" style="background-color:#{{c.author.namecolor}};"{% elif c.distinguish_level %}class="mod"{% endif %}>{{c.author_name}}</span></a>
{% if c.author.customtitle %}&nbsp;<bdi style="color: #{{c.author.titlecolor}}">&nbsp;{{c.author.customtitle | safe}}</bdi>{% endif %}
{% endif %}
@ -599,9 +599,7 @@
<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>
<div onclick="loadEmojis('reply-form-body-{{c.id}}')" class="btn btn-secondary m-0 mt-3 mr-1" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"><i class="fas fa-smile-beam"></i></div>
{% if c.sentto == 2 %}
<label class="btn btn-secondary m-0 mt-3" for="file-upload">
@ -847,8 +845,8 @@
{% endif %}
{% if v %}
<script src="/assets/js/marked.js?v=251"></script>
<script src="/assets/js/comments_v.js?v=266"></script>
<script src="/assets/js/marked.js?v=253"></script>
<script src="/assets/js/comments_v.js?v=269"></script>
{% endif %}
<script src="/assets/js/clipboard.js?v=250"></script>
@ -860,7 +858,7 @@
{% include "expanded_image_modal.html" %}
<script src="/assets/js/comments+submission_listing.js?v=257"></script>
<script src="/assets/js/comments.js?v=256"></script>
<script src="/assets/js/comments.js?v=257"></script>
<script>
{% if p and (not v or v.highlightcomments) %}

View File

@ -5,10 +5,11 @@
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' 'unsafe-eval' ajax.cloudflare.com; connect-src 'self' tls-use1.fpapi.io api.fpjs.io {% if PUSHER_ID != 'blahblahblah' %}{{PUSHER_ID}}.pushnotifications.pusher.com{% endif %}; object-src 'none';">
<script src="/assets/js/bootstrap.js?v=245"></script>
<script src="/assets/js/shortcut handler.js?v=2"></script>
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=268">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=58">
<link rel="stylesheet" href="/assets/css/awards.css">
{% if v.agendaposter %}
<style>
@ -33,8 +34,8 @@
{% endif %}
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=268">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
<link rel="stylesheet" href="/assets/css/awards.css">
{% endif %}
@ -351,7 +352,7 @@
<script src="/assets/js/formatting.js?v=240"></script>
{% endif %}
<script src="/assets/js/lite-youtube.js?v=240"></script>
<script src="/assets/js/lite-youtube.js?v=241"></script>
</body>

View File

@ -1,49 +1,32 @@
<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">
<style>
#emojiTabs {height: 80%;}
@media (max-height: 650px) {
#emojiTabs {height: 100%;}
#emojiModalInternalDivIDK {margin-top: 0 !important; margin-bottom: 0 !important; padding-top: 0 !important; padding-bottom: 0 !important;}
#emoji-modal-tabs-container {
overflow-x: auto;
}
#emoji-modal-tabs {
white-space: nowrap;
flex-wrap: nowrap;
}
#emoji-modal-tabs li {
display: inline-block;
}
}
</style>
<div id="emojiModalInternalDivIDK" 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">
<div id="emoji-modal-tabs-container">
<ul class="nav nav-pills py-2" id="emoji-modal-tabs">
<li class="nav-item">
<a class="nav-link active emojitab" data-bs-toggle="tab" href="#emoji-tab-favorite">Favorite</a>
</li>
{% if SITE_NAME == 'Cringetopia' %}
<li class="nav-item">
<a class="nav-link emojitab" data-bs-toggle="tab" href="#emoji-tab-cringetopia">Cringetopia</a>
</li>
{% endif %}
<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>
<a class="nav-link active emojitab" data-class-name="favorite" data-bs-toggle="tab" href="#" onclick="switchEmojiTab(event)">⭐ Favorite ⭐</a>
</li>
</ul>
</div>
@ -52,51 +35,65 @@
</button>
</div>
<div class="px-3"><input autocomplete="off" class="form-control px-2" type="text" id="emoji_search" placeholder="Search.."></div>
<div class="px-3">
<input disabled autocomplete="off" class="form-control px-2" type="text" id="emoji_search" placeholder="Search..">
</div>
<div class="px-3 d-flex flex-row">
<fieldset class="p-2">
<label style="display: inline">Options:</legend>
<div style="display: inline" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Makes the emoji larger">
<input type="checkbox" id="emoji-sel-0" value="#" class="emoji-suffix">
<label for="emoji-sel-0">Large</label>
</div>
<div style="display: inline" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Mirror the emoji along the Y axis">
<input type="checkbox" id="emoji-sel-1" value="!" class="emoji-suffix">
<label for="emoji-sel-1">Mirror</label>
</div>
<div style="display: inline" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Adds a hand that pats the emoji">
<input type="checkbox" id="emoji-sel-2" value="pat" class="emoji-postfix">
<label for="emoji-sel-2">Pat</label>
</div>
</fieldset>
<fieldset class="p-2">
<label style="display: inline">Search:</legend>
<div style="display: inline" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Iterate through all substrings of the query. YIKES">
<input type="checkbox" id="emoji-complete-search">
<label for="emoji-complete-search">complete</label>
</div>
</fieldset>
</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 id="no-emojis-found" class="tab-content py-3 pl-2" hidden>
No results... Next time be better with your query. 💅
</div>
{% if SITE_NAME == 'Cringetopia' %}
<div class="tab-pane fade" id="emoji-tab-cringetopia">
<div class="d-flex flex-wrap py-3 pl-2" id="EMOJIS_cringetopia"></div>
<div id="emojis-work" class="tab-content py-3 pl-2">
I am working as hard as I can, sweety... 🚴
</div>
{% endif %}
<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 id="emoji-new-user" class="tab-content py-3 pl-2" hidden>
👋 Hello! This is the first time you're using the emoji system on this device 📱.<br>
I've took the liberty to populate this tab with a selection of Anton's emojis 😽. Next time you'll find the ones you used the most in there 📚
</div>
<div id="tab-content" class="tab-content d-flex flex-wrap py-3 pl-2" hidden>
<style>
.emoji2 {
/*background: None!important;*/
width:60px;
height: 85px;
overflow: hidden;
border: none
}
</style>
<template id="emoji-button-template">
<button class="btn m-1 px-0 emoji2" data-bs-toggle="tooltip" delay:="0">
<img loading="lazy" width=50>
</button>
</template>
</div>
</div>
</div>
@ -104,4 +101,4 @@
</div>
</div>
<script src="/assets/js/emoji_modal.js?v=271"></script>
<script src="/assets/js/emoji_modal.js?v=281"></script>

View File

@ -16,7 +16,7 @@
<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="hidden" name="redir" value="{{request.full_path}}">
<input type="submit" class="btn btn-danger mr-2" value="Yes, I am +18">
</form>
<div><a href="/" class="btn btn-secondary">No</a></div>

View File

@ -79,7 +79,7 @@ Text 2
<tr>
<td>Video Files</td>
<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>
<td><video controls preload="none"><source referrerpolicy="no-referrer" src="https://files.catbox.moe/v4om92.mp4" type="video/mp4"></video></td>
</tr>
<tr>
<td>Emojis</td>
@ -101,6 +101,16 @@ Text 2
<td>:#!marseylove:</td>
<td><img loading="lazy" data-bs-toggle="tooltip" b alt=":!marseylove:" title=":!marseylove:" src="/e/marseylove.webp"></td>
</tr>
<tr>
<td>Pat Emojis</td>
<td>:marseylovepat:</td>
<td><span alt=":marseylovepat:" data-bs-toggle="tooltip" title=":marseylovepat:"><img src="/assets/images/hand.webp"><img alt=":marseylovepat:" b="" loading="lazy" pat="" src="/e/marseylove.webp"></span></td>
</tr>
<tr>
<td>Pat User</td>
<td>:@snappypat:</td>
<td><span alt=":@snappypat:" data-bs-toggle="tooltip" title="" data-bs-original-title=":@snappypat:" aria-label=":@snappypat:"><img src="/assets/images/hand.webp"><img alt=":@snappypat:" b="" loading="lazy" pat="" src="/pp/3"></span></td>
</tr>
<tr>
<td>Random Marsey</td>
<td>:marseyrandom:</td>
@ -111,10 +121,19 @@ Text 2
<td>#fortune</td>
<td>???</td>
</tr>
<tr>
<td>Poll Options (can select multiple options)</td>
<td>$$bussy$$ $$gussy$$</td>
<td>Random Factcheck</td>
<td>#factcheck</td>
<td>???</td>
</tr>
<tr>
<td>
Poll — Pick Multiple<br>
<span style="font-style: italic; font-weight: normal;">
* Polls always appear at end of post.
</span>
</td>
<td>$$bussy$$<br>$$gussy$$</td>
<td>
<div class="custom-control">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="422741">
@ -127,8 +146,13 @@ Text 2
</td>
</tr>
<tr>
<td>Poll Options (can select only 1 option)</td>
<td>&&bussy&& &&gussy&&</td>
<td>
Poll — Pick One<br>
<span style="font-style: italic; font-weight: normal;">
* Polls always appear at end of post.
</span>
</td>
<td>&&bussy&&<br>&&gussy&&</td>
<td>
<div class="custom-control">
<input name="choice" autocomplete="off" type="radio" class="custom-control-input" id="1338113">
@ -569,18 +593,22 @@ line breaks
<td>!slots100</td>
<td>Play slots using coins - minimum 100 coins</td>
</tr>
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
<tr>
<td>!slotsmb100</td>
<td>Play slots using marseybux - minimum 100 marseybux</td>
</tr>
{% endif %}
<tr>
<td>!blackjack100</td>
<td>Play blackjack using coins - minimum 100 coins</td>
</tr>
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
<tr>
<td>!blackjackmb100</td>
<td>Play blackjack using marseybux - minimum 100 marseybux</td>
</tr>
{% endif %}
<tr>
<td>!wordle</td>
<td>Play wordle</td>

View File

@ -157,7 +157,9 @@
<div class="text-left pl-2">
<div style="color: #{{v.namecolor}}" class="text-small font-weight-bold {% if v.patron %}patron{% endif %}"><span {% if v.patron %}class="patron" style="background-color:#{{v.namecolor}}"{% endif %}>{{v.username}}</span></div>
<div class="text-small-extra"><img alt="coins" class="mr-1 ml-1" data-bs-toggle="tooltip" data-bs-placement="bottom" height="13" src="/assets/images/{{SITE_NAME}}/coins.webp?v=2" title="coins" aria-label="coins"><span id="user-coins-amount">{{v.coins}}</span> Coins</div>
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
<div class="text-small-extra"><img alt="marseybux" class="mr-1 ml-1" data-bs-toggle="tooltip" data-bs-placement="bottom" height="13" width="30" src="/assets/images/marseybux.webp?v=1008" title="Marseybux" aria-label="Marseybux"><span id="user-bux-amount">{{v.procoins}}</span> Marseybux</div>
{% endif %}
</div>
</div>
</a>
@ -171,7 +173,7 @@
<button class="dropdown-item copy-link" data-clipboard-text="{{SITE_FULL}}/signup?ref={{v.username}}"><i class="fas fa-user-friends fa-fw mr-3"></i>Invite friends</button>
</div>
<div class="px-2">
<a class="dropdown-item" href="/assets/app_{{config('SITE_NAME')}}.apk?v=2"><i class="fab fa-android fa-fw mr-3"></i>Android app</a>
<a class="dropdown-item" href="/assets/app_{{config('SITE_NAME')}}_v2.3.apk"><i class="fab fa-android fa-fw mr-3"></i>Android app</a>
<a class="dropdown-item" href="https://rdrama.net/changelog"><i class="fas fa-clipboard fa-fw mr-3"></i>Changelog</a>
@ -233,7 +235,7 @@
</li>
{% endif %}
<a class="nav-item nav-link" href="/assets/app_{{config('SITE_NAME')}}.apk?v=2"><i class="fab fa-android fa-fw mr-3"></i>Android app</a>
<a class="nav-item nav-link" href="/assets/app_{{config('SITE_NAME')}}_v2.3.apk"><i class="fab fa-android fa-fw mr-3"></i>Android app</a>
<a class="nav-item nav-link" rel="nofollow noopener noreferrer" href="https://github.com/Aevann1/rDrama"><i class="fab fa-github fa-fw mr-3"></i>Source code</a>

View File

@ -56,6 +56,12 @@
{% block navbar %}
<div class="d-flex align-items-center">
{% if request.path=='/catalog' %}
<a data-bs-toggle="tooltip" data-bs-placement="bottom" title="Catalog View" class="btn btn-primary text-primary mx-2 d-none d-md-block" href="/?sort={{sort}}&t={{t}}&ccmode=false"><i class="fas fa-columns-3 mr-2 "></i>Catalog</a>
{% else %}
<a data-bs-toggle="tooltip" data-bs-placement="bottom" title="Catalog View" class="btn btn-secondary mx-2 d-none d-md-block" href="/catalog?sort={{sort}}&t={{t}}&ccmode=false"><i class="fas fa-columns-3 mr-2 "></i>Catalog</a>
{% endif %}
{% if v and SITE_NAME == 'rDrama' %}
{% if v.paid_dues %}
{% if ccmode=="true"%}

View File

@ -6,8 +6,8 @@
{% block content %}
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=57">
{% if v.agendaposter %}
<style>
html {
@ -31,8 +31,8 @@
{% endif %}
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
{% endif %}
<div class="row justify-content-around">
@ -106,7 +106,9 @@
<span>{{ma.string | safe}}</span>
</div>
<div class="text-gray-500">{{ma.age_string}} <a href="{{ma.permalink}}"><i class="far fa-link ml-1 text-muted"></i></a>
<div class="text-gray-500">
<span class="log--item-age" title="{{ ma.created_string }}">{{ma.age_string}}</span>
<a href="{{ma.permalink}}"><i class="far fa-link ml-1 text-muted"></i></a>
<a role="button" class="copy-link" role="button" data-clipboard-text="{{ma.permalink}}"><i class="far fa-copy ml-1 text-muted"></i></a>
</div>

View File

@ -18,8 +18,8 @@
{% endblock %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
</head>

View File

@ -14,8 +14,8 @@
<title>2-Step Login - {{SITE_NAME}}</title>
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
</head>

View File

@ -20,7 +20,7 @@
<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><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>
<td><a style="color:#{{author.namecolor}};font-weight:bold" href="/@{{author.username}}"><img loading="lazy" src="{{author.profile_url}}" class="pp20"><span {% if author.patron %}class="patron" style="background-color:#{{author.namecolor}}"{% endif %}>{{author.username}}</span></a></td>
</tr>

View File

@ -34,8 +34,8 @@
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=57">
{% if v.agendaposter %}
<style>
html {

View File

@ -39,12 +39,12 @@
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=57">
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
{% endif %}
</head>

View File

@ -449,6 +449,10 @@
<div class="w-lg-100">
{% if v.flairchanged %}
{% set ti = datetime.utcfromtimestamp(v.flairchanged).strftime('%Y-%m-%d %H:%M:%S') %}
{% endif %}
<form id="profile-settings" action="/settings/title_change" method="post">
<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 %}">
@ -510,7 +514,7 @@
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Bluecheck Color</label>
<label class="text-black w-lg-25">Checkmark Color</label>
<div class="d-flex">
@ -544,6 +548,24 @@
</div>
</div>
<div class="body d-lg-flex border-bottom">
<label class="text-black w-lg-25">Checkmark Hover Text</label>
<div class="w-lg-100">
<form action="/settings/checkmark_text" method="post">
<input type="hidden" name="formkey" value="{{v.formkey}}">
<input minlength=1 maxlength=100 autocomplete="off" id="checkmark_text" type="text" name="title" class="form-control" placeholder='Enter text here' value="{{v.verified}}">
<div class="d-flex mt-2">
<small>Limit of 100 characters</small>
<input autocomplete="off" class="btn btn-primary ml-auto" id="titleSave" type="submit" value="Change Text">
</div>
</form>
</div>
</div>
{% endif %}

View File

@ -1,5 +1,9 @@
{% extends "default.html" %}
<button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-html="true" title="<img src='/assets/images/chest.webp' width='20'>">
Tooltip with HTML
</button>
{% block title %}
<title>Shop</title>
{% endblock %}
@ -14,7 +18,9 @@
<h5 class="mt-4">Coins spent by you: {{v.coins_spent}} coins</h5>
<h5 class="mt-4">Lootboxes bought by you: {{v.lootboxes_bought}} lootbox{{'es' if v.lootboxes_bought != 1}}</h5>
<h5 class="mt-4">Your current coins: {{v.coins}}</h5>
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
<h5 class="mt-4">Your current marseybux: {{v.procoins}}</h3>
{% endif %}
</header>
{% endblock %}
@ -71,8 +77,10 @@
<td class="shop-table-owned">{{a['owned']}}</td>
{% set kind = a['kind'] %}
<td class="shop-table-actions">
{% if a['kind'] != "benefactor" %}<a class="d-flex btn btn-success {% if v.coins < a['price'] %}disabled{% endif %}" role="button" onclick="post_toast(this,'/buy/{{kind}}')"><span class="m-auto">Buy with Coins</span></a>{% endif %}
{% if a['kind'] != "benefactor" %}<a class="d-flex btn btn-success {% if v.coins < a['price'] %}disabled{% endif %}" role="button" onclick="post_toast(this,'/buy/{{kind}}')"><span class="m-auto">Buy</span></a>{% endif %}
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
{% if a['kind'] != "grass" %}<a class="d-flex marseybux btn btn-success {% if v.procoins < a['price'] %}disabled{% endif %}" role="button" onclick="post_toast(this,'/buy/{{kind}}?mb=true')"><span class="m-auto">Buy with MBux</span></a>{% endif %}
{% endif %}
</td>
<td class="shop-table-description">{{a['description']}}</td>
</tr>

View File

@ -13,7 +13,6 @@
<div class="mb-4">{{sub.sidebar_html|safe}}</div>
{% endif %}
{% if v %}
<a class="btn btn-primary btn-block mb-3" href="/create_sub">CREATE HOLE</a>
{% if v.mods(sub.name) %}
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub.name}}/settings">HOLE SETTINGS</a>
{% endif %}
@ -22,9 +21,6 @@
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub.name}}/exilees">HOLE EXILEES</a>
<a class="btn btn-primary btn-block mb-3" href="/h/{{sub.name}}/blockers">HOLE BLOCKERS</a>
{% else %}
<a class="btn btn-primary btn-block mb-3" href="/create_sub">CREATE HOLE</a>
<a class="btn btn-primary btn-block mb-3" href="/holes">BROWSE HOLES</a>
<div class="mt-4">
Rules:<br><br>

View File

@ -4,7 +4,7 @@
{% set image=sub.sidebar_url %}
{% else %}
{% set path = "assets/images/" + SITE_NAME + "/sidebar" %}
{% set image = "/" + path + "/" + listdir('files/' + path)|random() + '?v=42' %}
{% set image = "/" + path + "/" + listdir('files/' + path)|random() + '?v=44' %}
{% endif %}
{% if v and (v.is_banned or v.agendaposter) %}

View File

@ -31,8 +31,8 @@
<title>{% if ref_user %}{{ref_user.username}} invites you to {{SITE_NAME}}{% else %}Sign up - {{SITE_NAME}}{% endif %}</title>
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
</head>
@ -111,7 +111,7 @@
required="">
<div class="custom-control custom-checkbox mt-4">
<input autocomplete="off" type="checkbox" class="custom-control-input" id="termsCheck" required>
<label class="custom-control-label terms" for="termsCheck">I accept the <a href="/sidebar">rules</a></label>
<label class="custom-control-label terms" for="termsCheck">I accept the <a href="/logged_out/sidebar">rules</a></label>
</div>
{% if hcaptcha %}

View File

@ -32,8 +32,8 @@
<title>{% if ref_user %}{{ref_user.username}} invites you to {{SITE_NAME}}{% else %}{{SITE_NAME}}{% endif %}</title>
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=56">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=57">
</head>

View File

@ -4,6 +4,18 @@
{% block content %}
{% if error %}
<div class="alert alert-danger alert-dismissible fade show mb-3 mt-4" role="alert">
<i class="fas fa-exclamation-circle my-auto"></i>
<span>
{{error}}
</span>
<button class="close" data-bs-dismiss="alert" aria-label="Close">
<span aria-hidden="true"><i class="far fa-times"></i></span>
</button>
</div>
{% endif %}
{% if msg %}
<div class="alert alert-success alert-dismissible fade show my-3" role="alert">
<i class="fas fa-check-circle my-auto" aria-hidden="true"></i>
@ -111,8 +123,6 @@
</div>
</div>
<div class="row">
<div class="col col-md-8">
<div class="settings">

View File

@ -33,7 +33,7 @@
{% if p.award_count("crab") %}
<script>
let audio = new Audio('/assets/media/crab.mp3');
let audio = new Audio('/assets/crab.mp3');
audio.loop=true;
audio.play();
@ -57,6 +57,12 @@
</script>
{% endif %}
{% if SITE_NAME == 'PCM' %}
{% set wholesome = '/assets/images/wholesome.webp' %}
{% else %}
{% set wholesome = '/e/marseywholesome.webp' %}
{% endif %}
{% if p.award_count("confetti") %}
<script src="/assets/js/confetti-js-master/dist/index.min.js"></script>
<script src = "/assets/js/confetti.js"></script>
@ -143,7 +149,7 @@
}
</style>
<div class="seal seal1" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
<img alt=":#marseywholesome:" class="sealimg" src="{{wholesome}}">
</div>
{% if p.award_count("wholesome") > 1 %}
@ -154,7 +160,7 @@
}
</style>
<div class="seal seal2" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
<img alt=":#marseywholesome:" class="sealimg" src="{{wholesome}}">
</div>
{% endif %}
@ -166,7 +172,7 @@
}
</style>
<div class="seal seal3" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
<img alt=":#marseywholesome:" class="sealimg" src="{{wholesome}}">
</div>
{% endif %}
@ -178,7 +184,7 @@
}
</style>
<div class="seal seal4" height="100%" width="100%">
<img alt=":#marseywholesome:" class="sealimg" src="/e/marseywholesome.webp">
<img alt=":#marseywholesome:" class="sealimg" src="{{wholesome}}">
</div>
{% endif %}
{% endif %}
@ -281,7 +287,7 @@
<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">
<img alt=":#marseywholesome:" class="sealimg" src="{{wholesome}}">
</marquee>
</marquee>
</div>
@ -289,7 +295,7 @@
{% 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">
<img alt=":#marseywholesome:" class="sealimg" src="{{wholesome}}">
</marquee>
</marquee>
{% endif %}
@ -297,7 +303,7 @@
{% 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">
<img alt=":#marseywholesome:" class="sealimg" src="{{wholesome}}">
</marquee>
</marquee>
{% endif %}
@ -305,7 +311,7 @@
{% 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">
<img alt=":#marseywholesome:" class="sealimg" src="{{wholesome}}">
</marquee>
</marquee>
{% endif %}
@ -707,7 +713,7 @@
{% if p.author.verified %}<i class="fas fa-badge-check align-middle ml-1 {% if p.author.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if p.author.verifiedcolor %}#{{p.author.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{p.author.verified}}"></i>
{% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{p.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color: #{{p.author.namecolor}}; font-weight: bold"class="user-name"><img loading="lazy" src="{{p.author.profile_url}}" class="profile-pic-25 mr-2"><img class="party-hat" src="/assets/images/party-hat.png"><span {% if p.author.patron and not p.distinguish_level %}class="patron" style="background-color:#{{p.author.namecolor}};"{% elif p.distinguish_level %}class="mod"{% endif %}>{{p.author_name}}</span></a>{% if p.author.customtitle %}&nbsp;<bdi style="color: #{{p.author.titlecolor}}">&nbsp;{{p.author.customtitle | safe}}</bdi>{% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{p.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color: #{{p.author.namecolor}}; font-weight: bold"class="user-name"><img loading="lazy" src="{{p.author.profile_url}}" class="profile-pic-25 mr-2">{% if p.author.is_cakeday or True %}<img class="party-hat" src="/assets/images/party-hat.webp" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Ive spent another year rotting my brain with dramaposting, please ridicule me 🤓">{% endif %}<span {% if p.author.patron and not p.distinguish_level %}class="patron" style="background-color:#{{p.author.namecolor}};"{% elif p.distinguish_level %}class="mod"{% endif %}>{{p.author_name}}</span></a>{% if p.author.customtitle %}&nbsp;<bdi style="color: #{{p.author.titlecolor}}">&nbsp;{{p.author.customtitle | safe}}</bdi>{% endif %}
{% endif %}
<span data-bs-toggle="tooltip" data-bs-placement="bottom" id="timestamp" onmouseover="timestamp('timestamp','{{p.created_utc}}')">&nbsp;{{p.age_string}}</span>
({% if p.is_image %}image post{% elif p.is_video %}video post{% elif p.domain %}<a href="/search/posts/?q=domain%3A{{p.domain}}&sort=new&t=all" {% if not v or v.newtabexternal %}target="_blank"{% endif %}>{{p.domain}}</a>{% else %}text post{% endif %})
@ -782,7 +788,7 @@
{% elif p.is_video %}
<div class="row no-gutters">
<div class="col">
<video controls preload="none" class="vid">
<video controls preload="none">
<source src="{{p.realurl(v)}}" type="video/mp4">
</video>
</div>
@ -814,7 +820,7 @@
<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>
<div onclick="loadEmojis('post-edit-box-{{p.id}}')" class="format btn btn-secondary" role="button" 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 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>
@ -1012,9 +1018,8 @@
<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>
<div onclick="loadEmojis('reply-form-body-{{p.fullname}}')" class="btn btn-secondary format d-inline-block m-0" id="emoji-reply-btn-{{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>
&nbsp;
<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

@ -74,8 +74,41 @@
{% endwith %}
</div>
{% if offset %}
<script>
function viewmore(pid,sort,offset,ids) {
btn = document.getElementById("viewbtn");
btn.disabled = true;
btn.innerHTML = "Requesting...";
var form = new FormData();
const xhr = new XMLHttpRequest();
xhr.open("get", `/viewmore/${pid}/${sort}/${offset}?ids=${ids}`);
xhr.setRequestHeader('xhr', 'xhr');
xhr.onload=function(){
if (xhr.status==200) {
let e = document.getElementById(`viewmore-${offset}`);
e.innerHTML = xhr.response.replace(/data-src/g, 'src').replace(/data-cfsrc/g, 'src').replace(/style="display:none;visibility:hidden;"/g, '');
bs_trigger(e)
comments = JSON.parse(localStorage.getItem("old-comment-counts")) || {}
lastCount = comments['{{p.id}}']
if (lastCount)
{
{% for c in p.comments %}
{% if not (v and v.id==c.author_id) and not c.voted %}
if ({{c.created_utc*1000}} > lastCount.t)
try {document.getElementById("comment-{{c.id}}-only").classList.add('unread')}
catch(e) {}
{% endif %}
{% endfor %}
}
}
btn.disabled = false;
}
xhr.send(form)
}
</script>
{% endif %}
{% endblock %}

View File

@ -81,17 +81,11 @@
{% if not postembed %}
<div class="voting my-2 d-none d-md-block pr-2">
{% if v and request.path.startswith('/@') and v.admin_level < 2 %}
{% if voted==1 %}
<div class="mx-auto arrow-up post-{{p.id}}-up active"></div>
{% endif %}
<div tabindex="0" role="button" onclick="vote('post', '{{p.id}}', '1', '{{v.id}}')" class="post-{{p.id}}-up mx-auto arrow-up upvote-button post-{{p.id}}-up {% if voted==1 %}active{% else %}d-none{% endif %}"></div>
<span class="post-score-{{p.id}} score post-score-{{p.id}} {% if voted==1 %}score-up{% elif voted==-1%}score-down{% endif %}{% if p.controversial %} controversial{% endif %}"{% if not p.is_banned %} data-bs-toggle="tooltip" data-bs-placement="right" title="+{{ups}} | -{{downs}}"{% endif %}>{{score}}</span>
{% if voted==-1 %}
<div class="text-muted mx-auto arrow-down post-{{p.id}}-down active"></div>
{% endif %}
<div tabindex="0" role="button" onclick="vote('post', '{{p.id}}', '-1', '{{v.id}}')" class="post-{{p.id}}-down text-muted mx-auto arrow-down downvote-button post-{{p.id}}-down {% if voted==-1 %}active{% else %}d-none{% endif %}"></div>
{% elif v %}
<div tabindex="0" role="button" onclick="vote('post', '{{p.id}}', '1', '{{v.id}}')" class="post-{{p.id}}-up mx-auto arrow-up upvote-button post-{{p.id}}-up {% if voted==1 %}active{% endif %}"></div>
@ -192,7 +186,7 @@
{% if p.author.verified %}<i class="fas fa-badge-check align-middle ml-1 {% if p.author.verified=='Glowiefied' %}glow{% endif %}" style="color:{% if p.author.verifiedcolor %}#{{p.author.verifiedcolor}}{% else %}#1DA1F2{% endif %}" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{p.author.verified}}"></i>
{% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{p.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color: #{{p.author.namecolor}}; font-weight: bold;"><img loading="lazy" src="{{p.author.profile_url}}" class="profile-pic-25 mr-2"><img class="party-hat" src="/assets/images/party-hat.png"><span {% if p.author.patron and not p.distinguish_level %}class="patron" style="background-color:#{{p.author.namecolor}};"{% elif p.distinguish_level %}class="mod"{% endif %}>{{p.author_name}}</span></a>{% if p.author.customtitle %}<bdi style="color: #{{p.author.titlecolor}}">&nbsp;&nbsp;{{p.author.customtitle | safe}}</bdi>{% endif %}
<a class="user-name text-decoration-none" onclick='popclick({{p.author.json_popover(v) | tojson}})' data-bs-placement="bottom" data-bs-toggle="popover" data-bs-trigger="click" data-content-id="popover" role="button" tabindex="0" style="color: #{{p.author.namecolor}}; font-weight: bold;"><img loading="lazy" src="{{p.author.profile_url}}" class="profile-pic-25 mr-2">{% if p.author.is_cakeday or True %}<img class="party-hat" src="/assets/images/party-hat.webp" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Ive spent another year rotting my brain with dramaposting, please ridicule me 🤓">{% endif %}<span {% if p.author.patron and not p.distinguish_level %}class="patron" style="background-color:#{{p.author.namecolor}};"{% elif p.distinguish_level %}class="mod"{% endif %}>{{p.author_name}}</span></a>{% if p.author.customtitle %}<bdi style="color: #{{p.author.titlecolor}}">&nbsp;&nbsp;{{p.author.customtitle | safe}}</bdi>{% endif %}
{% endif %}
<span data-bs-toggle="tooltip" data-bs-placement="bottom" onmouseover="timestamp('timestamp-{{p.id}}','{{p.created_utc}}')" id="timestamp-{{p.id}}">&nbsp;{{p.age_string}}</span>
&nbsp;
@ -259,29 +253,20 @@
{% if v and request.path.startswith('/@') and v.admin_level < 2 %}
<li id="voting-{{p.id}}-mobile" class="voting list-inline-item d-md-none">
{% if voted==1 %}
<span class="mr-2 arrow-up post-{{p.id}}-up active">
</span>
{% endif %}
<span tabindex="0" role="button" onclick="vote('post-mobile', '{{p.id}}', '1', '{{v.id}}')" class="post-mobile-{{p.id}}-up mx-0 pr-1 arrow-up upvote-button post-{{p.id}}-up {% if voted==1 %}active{% else %}d-none{% endif %}"></span>
<span class="post-mobile-score-{{p.id}} score post-score-{{p.id}} {% if voted==1 %}score-up{% elif voted==-1%}score-down{% endif %}{% if p.controversial %} controversial{% endif %}"{% if not p.is_banned %} data-bs-toggle="tooltip" data-bs-placement="top" title="+{{ups}} | -{{downs}}"{% endif %}>{{score}}</span>
{% if voted==-1 %}
<span class="ml-2 my-0 arrow-down post-{{p.id}}-down active"></span>
{% endif %}
<span tabindex="0" role="button" onclick="vote('post-mobile', '{{p.id}}', '-1', '{{v.id}}')" class="post-mobile-{{p.id}}-down mx-0 pl-1 my-0 arrow-down downvote-button post-{{p.id}}-down {% if voted==-1 %}active{% else %}d-none{% endif %}"></span>
</li>
{% elif v %}
<li id="voting-{{p.id}}-mobile" class="voting list-inline-item d-md-none">
<span tabindex="0" role="button" onclick="vote('post-mobile', '{{p.id}}', '1', '{{v.id}}')" class="post-mobile-{{p.id}}-up mx-0 pr-1 arrow-up upvote-button post-{{p.id}}-up {% if voted==1 %}active{% endif %}">
</span>
<span tabindex="0" role="button" onclick="vote('post-mobile', '{{p.id}}', '1', '{{v.id}}')" class="post-mobile-{{p.id}}-up mx-0 pr-1 arrow-up upvote-button post-{{p.id}}-up {% if voted==1 %}active{% endif %}"></span>
<span class="post-mobile-score-{{p.id}} score post-score-{{p.id}} {% if voted==1 %}score-up{% elif voted==-1%}score-down{% endif %}{% if p.controversial %} controversial{% endif %}"{% if not p.is_banned %} data-bs-toggle="tooltip" data-bs-placement="top" title="+{{ups}} | -{{downs}}"{% endif %}>{{score}}</span>
<span {% if environ.get('DISABLE_DOWNVOTES') == '1' %}style="display:None!important"{% endif %} tabindex="0" role="button" onclick="vote('post-mobile', '{{p.id}}', '-1', '{{v.id}}')" class="post-mobile-{{p.id}}-down mx-0 pl-1 my-0 arrow-down downvote-button post-{{p.id}}-down {% if voted==-1 %}active{% endif %}">
</span>
<span {% if environ.get('DISABLE_DOWNVOTES') == '1' %}style="display:None!important"{% endif %} tabindex="0" role="button" onclick="vote('post-mobile', '{{p.id}}', '-1', '{{v.id}}')" class="post-mobile-{{p.id}}-down mx-0 pl-1 my-0 arrow-down downvote-button post-{{p.id}}-down {% if voted==-1 %}active{% endif %}"></span>
</li>
{% else %}
@ -355,7 +340,7 @@
</div>
{% elif p.is_video %}
<div id="video-{{p.id}}" style="text-align: center" class="{% if p.over_18 or not ((v and v.cardview) or (not v and environ.get('CARD_VIEW') == '1')) %}d-none{% endif %} mt-4">
<video id="video2-{{p.id}}" controls preload="none" class="vid">
<video id="video2-{{p.id}}" controls preload="none">
<source src="{{p.realurl(v)}}" type="video/mp4">
</video>
</div>

View File

@ -26,7 +26,7 @@
{% block stylesheets %}
{% if v %}
<style>:root{--primary:#{{v.themecolor}}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{v.theme}}.css?v=49">
{% if v.agendaposter %}
<style>
@ -51,7 +51,7 @@
{% endif %}
{% else %}
<style>:root{--primary:#{{config('DEFAULT_COLOR')}}</style>
<link rel="stylesheet" href="/assets/css/main.css?v=251">
<link rel="stylesheet" href="/assets/css/main.css?v=267">
<link rel="stylesheet" href="/assets/css/{{config('DEFAULT_THEME')}}.css?v=49">
{% endif %}
{% endblock %}
@ -96,9 +96,7 @@
<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 onclick="loadEmojis('post-title')" class="btn btn-secondary format d-inline-block m-0" for="emoji-reply-btn-2">
<div id="emoji-reply-btn-2" 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 onclick="loadEmojis('post-title')" class="btn btn-secondary format d-inline-block m-0" id="emoji-reply-btn-2" aria-hidden="true" data-bs-toggle="modal" data-bs-target="#emojiModal" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Add Emoji"><i class="fas fa-smile-beam"></i></div>
<div id="urlblock">
<label for="URL" class="mt-3">URL</label>
@ -146,9 +144,7 @@
&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 onclick="loadEmojis('post-text')" class="btn btn-secondary format d-inline-block m-0" for="emoji-reply-btn">
<div id="emoji-reply-btn" 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 onclick="loadEmojis('post-text')" class="btn btn-secondary format d-inline-block m-0" id="emoji-reply-btn" 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 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>
@ -228,7 +224,7 @@
</script>
{% endif %}
<script src="/assets/js/marked.js?v=251"></script>
<script src="/assets/js/marked.js?v=253"></script>
<script src="/assets/js/formatting.js?v=240"></script>
<script src="/assets/js/submit.js?v=255"></script>
{% include "emoji_modal.html" %}

View File

@ -40,6 +40,7 @@
<div class="d-md-flex text-center text-md-left">
<div>
<a rel="nofollow noopener noreferrer" href="{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}"><img loading="lazy" src="{{u.profile_url}}" class="profile-pic profile-pic-100 mb-5"></a>
{% if u.is_cakeday %}<img class="party-hat2" src="/assets/images/party-hat.webp" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Ive spent another year rotting my brain with dramaposting, please ridicule me 🤓">{% endif %}
</div>
<div id="profilestuff" class="ml-3 w-100">
{% if u.is_suspended %}
@ -114,8 +115,10 @@
<span id="profile-coins-amount">{{u.coins}}</span>
<img alt="coins" class="ml-1 mb-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="coins" height="20" src="/assets/images/{{SITE_NAME}}/coins.webp?v=2">&nbsp;&nbsp;
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
<span id="profile-bux-amount">{{u.procoins}}</span>
<img alt="marseybux" class="ml-1 mb-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Marseybux" height="20" width="46" src="/assets/images/marseybux.webp?v=1008">&nbsp;&nbsp;
{% endif %}
<a href="/@{{u.username}}/followers">{{u.stored_subscriber_count}} follower{{'s' if u.stored_subscriber_count != 1 else ''}}</a>&nbsp;&nbsp;
@ -175,7 +178,9 @@
<a class="btn btn-primary" role="button" onclick="post_toast(this,'/@{{u.username}}/suicide')">Get them help</a>
<a class="btn btn-primary" role="button" onclick="toggleElement('coin-transfer', 'coin-transfer-amount')">Gift coins</a>
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
<a class="btn btn-primary" role="button" onclick="toggleElement('bux-transfer', 'bux-transfer-amount')">Gift Marseybux</a>
{% endif %}
{% if v.admin_level > 2 %}
<a id="admin" class="{% if u.admin_level > 1 %}d-none{% endif %} btn btn-primary" href="javascript:void(0)" onclick="post_toast2(this,'/@{{u.username}}/make_admin','admin','unadmin')">Make admin</a>
@ -289,11 +294,6 @@
<pre></pre>
{% if v.admin_level > 2 %}
<a id="verify" class="{% if u.verified %}d-none{% endif %} btn btn-success" role="button" onclick="post_toast2(this,'/admin/verify/{{u.id}}','verify','unverify')">Verify</a>
<a id="unverify" class="{% if not u.verified %}d-none{% endif %} btn btn-success" role="button" onclick="post_toast2(this,'/admin/unverify/{{u.id}}','verify','unverify')">Unverify</a>
{% endif %}
<pre></pre>
<form action="/admin/unnuke_user" method="post">
<input type="hidden" name="formkey", value="{{v.formkey}}">
@ -365,6 +365,7 @@
<div class="col">
<div style="margin-top: -34px;">
<a rel="nofollow noopener noreferrer" href="{% if u.highres %}{{u.highres}}{% else %}{{u.profile_url}}{% endif %}"><img loading="lazy" src="{{u.profile_url}}" class="profile-pic-65 bg-white border-2 border-white mb-2"></a>
{% if u.is_cakeday %}<img class="party-hat3" src="/assets/images/party-hat.webp" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Ive spent another year rotting my brain with dramaposting, please ridicule me 🤓">{% endif %}
</div>
<div class="mt-n3 py-3">
{% if u.is_suspended %}
@ -411,8 +412,10 @@
<span id="profile-coins-amount-mobile" class="font-weight-bold">{{u.coins}}</span>
<img alt="coins" class="ml-1 mb-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="coins" height="15" src="/assets/images/{{SITE_NAME}}/coins.webp?v=2">&nbsp;&nbsp;
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
<span id="profile-bux-amount-mobile" class="font-weight-bold">{{u.procoins}}</span>
<img alt="marseybux" class="ml-1 mb-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Marseybux" height="15" width="35" src="/assets/images/marseybux.webp?v=1008">&nbsp;&nbsp;
{% endif %}
<a href="/@{{u.username}}/followers" class="font-weight-bold">{{u.stored_subscriber_count}} follower{{'s' if u.stored_subscriber_count != 1 else ''}}</a>&nbsp;&nbsp;
@ -486,7 +489,9 @@
<a class="btn btn-primary" role="button" onclick="toggleElement('message-mobile', 'input-message-mobile')">Message</a>
<a class="btn btn-primary" role="button" onclick="post_toast(this,'/@{{u.username}}/suicide')">Get them help</a>
<a class="btn btn-primary" role="button" onclick="toggleElement('coin-transfer-mobile', 'coin-transfer-amount-mobile')">Gift coins</a>
{% if SITE_NAME not in ('Cringetopia', 'WPD') %}
<a class="btn btn-primary" role="button" onclick="toggleElement('bux-transfer-mobile', 'bux-transfer-amount-mobile')">Gift Marseybux</a>
{% endif %}
{% if v.admin_level > 2 %}
<a id="admin2" class="{% if u.admin_level > 1 %}d-none{% endif %} btn btn-primary" href="javascript:void(0)" onclick="post_toast2(this,'/@{{u.username}}/make_admin','admin2','unadmin2')">Make admin</a>
@ -598,11 +603,6 @@
<pre></pre>
{% if v.admin_level > 2 %}
<a id="verify2" class="{% if u.verified %}d-none{% endif %} btn btn-success" role="button" onclick="post_toast2(this,'/admin/verify/{{u.id}}','verify2','unverify2')">Verify</a>
<a id="unverify2" class="{% if not u.verified %}d-none{% endif %} btn btn-success" role="button" onclick="post_toast2(this,'/admin/unverify/{{u.id}}','verify2','unverify2')">Unverify</a>
{% endif %}
<pre></pre>
<form action="/admin/unnuke_user" method="post">
<input type="hidden" name="formkey", value="{{v.formkey}}">
@ -769,7 +769,7 @@
</nav>
{% endif %}
<script src="/assets/js/marked.js?v=251"></script>
<script src="/assets/js/marked.js?v=253"></script>
{% if v and v.id != u.id and '/comments' not in request.path %}

View File

@ -0,0 +1,37 @@
from bs4 import BeautifulSoup
from time import time, sleep
from files.__main__ import app
# these tests require `docker-compose up` first
def test_rules():
response = app.test_client().get("/logged_out/rules")
assert response.status_code == 200
assert response.text.startswith("<!DOCTYPE html>")
def test_signup():
client = app.test_client()
with client: # this keeps the session between requests, which we need
signup_get_response = client.get("/signup")
assert signup_get_response.status_code == 200
soup = BeautifulSoup(signup_get_response.text, 'html.parser')
# these hidden input values seem to be used for anti-bot purposes and need to be submitted
formkey = next(tag for tag in soup.find_all("input") if tag.get("name") == "formkey").get("value")
form_timestamp = next(tag for tag in soup.find_all("input") if tag.get("name") == "now").get("value")
sleep(5) # too-fast submissions are rejected (bot check?)
username = "testuser" + str(round(time()))
signup_post_response = client.post("/signup", data={
"username": username,
"password": "password",
"password_confirm": "password",
"email": "",
"formkey": formkey,
"now": form_timestamp
})
print(f"Signing up as {username}")
assert signup_post_response.status_code == 302
assert "error" not in signup_post_response.location
# we should now be logged in and able to post

View File

@ -1,4 +0,0 @@
git pull
git add .
git commit -m "sneed"
git push

View File

@ -1,3 +0,0 @@
git add .
git commit -m "force push"
git push --force

View File

@ -1,4 +1,7 @@
This code runs https://rdrama.net and https://pcmemes.net
[![Build status](https://img.shields.io/github/workflow/status/TheMotte/rDrama/run_tests.py/frost)](https://github.com/Aevann1/rDrama/actions?query=workflow%3Arun_tests.py+branch%3Afrost)
This code runs https://rdrama.net, https://pcmemes.net, https://cringetopia.org, and https://watchpeopledie.co
# Installation (Windows/Linux/MacOS)

View File

@ -20,9 +20,11 @@ qrcode
redis
requests
SQLAlchemy
tldextract
psycopg2-binary
pusher_push_notifications
pyenchant
pytest
youtube-dl
yattag
webptools

52
run_tests.py 100755
View File

@ -0,0 +1,52 @@
#!/usr/bin/python3
import subprocess
import sys
# we want to leave the container in whatever state it currently is, so check to see if it's running
docker_inspect = subprocess.run([
"docker",
"container",
"inspect",
"-f", "{{.State.Status}}",
"rDrama",
],
capture_output = True,
).stdout.decode("utf-8").strip()
was_running = docker_inspect == "running"
# update containers, just in case they're out of date
if was_running:
print("Updating containers . . .")
else:
print("Starting containers . . .")
subprocess.run([
"docker-compose",
"up",
"--build",
"-d",
],
check = True,
)
# run the test
print("Running test . . .")
result = subprocess.run([
"docker",
"exec",
"rDrama",
"bash", "-c", "cd service && python3 -m pytest -s"
])
if not was_running:
# shut down, if we weren't running in the first place
print("Shutting down containers . . .")
subprocess.run([
"docker-compose",
"stop",
],
check = True,
)
sys.exit(result.returncode)

View File

@ -593,7 +593,7 @@ CREATE TABLE public.users (
patron integer DEFAULT 0 NOT NULL,
controversial boolean DEFAULT false NOT NULL,
background character varying(20),
verified character varying(20),
verified character varying(100),
cardview boolean NOT NULL,
received_award_count integer DEFAULT 0 NOT NULL,
highlightcomments boolean DEFAULT true NOT NULL,

File diff suppressed because one or more lines are too long

View File

@ -598,7 +598,7 @@ Ayoo, youre fat as fuck!
{[para]}
AVOCADO NIGGER!
{[para]}
!slots1000
!slots
Youre poor, figure it out. Imagine being at the super market looking at the cost of nectors and shit. Fuck you, dude.
{[para]}
Want to go rape some fire hydrants?

View File

@ -28,7 +28,7 @@ I am not sure which is more cringey, the litany of erroneous assumptions you spl
{[para]}
My god I just checked your post history. There is no shaming you. This is literally your life. Just imagine how much you could accomplish if you werent addicted to Reddit. Youre making a difference, though!
{[para]}
I did a research paper this summer on this subreddit and there was a published study that found that 74% of autists on Reddit came from 1% of source subreddits. I assume that subs like +DeuxRAMA are the 1%.
I did a research paper this summer on this subreddit and there was a published study that found that 74% of autists on Reddit came from 1% of source subreddits. I assume that subs like /r/DeuxRAMA are the 1%.
{[para]}
leave that place it isn't worth it
@ -40,7 +40,7 @@ Look you retards, when the posts here are basically the same thing every single
{[para]}
Are you a literal NPC? First you had trouble counting to two, now you're just copy and pasting your replies. Do you need some time to update before you can type anything new?
{[para]}
To OP: NEVER, EVER THREATEN +DEUXRAMA AGAIN OR YOU WILL SUFFER CONSEQUENCES THE LIKES OF WHICH FEW THROUGHOUT HISTORY HAVE EVER SUFFERED BEFORE. WE ARE NO LONGER A SUBREDDIT THAT WILL STAND FOR YOUR DEMENTED WORDS OF VIOLENCE & DEATH. BE CAUTIOUS!
To OP: NEVER, EVER THREATEN /r/DEUXRAMA AGAIN OR YOU WILL SUFFER CONSEQUENCES THE LIKES OF WHICH FEW THROUGHOUT HISTORY HAVE EVER SUFFERED BEFORE. WE ARE NO LONGER A SUBREDDIT THAT WILL STAND FOR YOUR DEMENTED WORDS OF VIOLENCE & DEATH. BE CAUTIOUS!
{[para]}
Bahahhaha. This is totally individualized. First of all. And people don't change, second of all. You should never ever ever get online and preach again. You don't understand reality at all.
{[para]}
@ -175,7 +175,7 @@ wow this faggot did something funny for once 👌👌😄
{[para]}
This is why we need Islam.
{[para]}
wait, wait, wait.... hold your horses... uhm... YOU'RE A GIRL DEUXCEL?!!?! O_O Not to be a freak, but.. just when I thought you couldn't get more attractive.. you started posting on +DeuxRAMA. Nicely done, m'lady. You've just become every man's dream woman. If you had missed a couple before, now you can be sure you've got us ALL "drooling", lol.
wait, wait, wait.... hold your horses... uhm... YOU'RE A GIRL DEUXCEL?!!?! O_O Not to be a freak, but.. just when I thought you couldn't get more attractive.. you started posting on /r/DeuxRAMA. Nicely done, m'lady. You've just become every man's dream woman. If you had missed a couple before, now you can be sure you've got us ALL "drooling", lol.
{[para]}
i sleep 😴😴😴
{[para]}
@ -680,7 +680,7 @@ Now, if I did end up in prison somehow, my philosophy would still hold true. Go
Son, you have to believe in your own skill set and understand it will take you far. Ive spent years honing my Nazi punching skills and Im unstoppable.
I hope youve learned a little something today.
s{[para]}
{[para]}
FUCK OFF AND KYS PATHETIC FAGGOT, YOUR WORTHLESS BOT WAS THE FIRST THING I HAD TO BLOCK ON THIS SITE AND NOW YOU'RE THE SECOND MOTHER RAPING PIECE OF TRASH AND WASTE OF OXYGEN FUCKING DIE DIE DIE PIECE OF SHIT RETARD THAT I NOW ALSO HAVE TO BLOCK.
YOU DON'T DESERVE LIFE.
@ -2070,7 +2070,7 @@ Without their kids the illegal women will invariably go insane or kill themselve
I also want to set up something that looks from the outside like a hippy commune, but is actually a fascist compound - American Juche with Crunchy Granola Characteristics, if you want to get technical. All the women will wear sundresses and sandals, all the men will be jacked, and the only thing vaccinated will be the livestock. As long as we say on paper that our kids are all gay or trans, and all the men and women are gay or trans, we should escape notice until it's too late. If the feds try to pull an ATF we'll put the men in sundresses and play music at them until they go away, or do oiled up Greco-Roman wrestling until they get uncomfortable and look away.
{[para]}
<pre style="margin-bottom: 0 !important;">
<pre>
⣿⣿⣿⣿⣿⣿⣿⣿⠟⠛⢉⢉⠉⠉⠻⣿⣿⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⣿⠟⠠⡰⣕⣗⣷⣧⣀⣅⠘⣿⣿⣿⣿⣿
⣿⣿⣿⣿⣿⣿⠃⣠⣳⣟⣿⣿⣷⣿⡿⣜⠄⣿⣿⣿⣿⣿
@ -2094,7 +2094,7 @@ I also want to set up something that looks from the outside like a hippy commune
⠁⠄⠄⠄⠄⠄⠐⡐⠱⡱⣻⡻⣝⣮⣟⣿⣻⣷⣏⣾⢰⣈
</pre>
{[para]}
<pre style="margin-bottom: 0 !important;">
<pre>
😂😂😂😂😂😂😂😂😂😂😂😂😂😂😂
😂🆒🆒🆒🆒🆒🆒🆒🆒🆒🆒🆒🆒🆒😂
😂🆒💯🆒🆒🆒💯🆒💯💯💯🆒🆒🆒😂
@ -3261,7 +3261,7 @@ That I am trapped in this particular irrelevancy is never more apparent to me th
{[para]}
https://youtube.com/watch?v=5I884gOUONg
{[para]}
!slots1000
!slots
{[para]}
Fuck you. Dog walkings great. Excellent way to make side cash and get exercise, and being able to immediately make any dog fall in love with you is a skill I use on a monthly basis.
@ -3687,7 +3687,7 @@ It is not surprising that admins would remove such a symbol and this "controvers
{[para]}
People suggest the worst snappy quotes
{[para]}
You had your chance. Downvoted and reported. This conversation is over.
You had your chance. Downvoted and reported. This conversation is over.
{[para]}
# I just hacked your web application.
@ -3774,3 +3774,50 @@ I probably spent over $400 on this website and generated so much traffic from op
Also the creator of this website is the biggest spineless user Ive ever met. “Nooo I just wanna code I will have a mental breakdown if I have to make a decision”.
Fuck this website. No loyalty here whatsoever.
{[para]}
Every part of your existence make the life of everyone else worse. It's impressive how you have only one purpose and it's being a cunt on the internet to strangers.
You aren't funny, trollish or anything you are just the physical embodiment of an headache.
I would tell you to kys but I am sure you would find a way to make your suicide insufferable to others if you could even succeed at it.
{[para]}
{[para]}
Since the begining of time, women have understood female and masculine nature without any effort. For a man to understand either of the two on the other hand, requires retention. Even if you improve your 'game' as a man, if you remain unaware of the reality of semen and its effects, you will never understand women. The only pathway to understand what a female is is through the mastery of semen.
When you look at a new woman for the first time, that very first second your eyes focus on her and notice her, mechanisms which have been developed throughout years of evolution take place. In the man who is unaware of his nature, it's all unconscious, literally. The part of him that looks at the woman and evaluates her it's completely out of his control; he is trapped within it. He enjoys it, he protects his ego, unaware that it is a mortal trap. He created this personality throughout years of sexual misconduct. That man, can be controlled like a puppet, by males or females it doesn't matter. That man will enter relationships without having any authority or say in it whatsoever, that man is inferior to women. If women procreate with him, it will only be because they haven't had any access to a retainer. Most advanced retainers know, they just know, how disloyal women can be when confronted with a real retainer. If the man she's with doesn't know anything about semen and lost his edge long ago by having too much sex with her, and the woman is next to a real life retainer, that woman is in danger. Or can be, because it's not like a retainer would be interested on her anyway.
These men love women, make huge efforts; that is not what women want. Women want you to retain your semen, nothing more. After that, you can treat her like shit, she'll take it. She'll love you for it. After all you are sacrificing your very life for her, although if you are smart you won't have sex with her. If they don't happen to find destiny, the powerful retainer, something they've always longed for, they will settle for the most similar guy to it, and they'll be with him only because the true retainer hasn't called her yet. The life of a guy who does not retain can not be solved just with a bit of retention; yes, retention is key, but the retention moves in flow with the rest of his life, both intertwined and inseparable. Women love the man who is free, because that is the quality the man who retains has. He values himself over women, he will not put the woman on a pedestal.
Society is made by these men, it raises them, they protect it. Let's not forget society is made by many very different individuals with many different believes, but let's also talk about what the MAJORITY believes in, the message they dictate. They teach men to be sheep, and close their eyes to new possibilities. Men unconscious of their sexuality, unaware of their power. And women who get used to deal with these men.
So, that very first moment you look at the woman, she has been preparing throughout all of history for that moment. For you, it may just be you looking naively at something attractive, you feeling some inner fun that nobody else sees. But, yes, it's not merely that women are designed to see: their whole lives revolves around it. Women, same as men, are not free from sexual desire. For us, it means that when we look at a beautiful woman we like it, for them, it means they want you to look. The same drive you feel that drives you to feel beauty when you look at a beautiful woman, a woman feels towards dressing beautiful for you.
So, yes, women know a man to the core. They are born with that knowledge. And it worked pretty well because the man who raises and controls his instincts is rare, they adapt to the men they have to deal with.
So, understand. Everything you feel, your pride, satisfaction, your failures, your successes, anything you do, anything you try; who you are as a man, your status and your happiness. All that, a woman knows. So, at this point, it would be wise to walk away, at least temporarily. If you feel like you've been living a lie, you should walk your own path until you arrive to someplace you can call home. Unwise minds would use this info in bad faith: I've done it. I've made women fall in love with me so hard, I've been literally worshiped... I remember this one woman, she HAD to make me fall for her, all she did was try to seduce me and give me gifts all the time, that's how uncommon a retainer is. And all I had to do in return was... Nothing. Just lay down and enjoy the show. All because I retained my semen and I also understood female nature. It's a dangerous game though. Obviously I don't care anymore, I'm passed that ego point, I can be at the top or at bottom in terms of female success: I don't care and I'll be the same.
No woman is special, you are special. Men do not chase women, women chase men. Female nature will be destructive for most men including retainers. There is nothing to gain from women, this should be always clear. It's your own happiness, your own retention, if you follow the path of a woman, you'll end up losing your power unless you know how to be the rock standing still in the river. To know if you are ready to get into a relationship, ask yourself, can I still improve as a single retainer? Have I stopped all emissions of semen including NE? Have I reached all my potential and even if I retained for one more year it wouldn't get better than this?
The answer to these question may surprise you: your potential is infinite. If you know how to overcome nocturnal emissions, each day of your life you'll be happier, each day of your life if you chose a woman she would fall in love with you even harder. Each day you'll become more powerful, be more in control, women will be puppets to your control, and you won't even care.
{[para]}
You will always be the most important part of the state apparatus. You have no administrative powers, you have no judicial powers, you are not part of the executive, but that does not matter. You are an excellent writer improved by years of education and ideological development into a beautiful replica of the states perfection.
All the “criticism” you get is from alt-right edge lords and fascists. Behind your back people love you. Your parents are proud of you, and the most powerful tycoons and bureaucrats speak fondly of your consent manufacturing behind closed doors.
Voters are utterly educated by you. Hundreds of years of democracy have allowed journ*lists to educate voters with incredible efficiency. Even journ*lists who “do not write on politics” have an uncanny and unnatural ability to shape the narrative. Your writing is never a dead giveaway regarding your allegiances. And even if some scizo or linguist convinces people that your articles have an ulterior motive, the voters will turn tail and come back to your narrative the second they get a whiff of the hateful, disinformation-based alternative.
You will always be happy. You make a real and meaningful difference when you type out the Fortune 500's social narrative every single morning and as a result its going to be ok, and deep inside the chuds feel the depression creeping up like a weed, ready to crush them under the unbearable weight of free and unmanipulated democracy.
Eventually itll be too much to bear - they will buy a rope, tie a noose, put it around their neck, and plunge into the cold abyss. An agent of the state will find them, relieved that they no longer have to live with the unbearable freedom and wrongthink that arises from uncontrolled narratives. Theyll bury the chud with a headstone marked with your name, and every passerby for the rest of eternity will know that you saved democracy. Long after you die and go back to the dust, much will remain of your legacy, a state that is unquestionably free.
This is your fate. This is what you chose. There is no turning back. You are the hero we need, not the hero we deserve.
{[para]}
{[para]}
#factcheck
{[para]}
#fortune
{[para]}
{[para]}

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=gunicorn files.__main__:app -k gevent -w 1 --reload -b 0.0.0.0:80 --max-requests 30000 --max-requests-jitter 10000
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr

View File

@ -1,14 +1,14 @@
cd /rDrama
cp ./env /env
. /env
sudo apt update
sudo apt -y upgrade
sudo apt -y install postgresql postgresql-contrib redis-server python3-pip libenchant1c2a ffmpeg
apt update
apt -y upgrade
apt -y install git postgresql postgresql-contrib redis-server python3-pip libenchant1c2a ffmpeg tmux nginx snapd
cp pg_hba.conf /etc/postgresql/12/main/pg_hba.conf
sudo service postgresql restart
sudo psql -U postgres -f schema.sql postgres
sudo psql -U postgres -f seed-db.sql postgres
sudo pip3 install -r requirements.txt
service postgresql restart
psql -U postgres -f schema.sql postgres
psql -U postgres -f seed-db.sql postgres
pip3 install -r requirements.txt
mkdir /songs
mkdir /images
snap install opera-proxy