From bdc32ac5faa480410a5d34055c8ea9bf3166ee2f Mon Sep 17 00:00:00 2001 From: Corban-Lee Date: Wed, 19 Jun 2024 00:37:58 +0100 Subject: [PATCH] simplified + added mutators --- src/feed.py | 58 ++------- src/mutators.py | 316 +++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 312 insertions(+), 62 deletions(-) diff --git a/src/feed.py b/src/feed.py index 29fab90..8b9f1c1 100644 --- a/src/feed.py +++ b/src/feed.py @@ -16,7 +16,7 @@ from sqlalchemy import select, insert, delete, and_ from sqlalchemy.exc import NoResultFound from textwrap import shorten -import mutators +from mutators import mutator_map from errors import IllegalFeed from db import DatabaseManager, RssSourceModel, FeedChannelModel from utils import get_rss_data, get_unparsed_feed @@ -69,55 +69,21 @@ class Article: ) def mutate(self, mutator: dict): + """ + Apply a mutation to a certain text attribute of + this Article instance. + """ log.debug("Applying mutator '%s'", mutator["name"]) - match mutator["value"]: + mutator_value = mutator["value"] - case "UWU_TITLE": - self.title = mutators.uwu(self.title) - - case "UWU_DESC": - self.description = mutators.uwu(self.description) - - case "GIB_TITLE": - self.title = mutators.gibberish(self.title) - - case "GIB_DESC": - self.description = mutators.gibberish(self.description) - - case "L3_TITLE": - self.title = mutators.leet(self.title) - - case "L3_DESC": - self.description = mutators.leet(self.description) - - case "REV_TITLE": - self.title = mutators.reverse(self.title) - - case "REV_DESC": - self.description = mutators.reverse(self.description) - - case "RND_TITLE": - self.title = mutators.shuffle(self.title) - - case "RND_DESC": - self.description = mutators.shuffle(self.description) - - case "PGL_TITLE": - self.title = mutators.pig_latin(self.title) - - case "PGL_DESC": - self.description = mutators.pig_latin(self.description) - - case "RCS_TITLE": - self.title = mutators.random_case(self.title) - - case "RCS_DESC": - self.description = mutators.random_case(self.description) - - case _: - log.warn("Unknown mutator value '%s', skipping", mutator["value"]) + if mutator_value in mutator_map: + attr, func = mutator_map[mutator_value] + setattr(self, attr, func(getattr(self, attr))) + log.debug("mutated %s, to: %s", attr, getattr(self, attr)) + else: + log.warn("Unknown mutator value '%s', skipping", mutator["value"]) async def get_thumbnail_url(self, session: aiohttp.ClientSession) -> str | None: """Returns the thumbnail URL for an article. diff --git a/src/mutators.py b/src/mutators.py index 802f482..69c579e 100644 --- a/src/mutators.py +++ b/src/mutators.py @@ -2,50 +2,334 @@ import random import uwuify +# For l33t sp34k translation leet_map = {'a': '4', 'e': '3', 'i': '1', 'o': '0', 's': '5', 't': '7'} +# For upside-down translation +upside_down_dict = str.maketrans( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890,.!?\"'", + "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz∀BↃᗡƎℲ⅁HIſ⋊⅃WNOԀΌᴚS⊥∩ΛMX⅄Z⇂2Ɛᔭ59Ɫ860˙‘¡¿,'" +) + +# For gothic script +gothic_dict = { + 'a': '𝔞', 'b': '𝔟', 'c': '𝔠', 'd': '𝔡', 'e': '𝔢', 'f': '𝔣', 'g': '𝔤', 'h': '𝔥', 'i': '𝔦', 'j': '𝔧', + 'k': '𝔨', 'l': '𝔩', 'm': '𝔪', 'n': '𝔫', 'o': '𝔬', 'p': '𝔭', 'q': '𝔮', 'r': '𝔯', 's': '𝔰', 't': '𝔱', + 'u': '𝔲', 'v': '𝔳', 'w': '𝔴', 'x': '𝔵', 'y': '𝔶', 'z': '𝔷', 'A': '𝔄', 'B': '𝔅', 'C': 'ℭ', 'D': '𝔇', + 'E': '𝔈', 'F': '𝔉', 'G': '𝔊', 'H': 'ℌ', 'I': 'ℑ', 'J': '𝔍', 'K': '𝔎', 'L': '𝔏', 'M': '𝔐', 'N': '𝔑', + 'O': '𝔒', 'P': '𝔓', 'Q': '𝔔', 'R': 'ℜ', 'S': '𝔖', 'T': '𝔗', 'U': '𝔘', 'V': '𝔙', 'W': '𝔚', 'X': '𝔛', + 'Y': '𝔜', 'Z': 'ℨ' +} + +# For emoji substitution +emoji_dict = { + 'a': '🅰️', 'b': '🅱️', 'c': '🌜', 'd': '🌛', 'e': '📧', 'f': '🎏', 'g': '🌀', 'h': '♓', 'i': 'ℹ️', + 'j': '🎷', 'k': '🎋', 'l': '👢', 'm': 'Ⓜ️', 'n': '♑', 'o': '⚽', 'p': '🅿️', 'q': '🍳', 'r': '🌱', + 's': '💲', 't': '🌴', 'u': '⛎', 'v': '♈', 'w': '🔱', 'x': '❎', 'y': '🍸', 'z': 'Ⓩ' +} + +# For Zalgo translation +zalgo_chars = [chr(random.randint(768, 879)) for _ in range(3)] + +# For morse code translation +morse_code_dict = { + 'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', 'G': '--.', 'H': '....', + 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', + 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', + 'Y': '-.--', 'Z': '--..', '1': '.----', '2': '..---', '3': '...--', '4': '....-', '5': '.....', + '6': '-....', '7': '--...', '8': '---..', '9': '----.', '0': '-----', ',': '--..--', '.': '.-.-.-', + '?': '..--..', '/': '-..-.', '-': '-....-', '(': '-.--.', ')': '-.--.-', ' ': '/' +} + def uwu(text: str) -> str: + """ + Returns an 'uwuified' version of the given string. + """ + return uwuify.uwu(text) def gibberish(text: str) -> str: + """ + Returns a given string, but each character is randomy re-ordered. + """ + return " ".join(["".join( random.sample(word, len(word))) for word in text.split() ]) def leet(text: str) -> str: + """ + Translates a given string into 'l33t sp34k' and returns it. + """ + return "".join([ leet_map.get(char.lower(), char) - for char in text + for char in text.upper() ]) def reverse(text: str) -> str: + """ + Returns the passed string in reverse. + """ + return text[::-1] def shuffle(text: str) -> str: + """ + Returns a given string, where each word is randomly shuffled. + """ + words = text.split() random.shuffle(words) return " ".join(words) -def _pig_latin_word(word: str) -> str: - first_vowel = 0 - for char in word: - if char in "aeiou": - break - - first_vowel += 1 - - if first_vowel == 0: - return word + "way" - - else: - return word[first_vowel:] + word[:first_vowel] + "ay" - def pig_latin(text: str) -> str: + """ + Takes a given string, translates it to 'pig latin' and returns it. + """ + + def _pig_latin_word(word: str) -> str: + first_vowel = 0 + for char in word: + if char in "aeiou": + break + + first_vowel += 1 + + if first_vowel == 0: + return word + "way" + + else: + return word[first_vowel:] + word[:first_vowel] + "ay" + return " ".join([_pig_latin_word(word) for word in text.split()]) def random_case(text: str) -> str: + """ + Randomly converts each character of a given string to upper + or lower case, and returns the result. + """ + return "".join([ char.upper() if random.random() > 0.5 else char.lower() for char in text - ]) \ No newline at end of file + ]) + +def upside_down_text(text: str) -> str: + """ + Returns an upside-down translation of the given string. + """ + + return text.translate(upside_down_dict)[::-1] + +def gothic_script(text: str) -> str: + """ + Returns a 'gothic script' version of the given string. + """ + + return ''.join(gothic_dict.get(c, c) for c in text) + +def emoji_substitution(text: str) -> str: + """ + Uses an emoji substitution sipher on the given string, + and returns the result. + """ + + return ''.join(emoji_dict.get(c.lower(), c) for c in text) + +def small_caps(text: str) -> str: + """ + Translates a given string to it's small caps ascii counterpart, + and returns the result. + """ + + def _to_small_caps(c: str) -> str: + if "a" <= c <= "z": + return chr(ord(c) + 0x1D00 - ord("a")) + elif "A" <= c <= "Z": + return chr(ord(c) + 0x1D00 - ord("A")) + else: + return c + + return ''.join(_to_small_caps(c) for c in text) + +def zalgo(text: str) -> str: + return "".join( + c + "".join(random.choices( + zalgo_chars, k=random.randint(1, 3) + )) for c in text + ) + +def morse_code(text: str) -> str: + return " ".join(morse_code_dict.get(c.upper(), c) for c in text) + +def to_binary(text: str) -> str: + return ' '.join(format(ord(c), '08b') for c in text) + +def to_hexadecimal(text: str) -> str: + return ' '.join(format(ord(c), '02x') for c in text) + +def remove_vowels(text: str) -> str: + return ''.join(c for c in text if c.lower() not in 'aeiou') + +def double_characters(text: str) -> str: + return ''.join(c*2 for c in text) + +def randomly_inserted_emoji(text: str) -> str: + emojis = ['😀', '😂', '😍', '😎', '😊', '😜'] + return ' '.join(word + random.choice(emojis) for word in text.split()) + +def baby_talk(text: str) -> str: + replacements = {'r': 'w', 'l': 'w', 'th': 'd', 'u': 'oo'} + baby_text = [] + for word in text.split(): + for k, v in replacements.items(): + word = word.replace(k, v) + baby_text.append(word) + return " ".join(baby_text) + +def pirate_speak(text: str) -> str: + replacements = {'hello': 'ahoy', 'my': 'me', 'friend': 'matey', 'is': 'be', 'am': 'be', 'are': 'be'} + pirate_text = [ + replacements.get(word.lower(), word) + for word in text.split() + ] + return " ".join(pirate_text) + ", arrr!" + +def valley_girl(text: str) -> str: + valley_text = [] + for word in text.split(): + valley_text.append(word) + if random.random() < 0.3: + valley_text.append("like") + + return ' '.join(valley_text) + ", you know?" + +def degeneracy(text: str) -> str: + cute_phrases = ['kawaii', 'nya~', 'uwu', '^_^', 'rawr'] + cute_text = [] + for word in text.split(): + cute_text.append(word) + if random.random() < 0.3: + cute_text.append(random.choice(cute_phrases)) + return " ".join(cute_text) + +def cat_speak(text: str) -> str: + cat_text = [] + for word in text.split(): + cat_text.append(word) + if random.random() < 0.3: + cat_text.append('meow') + + return " ".join(cat_text) + +def nerdify(text: str) -> str: + nerdy_phrases = ['quark', 'photon', 'nanobot', 'AI', 'quantum', 'algorithm'] + words = text.split() + return ' '.join(word + random.choice(nerdy_phrases) if random.random() < 0.3 else word for word in words) + +def backward_words(text: str) -> str: + return ' '.join(word[::-1] for word in text.split()) + +def random_gibberish(text: str) -> str: + gibberish_words = ['blorpy', 'snorf', 'flibber', 'zoodle', 'womp', 'glarb'] + words = text.split() + return ' '.join(random.choice(gibberish_words) if random.random() < 0.3 else word for word in words) + +def insert_random_animal_sounds(text: str) -> str: + animal_sounds = ['moo', 'oink', 'meow', 'woof', 'quack', 'neigh'] + result = [] + for word in text.split(): + result.append(word) + if random.random() < 0.3: + result.append(random.choice(animal_sounds)) + return ' '.join(result) + +def shakespearean_speak(text: str) -> str: + shakespearean_dict = { + 'you': 'thou', 'your': 'thy', 'you\'re': 'thou art', 'are': 'art', 'am': 'am', 'is': 'is', 'have': 'hast', + 'will': 'wilt', 'today': 'this day', 'hello': 'hail', 'goodbye': 'farewell' + } + words = text.split() + return ' '.join(shakespearean_dict.get(word.lower(), word) for word in words) + +def robot_speak(text: str) -> str: + robot_noises = ['beep', 'boop', 'whirr', 'click', 'buzz'] + words = text.split() + return ' '.join(random.choice(robot_noises) if word.isalpha() and random.random() < 0.2 else word for word in words) + +def emojify(text: str) -> str: + emoji_dict = { + 'happy': '😄', 'sad': '😢', 'angry': '😡', 'love': '❤️', 'laugh': '😂', 'cry': '😭', 'sleepy': '😴', + 'surprise': '😮', 'cool': '😎', 'fire': '🔥', 'party': '🎉', 'star': '⭐' + } + words = text.split() + return ' '.join(emoji_dict.get(word.lower(), word) for word in words) + + + + + +# Maps instructions for an Article item to mutate attributes. +mutator_map = { + "UWU_TITLE": ("title", uwu), + "UWU_DESC": ("description", uwu), + "GIB_TITLE": ("title", gibberish), + "GIB_DESC": ("description", gibberish), + "L3_TITLE": ("title", leet), + "L3_DESC": ("description", leet), + "REV_TITLE": ("title", reverse), + "REV_DESC": ("description", reverse), + "RND_TITLE": ("title", shuffle), + "RND_DESC": ("description", shuffle), + "PGL_TITLE": ("title", pig_latin), + "PGL_DESC": ("description", pig_latin), + "RNC_TITLE": ("title", random_case), + "RNC_DESC": ("description", random_case), + "UDT_TITLE": ("title", upside_down_text), + "UDT_DESC": ("description", upside_down_text), + "GS_TITLE": ("title", gothic_script), + "GS_DESC": ("description", gothic_script), + "EMJ_TITLE": ("title", emoji_substitution), + "EMJ_DESC": ("description", emoji_substitution), + "SML_TITLE": ("title", small_caps), + "SML_DESC": ("description", small_caps), + "ZGO_TITLE": ("title", zalgo), + "ZGO_DESC": ("description", zalgo), + "MC_TITLE": ("title", morse_code), + "MC_DESC": ("description", morse_code), + "BIN_TITLE": ("title", to_binary), + "BIN_DESC": ("description", to_binary), + "HEX_TITLE": ("title", to_hexadecimal), + "HEX_DESC": ("description", to_hexadecimal), + "RMV_TITLE": ("title", remove_vowels), + "RMV_DESC": ("description", remove_vowels), + "DBL_TITLE": ("title", double_characters), + "DBL_DESC": ("description", double_characters), + "RNE_TITLE": ("title", randomly_inserted_emoji), + "RNE_DESC": ("description", randomly_inserted_emoji), + "PIR_TITLE": ("title", pirate_speak), + "PIR_DESC": ("description", pirate_speak), + "VAL_TITLE": ("title", valley_girl), + "VAL_DESC": ("description", valley_girl), + "DEG_TITLE": ("title", degeneracy), + "DEG_DESC": ("description", degeneracy), + "CAT_TITLE": ("title", cat_speak), + "CAT_DESC": ("description", cat_speak), + "NRD_TITLE": (), + "NRD_DESC": (), + "BKW_TITLE": (), + "BKW_DESC": (), + "RNG_TITLE": (), + "RNG_DESC": (), + "RAS_TITLE": (), + "RAS_DESC": (), + "SHK_TITLE": (), + "SHK_DESC": (), + "RBT_TITLE": (), + "RBT_DESC": (), + "EMI_TITLE": (), + "EMI_DESC": (), + +} \ No newline at end of file