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 = ':{0}:' + 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