diff --git a/files/assets/css/main.css b/files/assets/css/main.css index d6f5a30bb..a21778cd6 100644 --- a/files/assets/css/main.css +++ b/files/assets/css/main.css @@ -3181,7 +3181,7 @@ small, .small { width: 28px; height: 28px; } -.profile-pic-20, .pp20, img[src^="/uid/"], img[src^="/pp/"] { +.profile-pic-20, .pp20, img[src^="/uid/"], img[src^="/pp/"]:not(img[alt^=":"]) { margin-right: 0.25rem !important; width: 20px; height: 20px; @@ -4539,7 +4539,7 @@ video { .text-removed { color: #ffabab !important; } -.mirrored, img[alt^=":!"], img[alt^=":#!"] { +.mirrored, img[alt^=":!"], img[alt^=":#!"], .pat-container[alt^=":!"], .pat-container[alt^=":#!"] { transform: scaleX(-1); -webkit-transform: scaleX(-1); } @@ -4589,6 +4589,33 @@ input[type=radio] ~ .custom-control-label::before { -o-object-fit: contain; object-fit: contain; } + +.pat-container { + position: relative; + display: inline-block; +} +img[pat] { + animation: pat-pfp-anim 0.3s infinite !important; + transform-origin: bottom center; + margin-top: 10%; + border-radius: 50%; + text-align: center; + object-fit: cover; + background-color: var(--gray-600); +} +.pat-hand { + position: absolute; + width: 90%; + height: 90%; + margin-top: -10%; + z-index: 1; +} +@keyframes pat-pfp-anim { + 0% { transform: scale(1, 0.8) } + 50% { transform: scale(0.8, 1) } + 100% { transform: scale(1, 0.8) } +} + .twitter-tweet { margin-bottom: 9.8px; padding-bottom: 7px; diff --git a/files/assets/images/pat/hand.webp b/files/assets/images/pat/hand.webp new file mode 100644 index 000000000..b165f7039 Binary files /dev/null and b/files/assets/images/pat/hand.webp differ diff --git a/files/helpers/const.py b/files/helpers/const.py index d7e80a9c6..c5a97dac2 100644 --- a/files/helpers/const.py +++ b/files/helpers/const.py @@ -717,10 +717,12 @@ marseys_const2 = marseys_const + ['chudsey','a','b','c','d','e','f','g','h','i', db.close() if SITE_NAME == 'PCM': + valid_username_chars = 'a-zA-Z0-9_\-А-я' valid_username_regex = re.compile("^[a-zA-Z0-9_\-А-я]{3,25}$", flags=re.A) mention_regex = re.compile('(^|\s|
)@(([a-zA-Z0-9_\-А-я]){3,25})', flags=re.A) mention_regex2 = re.compile('
@(([a-zA-Z0-9_\-А-я]){3,25})', flags=re.A) else: + valid_username_chars = 'a-zA-Z0-9_\-' valid_username_regex = re.compile("^[a-zA-Z0-9_\-]{3,25}$", flags=re.A) mention_regex = re.compile('(^|\s|
)@(([a-zA-Z0-9_\-]){1,25})', flags=re.A) mention_regex2 = re.compile('
@(([a-zA-Z0-9_\-]){1,25})', flags=re.A)
@@ -759,10 +761,10 @@ strikethrough_regex = re.compile('''~{1,2}([^~]+)~{1,2}''', flags=re.A)
mute_regex = re.compile("/mute @([a-z0-9_\-]{3,25}) ([0-9])+", flags=re.A)
-emoji_regex = re.compile("[^a]>\s*(:[!#]{0,2}\w+:\s*)+<\/", flags=re.A)
-emoji_regex2 = re.compile('(?\s*(:[!#@]{{0,3}}[{valid_username_chars}]+:\s*)+<\/", flags=re.A)
+emoji_regex2 = re.compile(f"(?([\w:~,()\-.#&\/=?@%;+]{5,250})<\/a>', flags=re.A)
diff --git a/files/helpers/sanitize.py b/files/helpers/sanitize.py
index 7127c5bc8..9c41067d1 100644
--- a/files/helpers/sanitize.py
+++ b/files/helpers/sanitize.py
@@ -43,7 +43,8 @@ def allowed_attributes(tag, name, value):
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']: return True
+ if name in ['alt','title','g','b','pat']: return True
+ if name == 'class' and value == 'pat-hand': return True
return False
if tag == 'lite-youtube':
@@ -64,6 +65,13 @@ def allowed_attributes(tag, name, value):
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
+ return False
+
url_re = build_url_re(tlds=TLDS, protocols=['http', 'https'])
@@ -81,6 +89,43 @@ def handler(signum, frame):
print("Timeout!")
raise Exception("Timeout")
+def render_emoji(html, regexp, edit, marseys_used=set(), b=False):
+ emojis = list(regexp.finditer(html))
+ captured = set()
+
+ for i in emojis:
+ if i.group(0) in captured: continue
+ captured.add(i.group(0))
+
+ emoji = i.group(1).lower()
+ attrs = ''
+ if b: attrs += ' b'
+ if not edit and len(emojis) <= 20 and random() < 0.0025 and ('marsey' in emoji or emoji in marseys_const2): attrs += ' g'
+
+ old = emoji
+ emoji = emoji.replace('!','').replace('#','')
+ if emoji == 'marseyrandom': emoji = choice(marseys_const2)
+
+ emoji_partial = ''
+ emoji_html = None
+
+ if path.isfile(f'files/assets/images/emojis/{emoji}.webp'):
+ emoji_html = emoji_partial.format(old, f'/e/{emoji}.webp', attrs)
+ elif emoji.endswith('pat'):
+ if path.isfile(f"files/assets/images/emojis/{emoji.replace('pat','')}.webp"):
+ pat(emoji.replace('pat',''))
+ emoji_html = emoji_partial.format(old, f'/e/{emoji}.webp', attrs)
+ requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data={'files': [f"https://{request.host}/e/{emoji}.webp"]}, timeout=5)
+ elif emoji.startswith('@'):
+ if u := get_user(emoji[1:-3], graceful=True):
+ attrs += ' pat'
+ emoji_html = f'
{emoji_partial.format(old, f"/pp/{u.id}", attrs)}'
+
+
+ if emoji_html:
+ html = re.sub(f'(?\1', sanitized)
- if comment: marseys_used = set()
+ marseys_used = set()
emojis = list(emoji_regex.finditer(sanitized))
if len(emojis) > 20: edit = True
@@ -159,55 +204,14 @@ def sanitize(sanitized, alert=False, comment=False, edit=False):
if 'marseylong1' in old or 'marseylong2' in old or 'marseyllama1' in old or 'marseyllama2' in old: new = old.lower().replace(">", " class='mb-0'>")
else: new = old.lower()
- captured2 = []
- for i in emoji_regex2.finditer(new):
- if i.group(0) in captured2: continue
- captured2.append(i.group(0))
-
- emoji = i.group(1).lower()
- remoji = emoji.replace('!','').replace('#','')
-
- golden = ' '
- if not edit and random() < 0.0025 and ('marsey' in emoji or emoji in marseys_const2): golden = 'g '
-
- if remoji == 'marseyrandom': remoji = choice(marseys_const2)
-
- if path.isfile(f'files/assets/images/emojis/{remoji}.webp'):
- new = re.sub(f'(?', new, flags=re.I|re.A)
- if comment: marseys_used.add(emoji)
- elif remoji.endswith('pat') and path.isfile(f"files/assets/images/emojis/{remoji.replace('pat','')}.webp"):
- pat(remoji.replace('pat',''))
- new = re.sub(f'(?', new, flags=re.I|re.A)
- requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data={'files': [f"https://{request.host}/e/{emoji}.webp"]}, timeout=5)
-
+ new = render_emoji(new, emoji_regex2, edit, marseys_used, True)
sanitized = sanitized.replace(old, new)
emojis = list(emoji_regex3.finditer(sanitized))
if len(emojis) > 20: edit = True
- captured = []
- for i in emojis:
- if i.group(0) in captured: continue
- captured.append(i.group(0))
-
- emoji = i.group(1).lower()
- golden = ' '
- if not edit and random() < 0.0025 and ('marsey' in emoji or emoji in marseys_const2): golden = 'g '
-
- old = emoji
- emoji = emoji.replace('!','').replace('#','')
- if emoji == 'marseyrandom': emoji = choice(marseys_const2)
-
-
- if path.isfile(f'files/assets/images/emojis/{emoji}.webp'):
- sanitized = re.sub(f'(?', sanitized, flags=re.I|re.A)
- if comment: marseys_used.add(emoji)
- elif emoji.endswith('pat') and path.isfile(f"files/assets/images/emojis/{emoji.replace('pat','')}.webp"):
- pat(emoji.replace('pat',''))
- sanitized = re.sub(f'(?', sanitized, flags=re.I|re.A)
- requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data={'files': [f"https://{request.host}/e/{emoji}.webp"]}, timeout=5)
-
+ sanitized = render_emoji(sanitized, emoji_regex3, edit, marseys_used)
for rd in ["://reddit.com", "://new.reddit.com", "://www.reddit.com", "://redd.it", "://libredd.it", "://teddit.net"]:
sanitized = sanitized.replace(rd, "://old.reddit.com")
@@ -313,29 +317,7 @@ def filter_emojis_only(title, edit=False, graceful=False):
title = title.replace('','').replace('','').replace("\ufeff", "").replace("𒐪","").replace("\n", "").replace("\r", "").replace("\t", "").replace("&", "&").replace('<','<').replace('>','>').replace('"', '"').replace("'", "'").strip()
- emojis = list(emoji_regex4.finditer(title))
- if len(emojis) > 20: edit = True
-
- captured = []
- for i in emojis:
- if i.group(0) in captured: continue
- captured.append(i.group(0))
-
- emoji = i.group(1).lower()
- golden = ' '
- if not edit and random() < 0.0025 and ('marsey' in emoji or emoji in marseys_const2): golden = 'g '
-
- old = emoji
- emoji = emoji.replace('!','').replace('#','')
- if emoji == 'marseyrandom': emoji = choice(marseys_const2)
-
- if path.isfile(f'files/assets/images/emojis/{emoji}.webp'):
- title = re.sub(f'(?', title, flags=re.I|re.A)
- elif emoji.endswith('pat') and path.isfile(f"files/assets/images/emojis/{emoji.replace('pat','')}.webp"):
- pat(emoji.replace('pat',''))
- title = re.sub(f'(?', title, flags=re.I|re.A)
- requests.post(f'https://api.cloudflare.com/client/v4/zones/{CF_ZONE}/purge_cache', headers=CF_HEADERS, data={'files': [f"https://{request.host}/e/{emoji}.webp"]}, timeout=5)
-
+ title = render_emoji(title, emoji_regex4, edit)
title = strikethrough_regex.sub(r'
\1', title)
@@ -344,4 +326,4 @@ def filter_emojis_only(title, edit=False, graceful=False):
signal.alarm(0)
if len(title) > 1500 and not graceful: abort(400)
- else: return title
\ No newline at end of file
+ else: return title