diff --git a/.github/add-headers.py b/.github/add-headers.py index e7935a14d..c1340569a 100644 --- a/.github/add-headers.py +++ b/.github/add-headers.py @@ -1,32 +1,32 @@ import os headersData = { - "AdblockVPNGuide.md": [":name_badge:", "140", "# Adblocking / Privacy", "Adblocking, Privacy, VPN's, Proxies, Antivirus"], - "AI.md": [":robot_face:", "130", "# Artificial Intelligence", "Chat Bots, Text Generators, Image Generators, ChatGPT Tools"], - "Android-iOSGuide.md": [":iphone:", "45", "# Android / iOS", "Apps, Jailbreaking, Android Emulators"], - "AudioPiracyGuide.md": [":musical_note:", "110", "# Music / Podcasts / Radio", "Stream Audio, Download Audio, Torrent Audio"], - "Beginners-Guide.md": [":pirate_flag:", "150", "# Beginners Guide to Piracy", ""], - "DEVTools.md": [":male-technologist:", "50", "# Developer Tools", ""], - "DownloadPiracyGuide.md": [":floppy_disk:", "80", "# Downloading", "Download Sites, Software Sites, Open Directories"], - "EDUPiracyGuide.md": [":brain:", "60", "# Educational", "Courses, Documentaries, Learning Resources"], - "GamingPiracyGuide.md": [":video_game:", "100", "# Gaming / Emulation", "Download Games, ROMs, Gaming Tools"], - "LinuxGuide.md": [":penguin:", "40", "# Linux / MacOS", "Apps, Software Sites, Gaming"], - "MISCGuide.md": [":open_file_folder:", "30", "# Miscellaneous", "Extensions, Indexes, News, Health, Food, Fun"], - "NSFWPiracy.md": [":underage:", "25", "# NSFW", ""], - "Non-English.md": [":earth_asia:", "35", "# Non-English", "International Piracy Sites"], - "ReadingPiracyGuide.md": [":green_book:", "90", "# Books / Comics / Manga", "Books, Comics, Magazines, Newspapers"], - "STORAGE.md": [":card_file_box:", "1", "", ""], - "TOOLSGuide.md": [":wrench:", "58", "# Tools", "General Tools, Internet Tools, System Tools"], - "TorrentPiracyGuide.md": [":cyclone:", "70", "# Torrenting", "Torrent Clients, Torrent Sites, Trackers"], - "VideoPiracyGuide.md": [":tv:", "120", "# Movies / TV / Anime", "Stream Videos, Download Videos, Torrent Videos"], - "base64.md": [":key:", "20", "", ""], - "img-tools.md": [":camera:", "55", "# Image Tools", ""], - "UnsafeSites.md": [":warning:", "21", "# Unsafe Sites", ""] + "AdblockVPNGuide.md": ["Adblocking / Privacy", "Adblocking, Privacy, VPN's, Proxies, Antivirus"], + "AI.md": ["Artificial Intelligence", "Chat Bots, Text Generators, Image Generators, ChatGPT Tools"], + "Android-iOSGuide.md": ["Android / iOS", "Apps, Jailbreaking, Android Emulators"], + "AudioPiracyGuide.md": ["Music / Podcasts / Radio", "Stream Audio, Download Audio, Torrent Audio"], + "Beginners-Guide.md": ["Beginners Guide", "A Guide for Beginners to Piracy"], + "DEVTools.md": ["Developer Tools", "Git, Hosting, App Dev, Software Dev"], + "DownloadPiracyGuide.md": ["Downloading", "Download Sites, Software Sites, Open Directories"], + "EDUPiracyGuide.md": ["Educational", "Courses, Documentaries, Learning Resources"], + "GamingPiracyGuide.md": ["Gaming / Emulation", "Download Games, ROMs, Gaming Tools"], + "LinuxGuide.md": ["Linux / MacOS", "Apps, Software Sites, Gaming"], + "MISCGuide.md": ["Miscellaneous", "Extensions, Indexes, News, Health, Food, Fun"], + "NSFWPiracy.md": ["NSFW", "NSFW Indexes, Streaming, Downloading"], + "Non-English.md": ["Non-English", "International Piracy Sites"], + "ReadingPiracyGuide.md": ["Books / Comics / Manga", "Books, Comics, Magazines, Newspapers"], + "STORAGE.md": ["Storage", "Index for everything in the wiki."], + "TOOLSGuide.md": ["Tools", "General Tools, Internet Tools, System Tools"], + "TorrentPiracyGuide.md": ["Torrenting", "Torrent Clients, Torrent Sites, Trackers"], + "VideoPiracyGuide.md": ["Movies / TV / Anime", "Stream Videos, Download Videos, Torrent Videos"], + "base64.md": ["Base64", "Base64 storage"], + "img-tools.md": ["Image Tools", "Image Editors, Generators, Compress"], + "UnsafeSites.md": ["Unsafe Sites", "Unsafe/harmful sites to avoid."] } def getHeaderForPage(pageFilename): data = headersData[pageFilename] - header = '---\n' + 'icon: ' + '"' + data[0] + '"' + '\n' + 'order: ' + data[1] + '\n' + '---\n' + data[2] + '\n' + data[3] + '\n\n' + header = '---\n' + 'title: ' + '"' + data[0] + '"' + '\n' + 'description: ' + data[1] + '\n' + '---\n' + '# ' + data[0] + '\n' + data[1] + '\n\n' return header def apply_to_all_md_files_in_current_dir(): @@ -36,7 +36,6 @@ def apply_to_all_md_files_in_current_dir(): with open(file, 'r', encoding='utf-8') as f: content = f.read() if not content.startswith('---'): - print("adding header to " + file) with open(file, 'w', encoding='utf-8') as f2: header = getHeaderForPage(file) f2.write(header+content) diff --git a/.github/assets/README.md b/.github/assets/README.md deleted file mode 100644 index 09e8432cf..000000000 --- a/.github/assets/README.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -image: /static/banner4.png -icon: ":wave:" ---- -# Welcome - -![](/static/banner4.png) - -**The Largest Collection of Free Stuff On The Internet!** - -* Anyone can suggest [changes or corrections](https://rentry.org/fmhyedit) to the wiki. Please read our [Contribution Guide](https://rentry.co/Contrib-Guide) before trying to add or remove anything. -* If you're adding a new site, please [search](https://raw.githubusercontent.com/nbats/FMHYedit/main/single-page) first to make sure we don't already have it. -* Approved edits will be applied to this site and all [🔒 backups](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/backups). -* You can send us stuff directly via [💬 Discord](https://redd.it/17f8msf). -* You can also checkout our subreddit, [r/FREEMEDIAHECKYEAH](https://www.reddit.com/r/FREEMEDIAHECKYEAH/) to know about any major updates to the wiki. - -*** - -Emoji Legend: - -* 🌐 - 3rd Party Indexes -* ↪️ - Storage Page Links -* ⭐ - Community Recommendations diff --git a/.github/assets/retype.yml b/.github/assets/retype.yml deleted file mode 100644 index 4bf2c0c6b..000000000 --- a/.github/assets/retype.yml +++ /dev/null @@ -1,33 +0,0 @@ -input: . -output: .retype -url: fmhy.pages.dev -branding: - logo: /static/fmhy.ico - logoDark: /static/fmhy.ico - title: FMHY -favicon: /static/favicon.ico - -links: # Top bar. Icons from FontAwesome 6.4.2. - - text: Discord - link: https://redd.it/17f8msf - icon: - - text: Search - link: https://fmhy-search.streamlit.app/ - icon: - - text: Reddit - link: https://www.reddit.com/r/FREEMEDIAHECKYEAH/ - icon: - - text: Github - link: https://github.com/fmhy - icon: - - text: Updates - link: https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/updates/ - icon: log - - text: Backups - link: https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/backups - icon: shield-lock - -footer: - copyright: "" -meta: - title: " | FreeMediaHeckYeah" diff --git a/.github/replace-links-to-fmhy-in-files-in-current-dir.py b/.github/replace-links-to-fmhy-in-files-in-current-dir.py deleted file mode 100644 index 9a279ca4f..000000000 --- a/.github/replace-links-to-fmhy-in-files-in-current-dir.py +++ /dev/null @@ -1,193 +0,0 @@ -import re -import os -import sys - -def replaces_for_beginners_guide(text): - text = re.sub('\[TOC\]\n', '', text, flags=re.MULTILINE) - text = re.sub('\*\*Table of Contents\*\*\n\[TOC2\]\n', '', text, flags=re.MULTILINE) - text = re.sub('# -> \*\*\*Beginners Guide to Piracy\*\*\* <-\n', '', text, flags=re.MULTILINE) - text = re.sub(r"!!!note\s(.+?)\n", r"!!!\n\1\n!!!\n", text, flags=re.MULTILINE) - text = re.sub(r"!!!info\s(.+?)\n", r"!!!\n\1\n!!!\n", text, flags=re.MULTILINE) - text = re.sub(r"!!!warning\s(.+?)\n", r"!!!warning\n\1\n!!!\n", text, flags=re.MULTILINE) - text = re.sub(r">\s(.+?)\n", r"> \1\n\n", text, flags=re.MULTILINE) - text = re.sub('\*\*\[\^ Back to Top\]\(#beginners-guide-to-piracy\)\*\*', '', text, flags=re.MULTILINE) - text = re.sub("!!!\n!!!\n", "!!!\n", text, flags=re.MULTILINE) - text = re.sub("\n\*\*\[", "\n* **[", text, flags=re.MULTILINE) - return text - -def do_some_individual_replaces(text): - #special cases of link not replaced correctly - text = re.sub('.pages.dev/storage/#encode--decode_urls', '.pages.dev/storage/#encode--decode-urls', text) - text = re.sub('.pages.dev/base64/#do-k-ument', '.pages.dev/base64/#do_k_ument', text) - text = re.sub('.pages.dev/devtools/#machine-learning2', '.pages.dev/devtools/#machine-learning-1', text) - - #Base64-decoder script link - text = re.sub('\*\* site or extension\.\n', '** site or extension\.\nAlternatively, install this [userscript](https://rentry.co/wc7s2/raw)\n', text, flags=re.MULTILINE) - - return text - -def change_some_general_formatting(text): - text = re.sub('\*\*\*\n\n', '', text, flags=re.MULTILINE) - text = re.sub('\*\*\*\n', '', text, flags=re.MULTILINE) - - text = re.sub('# ►', '##', text) - text = re.sub('## ▷', '###', text) - text = re.sub('####', '###', text) - - text = re.sub(r'^\*\*Note\*\* - (.+)$', r'!!!\n\1\n!!!', text, flags=re.MULTILINE) - text = re.sub(r'^\* \*\*Note\*\* - (.+)$', r'!!!\n\1\n!!!', text, flags=re.MULTILINE) - text = re.sub(r'^Note - (.+)$', r'!!!\n\1\n!!!', text, flags=re.MULTILINE) - text = re.sub(r'^\*\*Warning\*\* - (.+)$', r'!!!warning\n\1\n!!!', text, flags=re.MULTILINE) - - return text - -def remove_backtowiki_and_toc(text): - text = re.sub('\*\*\[◄◄ Back to Wiki Index\]\(https://www\.reddit\.com/r/FREEMEDIAHECKYEAH/wiki/index\)\*\*\n', '', text, flags=re.MULTILINE) - text = re.sub(r'\*\*\[Table of Contents\]\(https?:\/\/.*?ibb\.co.*\)\*\* - For mobile users\n', '', text, flags=re.MULTILINE) - text = re.sub("\*\*\*\n\*\*\*\n\*\*\*\n\*\*\*\n\n\n\*\*\*\n\*\*\*\n\n", '', text, flags=re.MULTILINE) - text = re.sub("\*\*\*\n\*\*\*\n\*\*\*\n\*\*\*\n\n\n\*\*\*\n\*\*\* \n\n", '', text, flags=re.MULTILINE) - text = re.sub("\*\*\*\n\*\*\*\n\*\*\*\n\n\n\*\*\*\n\*\*\*\n\n", '', text, flags=re.MULTILINE) - text = re.sub("\*\*\*\n\*\*\*\n\*\*\*\n\*\*\*\n\n\n\*\*\*\n\n", '', text, flags=re.MULTILINE) - text = re.sub("\*\*\*\n\*\*\*\n\n\n\*\*\*\n\n", '', text, flags=re.MULTILINE) - return text - -def replace_domain_and_page(text): - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/ai', 'fmhy.pages.dev/ai', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/adblock-vpn-privacy', 'fmhy.pages.dev/adblockvpnguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/android', 'fmhy.pages.dev/android-iosguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/video', 'fmhy.pages.dev/videopiracyguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/audio', 'fmhy.pages.dev/audiopiracyguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/games', 'fmhy.pages.dev/gamingpiracyguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/reading', 'fmhy.pages.dev/readingpiracyguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/download', 'fmhy.pages.dev/downloadpiracyguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/torrent', 'fmhy.pages.dev/torrentpiracyguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/edu', 'fmhy.pages.dev/edupiracyguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/tools-misc', 'fmhy.pages.dev/toolsguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/linux', 'fmhy.pages.dev/linuxguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/non-eng', 'fmhy.pages.dev/non-english', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/misc', 'fmhy.pages.dev/miscguide', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/storage', 'fmhy.pages.dev/storage', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/dev-tools', 'fmhy.pages.dev/devtools', text) - text = re.sub('www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/img-tools', 'fmhy.pages.dev/img-tools', text) - - text = re.sub('github.com/nbats/FMHYedit/blob/main/base64.md#', 'fmhy.pages.dev/base64/#', text) - - return text - -def replace_underscore_in_subsections(text): - pattern = r'(/#[\w\-]+(?:_[\w]+)*)' - matches = re.findall(pattern, text) - for match in matches: - replacement = match.replace('_', '-') - text = text.replace(match, replacement) - return text - -def reformat_subsections(text): - text = re.sub('/#wiki_', '/#', text) - text = re.sub('#wiki_', '/#', text) - text = re.sub('.25BA_', '', text) - text = re.sub('.25B7_', '', text) - text = re.sub('_.2F_', '--', text) - text = replace_underscore_in_subsections(text) - return text - -def replace_urls_in_links_to_FMHY_wiki(text): - text = remove_backtowiki_and_toc(text) - text = replace_domain_and_page(text) - text = reformat_subsections(text) - text = change_some_general_formatting(text) - text = do_some_individual_replaces(text) - return text - -def apply_replace_to_all_md_files_in_current_dir(): - files = os.listdir('.') - for file in files: - if file.endswith('.md'): - with open(file, 'r', encoding='utf-8') as f: - content = f.read() - content = replace_urls_in_links_to_FMHY_wiki(content) - if file == "Beginners-Guide.md": - content = replaces_for_beginners_guide(content) - with open(file, 'w', encoding='utf-8') as f2: - f2.write(content) - -def print_info_for_confirmation(): - print("This script is about to replace URLs in all .md files in the current directory: " + os.getcwd()) - print("The affected files will be the following:") - files = os.listdir('.') - for file in files: - if file.endswith('.md'): - print(file) - - - - -# TESTER -testText = """ -aaaaaaaa (https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/storage#wiki_telegram_audio_download) aaaaaaa -**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)** -* ⭐ **[YouTube Music Clients](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/storage#wiki_youtube_music_players)** -Soundtracks](https://github.com/nbats/FMHYedit/blob/main/base64.md#damons-game-soundtracks)**, [Squ -(https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/storage#wiki_game_libraries_.2F_launcher) -(https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/games#wiki_.25BA_tracking_.2F_discovery) -(https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/linux#wiki_.25BA_linux_adblock_.2F_privacy) -(https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/video#wiki_.25BA_download_sites) -[sdfasdf](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/android#wiki_.25B7_android_podcasts_.2F_radio)gwrgewrgew -(https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/android#wiki_.25B7_android_relaxation) -adfads awerfaw (https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/storage#wiki_music_libraries_.2F_players) -(https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/edu/#wiki_.25BA_downloading) aaaaaaaaa -(https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/adblock-vpn-privacy#wiki_.25B7_adblocking_extensions) -* [link](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/torrent) - ...sdvs -https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/img-tools#wiki_.25B7_painting_.2F_drawing -* ⭐ **[AI Indexes](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/ai#wiki_.25BA_ai_indexes)** - Artificial Intelligence Indexes -(https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/adblock-vpn-privacy#wiki_.25BA_vpn) -https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/storage/#wiki_open_directory_search_string_builder -https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/tools-misc#wiki_.25B7_file_tools - -~~~~~~ -*** -*** -**[◄◄ Back to Wiki Index](https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/index)** -*** -*** - -**[Table of Contents](https://i.imgur.com/whYmImm.png)** - For mobile users - -*** -*** - -~~~~~~ - -argaseg ae this is legit test between stuff to remove1 - -*** - -# ► with ## -## ▷ with ### -#### with ### - -* **Note** - Some aggregators still include TPB, so it's best to avoid using them for software and games. - -**Warning** - Misuse of Chat Archivers, Deleters, Mods & 3rd Party Clients is against Discords TOS, so use them at your own risk. - -**[movie-web](https://movie-web.app/) / [FMovies](https://fmovies.name/) / [Soap2Day](https://soapgate.org/)** - Fast streaming -**[Zoro](https://zoro.to/) / [9Anime](https://www.9anime.to/)** - Fast anime streaming - - -""" -def just_test_the_replacer_function(): - print("---TEST---") - print("This is how the resulting edited links would look like:") - print( replace_urls_in_links_to_FMHY_wiki(testText) ) - print("---END OF TEST---\n\n\n") - -just_test_the_replacer_function() - - - - -# MAIN EXECUTION -print("---MAIN SCRIPT---") -print_info_for_confirmation() -apply_replace_to_all_md_files_in_current_dir() -print("---END OF MAIN SCRIPT---") diff --git a/.github/replace.py b/.github/replace.py new file mode 100644 index 000000000..12e97160d --- /dev/null +++ b/.github/replace.py @@ -0,0 +1,116 @@ +import re +import os +import sys + +def replaces_for_beginners_guide(text): + text = re.sub('\[TOC\]\n', '', text, flags=re.MULTILINE) + text = re.sub('\*\*Table of Contents\*\*\n\[TOC2\]\n', '', text, flags=re.MULTILINE) + text = re.sub('# -> \*\*\*Beginners Guide to Piracy\*\*\* <-\n', '', text, flags=re.MULTILINE) + text = re.sub(r"!!!note\s(.+?)\n", r":::info\n\1\n:::\n", text, flags=re.MULTILINE) + text = re.sub(r"!!!info\s(.+?)\n", r":::info\n\1\n:::\n", text, flags=re.MULTILINE) + text = re.sub(r"!!!warning\s(.+?)\n", r":::warning\n\1\n:::\n", text, flags=re.MULTILINE) + text = re.sub(r">\s(.+?)\n", r"> \1\n\n", text, flags=re.MULTILINE) + text = re.sub('\*\*\[\^ Back to Top\]\(#beginners-guide-to-piracy\)\*\*', '', text, flags=re.MULTILINE) + text = re.sub(r"!!!\s(.+?)\n", r":::info\n\1\n:::\n", text, flags=re.MULTILINE) + text = re.sub("\n\*\*\[", "\n* **[", text, flags=re.MULTILINE) + text = re.sub(r'>(.*)\n\n(.*)', r':::details \1\n\2\n:::', text, flags=re.MULTILINE) + return text + +def do_some_individual_replaces(text): + #special cases of link not replaced correctly + text = re.sub('/storage/#encode--decode_urls', '/storage/#encode--decode-urls', text) + text = re.sub('/base64/#do-k-ument', '/base64/#do_k_ument', text) + text = re.sub('/devtools/#machine-learning2', '/devtools/#machine-learning-1', text) + + #Base64-decoder script link + text = re.sub('(.+?) site or extension\.\n', 'Click on the texts to copy them decoded.\n', text, flags=re.MULTILINE) + + return text + +def change_some_general_formatting(text): + text = re.sub('\*\*\*\n\n', '', text, flags=re.MULTILINE) + text = re.sub('\*\*\*\n', '', text, flags=re.MULTILINE) + + text = re.sub('# ►', '##', text) + text = re.sub('## ▷', '###', text) + text = re.sub('####', '###', text) + + text = re.sub(r'^\*\*Note\*\* - (.+)$', r':::tip\n\1\n:::', text, flags=re.MULTILINE) + text = re.sub(r'^\* \*\*Note\*\* - (.+)$', r':::tip\n\1\n:::', text, flags=re.MULTILINE) + text = re.sub(r'^Note - (.+)$', r':::tip\n\1\n:::', text, flags=re.MULTILINE) + text = re.sub(r'^\*\*Warning\*\* - (.+)$', r':::warning\n\1\n:::', text, flags=re.MULTILINE) + + text = re.sub(r'^\*\s([^*])', "- \\1", text, 0, re.MULTILINE) + return text + +def remove_backtowiki_and_toc(text): + text = re.sub('\*\*\[◄◄ Back to Wiki Index\]\(https://www\.reddit\.com/r/FREEMEDIAHECKYEAH/wiki/index\)\*\*\n', '', text, flags=re.MULTILINE) + text = re.sub(r'\*\*\[Table of Contents\]\(https?:\/\/.*?ibb\.co.*\)\*\* - For mobile users\n', '', text, flags=re.MULTILINE) + text = re.sub("\*\*\*\n\*\*\*\n\*\*\*\n\*\*\*\n\n\n\*\*\*\n\*\*\*\n\n", '', text, flags=re.MULTILINE) + text = re.sub("\*\*\*\n\*\*\*\n\*\*\*\n\*\*\*\n\n\n\*\*\*\n\*\*\* \n\n", '', text, flags=re.MULTILINE) + text = re.sub("\*\*\*\n\*\*\*\n\*\*\*\n\n\n\*\*\*\n\*\*\*\n\n", '', text, flags=re.MULTILINE) + text = re.sub("\*\*\*\n\*\*\*\n\*\*\*\n\*\*\*\n\n\n\*\*\*\n\n", '', text, flags=re.MULTILINE) + text = re.sub("\*\*\*\n\*\*\*\n\n\n\*\*\*\n\n", '', text, flags=re.MULTILINE) + return text + +def replace_domain_and_page(text): + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/ai', '/ai', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/adblock-vpn-privacy', '/adblockvpnguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/android', '/android-iosguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/video', '/videopiracyguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/audio', '/audiopiracyguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/games', '/gamingpiracyguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/reading', '/readingpiracyguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/download', '/downloadpiracyguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/torrent', '/torrentpiracyguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/edu', '/edupiracyguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/tools-misc', '/toolsguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/linux', '/linuxguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/non-eng', '/non-english', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/misc', '/miscguide', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/storage', '/storage', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/dev-tools', '/devtools', text) + text = re.sub('https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/img-tools', '/img-tools', text) + + text = re.sub('https://github.com/nbats/FMHYedit/blob/main/base64.md#', '/base64/#', text) + + return text + +def replace_underscore_in_subsections(text): + pattern = r'(/#[\w\-]+(?:_[\w]+)*)' + matches = re.findall(pattern, text) + for match in matches: + replacement = match.replace('_', '-') + text = text.replace(match, replacement) + return text + +def reformat_subsections(text): + text = re.sub('/#wiki_', '/#', text) + text = re.sub('#wiki_', '/#', text) + text = re.sub('.25BA_', '', text) + text = re.sub('.25B7_', '', text) + text = re.sub('_.2F_', '--', text) + text = replace_underscore_in_subsections(text) + return text + +def replace_urls_in_links_to_FMHY_wiki(text): + text = remove_backtowiki_and_toc(text) + text = replace_domain_and_page(text) + text = reformat_subsections(text) + text = change_some_general_formatting(text) + text = do_some_individual_replaces(text) + return text + +def apply_replace_to_all_md_files_in_current_dir(): + files = os.listdir('.') + for file in files: + if file.endswith('.md'): + with open(file, 'r', encoding='utf-8') as f: + content = f.read() + content = replace_urls_in_links_to_FMHY_wiki(content) + if file == "Beginners-Guide.md": + content = replaces_for_beginners_guide(content) + with open(file, 'w', encoding='utf-8') as f2: + f2.write(content) + +apply_replace_to_all_md_files_in_current_dir() diff --git a/.github/script.sh b/.github/script.sh new file mode 100644 index 000000000..64589dbbf --- /dev/null +++ b/.github/script.sh @@ -0,0 +1,7 @@ +python .github/add-headers.py +python .github/replace.py + +for file in *; do + [[ -f "$file" ]] && mv "$file" "${file,,}" 2>/dev/null +done +exit 0 diff --git a/.github/workflows/deploy-api.yml b/.github/workflows/deploy-api.yml new file mode 100644 index 000000000..22b2496e9 --- /dev/null +++ b/.github/workflows/deploy-api.yml @@ -0,0 +1,43 @@ +name: Deploy API + +on: + push: + branches: + - main + - vitepress + pull_request: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + ci: + name: Release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 6 + - uses: pnpm/action-setup@v2.4.0 + + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "pnpm" + + - run: pnpm install --no-frozen-lockfile + + - name: Build + run: pnpm api:build + env: + WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }} + NITRO_PRESET: cloudflare + + - name: Publish to Cloudflare + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + vars: WEBHOOK_URL + env: + WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }} diff --git a/.github/workflows/retype.yml b/.github/workflows/retype.yml deleted file mode 100644 index 966fa8366..000000000 --- a/.github/workflows/retype.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Update FMHY Retype -on: - workflow_dispatch: - schedule: - - cron: '0 */2 * * *' - -jobs: - update: - name: Update FMHY Retype - runs-on: ubuntu-latest - - permissions: - contents: write - - steps: - - uses: actions/checkout@v2 - - - name: Move Files - run: | - cp -r .github/assets/* . - shell: bash - - - name: Install Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - - name: Prepare Files - shell: bash - run: | - python .github/replace-links-to-fmhy-in-files-in-current-dir.py - python .github/add-headers.py - - - name: Build Retype - uses: retypeapp/action-build@latest - - - uses: retypeapp/action-github-pages@latest - with: - branch: retype - update-branch: true diff --git a/.github/workflows/single-page.yml b/.github/workflows/single-page.yml index 846149240..f6652f3f6 100644 --- a/.github/workflows/single-page.yml +++ b/.github/workflows/single-page.yml @@ -3,7 +3,7 @@ name: Update Single Page on: workflow_dispatch: schedule: - - cron: '0 */2 * * *' + - cron: "0 */2 * * *" jobs: update: @@ -19,7 +19,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: "3.10" - name: Configure Git run: | diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..b94471b1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +**/.vitepress/dist +**/.vitepress/cache +node_modules +*.log* +.nitro +.cache +.output +.env +dist diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..cf0404245 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=true +strict-peer-dependencies=false diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..18f1a04b0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +**/*.md +pnpm-lock.yaml diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..62e685119 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,14 @@ +{ + "semi": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "singleQuote": false, + "quoteProps": "as-needed", + "jsxSingleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "bracketSameLine": true, + "arrowParens": "always", + "proseWrap": "always" +} diff --git a/.vitepress/config.mts b/.vitepress/config.mts new file mode 100644 index 000000000..b3f7d8058 --- /dev/null +++ b/.vitepress/config.mts @@ -0,0 +1,135 @@ +import { defineConfig } from "vitepress"; +import { withPwa } from "@vite-pwa/vitepress"; +import UnoCSS from "unocss/vite"; +import { presetUno, presetAttributify, presetIcons } from "unocss"; +import { commitRef, meta } from "./constants"; +import { pwa } from "./pwa"; +import { fileURLToPath } from "url"; +import { generateImages, generateMeta } from "./hooks"; +import { toggleStarredPlugin } from "./markdown/toggleStarred"; +import { base64DecodePlugin } from "./markdown/base64"; + +export default withPwa( + defineConfig({ + title: "FMHY", + description: meta.description, + titleTemplate: ":title • freemediaheckyeah", + lang: "en-US", + lastUpdated: true, + cleanUrls: true, + appearance: "dark", + srcExclude: ["readme.md", "single-page", "toolsguide.md"], + ignoreDeadLinks: true, + metaChunk: true, + sitemap: { + hostname: meta.hostname, + }, + head: [ + ["meta", { name: "theme-color", content: "#7bc5e4" }], + ["meta", { name: "og:type", content: "website" }], + ["meta", { name: "og:locale", content: "en" }], + ["link", { rel: "icon", href: "/test.png" }], + // PWA + ["link", { rel: "icon", href: "/test.png", type: "image/svg+xml" }], + ["link", { rel: "alternate icon", href: "/test.png" }], + ["link", { rel: "mask-icon", href: "/test.png", color: "#7bc5e4" }], + // prettier-ignore + ["meta", { name: "keywords", content: meta.keywords.join(" ") }], + ["link", { rel: "apple-touch-icon", href: "/test.png", sizes: "192x192" }], + ], + transformHead: async (context) => generateMeta(context, meta.hostname), + buildEnd: async (context) => { + generateImages(context); + }, + vite: { + plugins: [ + UnoCSS({ + presets: [ + presetUno(), + presetAttributify(), + presetIcons({ + scale: 1.2, + extraProperties: { + display: "inline-block", + "vertical-align": "middle", + }, + }), + ], + }), + ], + build: { + // Shut the fuck up + chunkSizeWarningLimit: Infinity, + }, + resolve: { + alias: [ + { + find: /^.*VPSwitchAppearance\.vue$/, + replacement: fileURLToPath( + new URL("./theme/components/ThemeSwitch.vue", import.meta.url), + ), + }, + ], + }, + }, + markdown: { + config(md) { + md.use(toggleStarredPlugin); + md.use(base64DecodePlugin); + }, + }, + themeConfig: { + search: { + options: { + detailedView: true, + }, + provider: "local", + }, + footer: { + message: `Made with ❤️ (rev: ${commitRef})`, + }, + outline: "deep", + logo: "/fmhy.ico", + nav: [ + { text: "Beginners Guide", link: "/beginners-guide" }, + { text: "Glossary", link: "https://rentry.org/The-Piracy-Glossary" }, + { text: "Guides", link: "https://rentry.co/fmhy-guides" }, + { text: "Backups", link: "https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/backups" }, + { text: "Updates", link: "https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/updates/" }, + ], + sidebar: [ + { text: "📛 Adblocking / Privacy", link: "/adblockvpnguide" }, + { text: "🤖 Artificial Intelligence", link: "/ai" }, + { text: "📺 Movies / TV / Anime", link: "/videopiracyguide" }, + { text: "🎵 Music / Podcasts / Radio", link: "/audiopiracyguide" }, + { text: "🎮 Gaming / Emulation", link: "/gamingpiracyguide" }, + { text: "📗 Books / Comics / Manga", link: "/readingpiracyguide" }, + { text: "💾 Downloading", link: "/downloadpiracyguide" }, + { text: "🌀 Torrenting", link: "/torrentpiracyguide" }, + { text: "🧠 Educational", link: "/edupiracyguide" }, + { text: "🔧 Tools", link: "/toolsguide" }, + { text: "📷 Image Tools", link: "/img-tools" }, + { text: "👨‍💻 Developer Tools", link: "/devtools" }, + { text: "📱 Android / iOS", link: "/android-iosguide" }, + { text: "🐧 Linux / MacOS", link: "/linuxguide" }, + { text: "🌍 Non-English", link: "/non-english" }, + { text: "📂 Miscellaneous", link: "/miscguide" }, + { text: "🔞 NSFW", link: "/nsfwpiracy" }, + { text: "⚠️ Unsafe Sites", link: "/unsafesites" }, + { text: "🔑 Base64", link: "/base64" }, + { text: "📦 Storage", link: "/storage" }, + ], + socialLinks: [ + { icon: "github", link: "https://github.com/fmhy/FMHYEdit" }, + { icon: "discord", link: "https://discord.gg/Stz6y6NgNg" }, + { + icon: { + svg: '', + }, + link: "https://reddit.com/r/FREEMEDIAHECKYEAH", + }, + ], + ...pwa, + }, + }), +); diff --git a/.vitepress/constants.ts b/.vitepress/constants.ts new file mode 100644 index 000000000..5282db678 --- /dev/null +++ b/.vitepress/constants.ts @@ -0,0 +1,16 @@ +export const meta = { + name: "FreeMediaHeckYeah", + description: + "The Largest Collection Of Free Stuff On The Internet!The Largest Collection Of Free Stuff On The Internet!", + hostname: process.env.COMMIT_REF ? "https://fmhy.netlify.app" : "https://fmhy.pages.dev", + keywords: ["stream", "movies", "gaming", "reading", "anime"], +}; + +// Netlify to Cloudflare otherwise dev +export const commitRef = process.env.COMMIT_REF + ? `${process.env.COMMIT_REF.slice(0, 8)}` + : process.env.CF_PAGES_COMMIT_SHA + ? `${process.env.CF_PAGES_COMMIT_SHA.slice(0, 8)}` + : "dev"; diff --git a/.vitepress/fonts/Inter-Bold.otf b/.vitepress/fonts/Inter-Bold.otf new file mode 100644 index 000000000..c74cc0c6c Binary files /dev/null and b/.vitepress/fonts/Inter-Bold.otf differ diff --git a/.vitepress/fonts/Inter-Medium.otf b/.vitepress/fonts/Inter-Medium.otf new file mode 100644 index 000000000..ca7bfcd43 Binary files /dev/null and b/.vitepress/fonts/Inter-Medium.otf differ diff --git a/.vitepress/fonts/Inter-Regular.otf b/.vitepress/fonts/Inter-Regular.otf new file mode 100644 index 000000000..84e6a61c3 Binary files /dev/null and b/.vitepress/fonts/Inter-Regular.otf differ diff --git a/.vitepress/fonts/Inter-SemiBold.otf b/.vitepress/fonts/Inter-SemiBold.otf new file mode 100644 index 000000000..daf4c4413 Binary files /dev/null and b/.vitepress/fonts/Inter-SemiBold.otf differ diff --git a/.vitepress/hooks/Template.vue b/.vitepress/hooks/Template.vue new file mode 100644 index 000000000..1974895d7 --- /dev/null +++ b/.vitepress/hooks/Template.vue @@ -0,0 +1,22 @@ + + + diff --git a/.vitepress/hooks/index.ts b/.vitepress/hooks/index.ts new file mode 100644 index 000000000..c107def90 --- /dev/null +++ b/.vitepress/hooks/index.ts @@ -0,0 +1,6 @@ +/** + * Barrel generated using @taskylizard/tasker. + */ + +export * from "./meta"; +export * from "./opengraph"; diff --git a/.vitepress/hooks/meta.ts b/.vitepress/hooks/meta.ts new file mode 100644 index 000000000..83353c23a --- /dev/null +++ b/.vitepress/hooks/meta.ts @@ -0,0 +1,84 @@ +import type { HeadConfig, TransformContext } from "vitepress"; + +export function generateMeta(context: TransformContext, hostname: string) { + const head: HeadConfig[] = []; + const { pageData } = context; + + const url = `${hostname}/${pageData.relativePath.replace(/((^|\/)index)?\.md$/, "$2")}`; + + head.push(["link", { rel: "canonical", href: url }]); + head.push(["meta", { property: "og:url", content: url }]); + head.push(["meta", { name: "twitter:url", content: url }]); + head.push(["meta", { name: "twitter:card", content: "summary_large_image" }]); + + head.push(["meta", { property: "og:title", content: pageData.frontmatter.title }]); + head.push(["meta", { name: "twitter:title", content: pageData.frontmatter.title }]); + + head.push([ + "meta", + { + property: "og:description", + content: pageData.frontmatter.description, + }, + ]); + head.push([ + "meta", + { + name: "twitter:description", + content: pageData.frontmatter.description, + }, + ]); + + if (pageData.frontmatter.image) { + head.push([ + "meta", + { + property: "og:image", + content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`, + }, + ]); + head.push([ + "meta", + { + name: "twitter:image", + content: `${hostname}/${pageData.frontmatter.image.replace(/^\//, "")}`, + }, + ]); + } else { + const url = pageData.filePath.replace("index.md", "").replace(".md", ""); + const imageUrl = `${url}/__og_image__/og.png`.replace(/\/\//g, "/").replace(/^\//, ""); + + head.push(["meta", { property: "og:image", content: `${hostname}/${imageUrl}` }]); + head.push(["meta", { property: "og:image:width", content: "1200" }]); + head.push(["meta", { property: "og:image:height", content: "628" }]); + head.push(["meta", { property: "og:image:type", content: "image/png" }]); + head.push(["meta", { property: "og:image:alt", content: pageData.frontmatter.title }]); + head.push(["meta", { name: "twitter:image", content: `${hostname}/${imageUrl}` }]); + head.push(["meta", { name: "twitter:image:width", content: "1200" }]); + head.push(["meta", { name: "twitter:image:height", content: "628" }]); + head.push(["meta", { name: "twitter:image:alt", content: pageData.frontmatter.title }]); + } + if (pageData.frontmatter.tag) { + head.push(["meta", { property: "article:tag", content: pageData.frontmatter.tag }]); + } + if (pageData.frontmatter.date) { + head.push([ + "meta", + { + property: "article:published_time", + content: pageData.frontmatter.date, + }, + ]); + } + if (pageData.lastUpdated && pageData.frontmatter.lastUpdated !== false) { + head.push([ + "meta", + { + property: "article:modified_time", + content: new Date(pageData.lastUpdated).toISOString(), + }, + ]); + } + + return head; +} diff --git a/.vitepress/hooks/opengraph.ts b/.vitepress/hooks/opengraph.ts new file mode 100644 index 000000000..719fd5093 --- /dev/null +++ b/.vitepress/hooks/opengraph.ts @@ -0,0 +1,89 @@ +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { createContentLoader } from "vitepress"; +import type { ContentData, SiteConfig } from "vitepress"; +import { type SatoriOptions, satoriVue } from "x-satori/vue"; +import { renderAsync } from "@resvg/resvg-js"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const __fonts = resolve(__dirname, "../fonts"); + +export async function generateImages(config: SiteConfig) { + const pages = await createContentLoader("**/*.md", { excerpt: true }).load(); + const template = await readFile(resolve(__dirname, "./Template.vue"), "utf-8"); + + const fonts: SatoriOptions["fonts"] = [ + { + name: "Inter", + data: await readFile(resolve(__fonts, "Inter-Regular.otf")), + weight: 400, + style: "normal", + }, + { + name: "Inter", + data: await readFile(resolve(__fonts, "Inter-Medium.otf")), + weight: 500, + style: "normal", + }, + { + name: "Inter", + data: await readFile(resolve(__fonts, "Inter-SemiBold.otf")), + weight: 600, + style: "normal", + }, + { + name: "Inter", + data: await readFile(resolve(__fonts, "Inter-Bold.otf")), + weight: 700, + style: "normal", + }, + ]; + + for (const page of pages) { + await generateImage({ + page, + template, + outDir: config.outDir, + fonts, + }); + } +} + +interface GenerateImagesOptions { + page: ContentData; + template: string; + outDir: string; + fonts: SatoriOptions["fonts"]; +} + +async function generateImage({ page, template, outDir, fonts }: GenerateImagesOptions) { + const { frontmatter, url } = page; + + const options: SatoriOptions = { + width: 1200, + height: 628, + fonts, + props: { + title: + frontmatter.layout === "home" + ? frontmatter.hero.name ?? frontmatter.title + : frontmatter.title, + description: + frontmatter.layout === "home" + ? frontmatter.hero.tagline ?? frontmatter.description + : frontmatter.description, + }, + }; + + const svg = await satoriVue(options, template); + + const render = await renderAsync(svg); + + const outputFolder = resolve(outDir, url.substring(1), "__og_image__"); + const outputFile = resolve(outputFolder, "og.png"); + + await mkdir(outputFolder, { recursive: true }); + + return await writeFile(outputFile, render.asPng()); +} diff --git a/.vitepress/hooks/satoriConfig.ts b/.vitepress/hooks/satoriConfig.ts new file mode 100644 index 000000000..9a3061299 --- /dev/null +++ b/.vitepress/hooks/satoriConfig.ts @@ -0,0 +1,45 @@ +import { readFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { SatoriOptions, defineSatoriConfig } from "x-satori/vue"; +const __dirname = dirname(fileURLToPath(import.meta.url)); +const __fonts = resolve(__dirname, "../fonts"); + +const fonts: SatoriOptions["fonts"] = [ + { + name: "Inter", + data: await readFile(resolve(__fonts, "Inter-Regular.otf")), + weight: 400, + style: "normal", + }, + { + name: "Inter", + data: await readFile(resolve(__fonts, "Inter-Medium.otf")), + weight: 500, + style: "normal", + }, + { + name: "Inter", + data: await readFile(resolve(__fonts, "Inter-SemiBold.otf")), + weight: 600, + style: "normal", + }, + { + name: "Inter", + data: await readFile(resolve(__fonts, "Inter-Bold.otf")), + weight: 700, + style: "normal", + }, +]; + +export default defineSatoriConfig({ + width: 1200, + height: 628, + fonts, + props: { + title: "Title", + description: + "Lorem ipsum dolor sit amet, qui minim labore adipisicing minim sint cillum sint consectetur cupidatat.", + dir: "/j", + }, +}); diff --git a/.vitepress/loaders/guides.data.ts b/.vitepress/loaders/guides.data.ts new file mode 100644 index 000000000..0c60e45e4 --- /dev/null +++ b/.vitepress/loaders/guides.data.ts @@ -0,0 +1,46 @@ +import { defineLoader } from "vitepress"; +import { writeFile, readFile } from "fs/promises"; + +interface Data { + title?: string; + content?: string; + url?: string; +} +declare const data: Data; +export { data }; + +const page = "https://rentry.co/fmhy-guides/raw"; +const regex = /\* \[([^\]]+)\]\(([^)]+)\)/g; +const rentryRe = /(?<=rentry\.(co|org)).*/; +const guides = new Set(); + +const f = async (url: string) => { + const contents = await (await fetch(url)) + .text() + .catch((error: Error) => console.error(`Failed at ${url}`, error)); + return contents; +}; + +export default defineLoader({ + async load(): Promise { + const contents = await f(page); + let match: any[] | null; + while ((match = regex.exec(contents)) !== null) { + const title = match[1]; + const url = match[2]; + // Fetch rentry guides + if (url.match(rentryRe)) { + const content = await f(url + "/raw"); + guides.add({ title, content }); + } else { + // Everything else can be here + guides.add({ title, url }); + } + } + const obj = Object.fromEntries( + [...guides.entries()].map((entry, index) => [index.toString(), entry]), + ); + await writeFile("./guides.json", JSON.stringify(obj, null, 4), "utf-8"); + return JSON.parse(await readFile("./guides.json", { encoding: "utf-8" })) as Data; + }, +}); diff --git a/.vitepress/markdown/base64.ts b/.vitepress/markdown/base64.ts new file mode 100644 index 000000000..f85b59b02 --- /dev/null +++ b/.vitepress/markdown/base64.ts @@ -0,0 +1,25 @@ +import { type MarkdownRenderer } from "vitepress"; + +// FIXME: tasky: possibly write less horror jank? +export function base64DecodePlugin(md: MarkdownRenderer) { + const decode = (str: string): string => Buffer.from(str, "base64").toString("binary"); + // Save the original rule for backticks + const defaultRender = + md.renderer.rules.code_inline || + function (tokens, idx, options, env, self) { + return self.renderToken(tokens, idx, options); + }; + + md.renderer.rules.code_inline = function (tokens, idx, options, env, self) { + // @ts-expect-error shut the fuck up already I HATE THIS + if (!env.frontmatter.title || (env.frontmatter.title && !env.frontmatter.title === "base64")) { + return defaultRender(tokens, idx, options, env, self); + } + const token = tokens[idx]; + const content = token.content; + + return ``; + }; +} diff --git a/.vitepress/markdown/toggleStarred.ts b/.vitepress/markdown/toggleStarred.ts new file mode 100644 index 000000000..37024e70d --- /dev/null +++ b/.vitepress/markdown/toggleStarred.ts @@ -0,0 +1,17 @@ +import type { MarkdownRenderer } from "vitepress"; + +const excluded = ["Beginners Guide"]; + +export function toggleStarredPlugin(md: MarkdownRenderer) { + md.renderer.rules.list_item_open = (tokens, index, options, env, self) => { + const contentToken = tokens[index + 2]; + if ( + !excluded.includes(env.frontmatter.title) && + contentToken && + contentToken.content.startsWith("⭐") + ) { + return `
  • `; + } + return self.renderToken(tokens, index, options); + }; +} diff --git a/.vitepress/middleware/cors.ts b/.vitepress/middleware/cors.ts new file mode 100644 index 000000000..6e74e76e9 --- /dev/null +++ b/.vitepress/middleware/cors.ts @@ -0,0 +1,6 @@ +import { corsEventHandler } from "nitro-cors"; + +export default corsEventHandler((_event) => {}, { + origin: "*", + methods: "*", +}); diff --git a/.vitepress/pwa.ts b/.vitepress/pwa.ts new file mode 100644 index 000000000..fe786ea65 --- /dev/null +++ b/.vitepress/pwa.ts @@ -0,0 +1,102 @@ +import type { PwaOptions } from "@vite-pwa/vitepress"; +import { meta } from "./constants"; +import { resolve } from "pathe"; +import fg from "fast-glob"; + +export const pwa = { + outDir: ".vitepress/dist", + registerType: "autoUpdate", + includeManifestIcons: false, + includeAssets: fg.sync("**/*.{png,webp,svg,gif,ico,txt}", { + cwd: resolve(__dirname, "../public"), + }), + + manifest: { + id: "/", + name: meta.name, + short_name: meta.name, + description: meta.description, + theme_color: "#ffffff", + start_url: "/", + lang: "en-US", + dir: "ltr", + orientation: "natural", + display: "standalone", + display_override: ["window-controls-overlay"], + categories: meta.keywords, + // TODO: replace with actual icons + icons: [ + { + src: "test.png", + sizes: "64x64", + type: "image/png", + }, + { + src: "test.png", + sizes: "192x192", + type: "image/png", + }, + { + src: "test.png", + sizes: "512x512", + type: "image/png", + purpose: "any", + }, + { + src: "maskable-icon.png", + sizes: "512x512", + type: "image/png", + purpose: "maskable", + }, + ], + handle_links: "preferred", + launch_handler: { + client_mode: ["navigate-existing", "auto"], + }, + edge_side_panel: { + preferred_width: 480, + }, + }, + experimental: { + includeAllowlist: true, + }, + workbox: { + globPatterns: ["**/*.{css,js,html,svg,png,ico,txt,woff2,json}"], + globIgnores: ["**/404.html"], + navigateFallback: null, + runtimeCaching: [ + { + urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i, + handler: "CacheFirst", + options: { + cacheName: "google-fonts-cache", + expiration: { + maxEntries: 10, + maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days + }, + cacheableResponse: { + statuses: [0, 200], + }, + }, + }, + { + urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i, + handler: "CacheFirst", + options: { + cacheName: "gstatic-fonts-cache", + expiration: { + maxEntries: 10, + maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days + }, + cacheableResponse: { + statuses: [0, 200], + }, + }, + }, + ], + }, + devOptions: { + enabled: true, + suppressWarnings: false, + }, +} satisfies PwaOptions; diff --git a/.vitepress/routes/index.post.ts b/.vitepress/routes/index.post.ts new file mode 100644 index 000000000..27515c312 --- /dev/null +++ b/.vitepress/routes/index.post.ts @@ -0,0 +1,49 @@ +import { fetcher } from "itty-fetcher"; +import { FeedbackSchema } from "../types/Feedback"; + +const feedbackOptions = [ + { label: "🐞 Bug", value: "bug" }, + { + label: "♻️ Suggestion", + value: "suggestion", + }, + { label: "📂 Other", value: "other" }, + { + label: "❤️ Appreciation", + value: "appreciate", + }, +]; + +function getFeedbackOption(value: string) { + return feedbackOptions.find((option) => option.value === value); +} + +export default defineEventHandler(async (event) => { + const { message, page, contact, type } = await readValidatedBody(event, FeedbackSchema.parse); + const env = useRuntimeConfig(event); + + if (!["bug", "suggestion", "other", "appreciate"].includes(type!) || !message) + throw new Error("Invalid input."); + + let description = `${message}\n\n`; + if (contact) description += `**Contact:** ${contact}`; + if (page) description += `**Page:** \`${page}\``; + + await fetcher() + .post(env.WEBHOOK_URL, { + username: "Feedback", + avatar_url: "https://i.kym-cdn.com/entries/icons/facebook/000/043/403/cover3.jpg", + embeds: [ + { + color: 3447003, + title: getFeedbackOption(type).label, + description: description, + }, + ], + }) + .catch((error) => { + throw new Error(error); + }); + + return { status: "ok" }; +}); diff --git a/.vitepress/routes/test.ts b/.vitepress/routes/test.ts new file mode 100644 index 000000000..a6fcdc197 --- /dev/null +++ b/.vitepress/routes/test.ts @@ -0,0 +1,3 @@ +export default eventHandler(() => { + return { nitro: "works" }; +}); diff --git a/.vitepress/theme/Layout.vue b/.vitepress/theme/Layout.vue new file mode 100644 index 000000000..2de3a90d1 --- /dev/null +++ b/.vitepress/theme/Layout.vue @@ -0,0 +1,19 @@ + + + diff --git a/.vitepress/theme/components/Announcement.vue b/.vitepress/theme/components/Announcement.vue new file mode 100644 index 000000000..bbea99494 --- /dev/null +++ b/.vitepress/theme/components/Announcement.vue @@ -0,0 +1,15 @@ + + + diff --git a/.vitepress/theme/components/CardField.vue b/.vitepress/theme/components/CardField.vue new file mode 100644 index 000000000..ae5511400 --- /dev/null +++ b/.vitepress/theme/components/CardField.vue @@ -0,0 +1,16 @@ + + + diff --git a/.vitepress/theme/components/Feedback.vue b/.vitepress/theme/components/Feedback.vue new file mode 100644 index 000000000..b0a2e5f95 --- /dev/null +++ b/.vitepress/theme/components/Feedback.vue @@ -0,0 +1,199 @@ + + +