├── .github └── workflows │ └── run_make_fmhy_bookmarks.yml ├── README.md ├── fmhy_in_bookmarks.html ├── fmhy_in_bookmarks_starred_only.html ├── make_fmhy_bookmarks.py └── requirements.txt /.github/workflows/run_make_fmhy_bookmarks.yml: -------------------------------------------------------------------------------- 1 | name: Run make_fmhy_bookmarks.py 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * 1' # This means the action will run every Monday at midnight UTC 6 | workflow_dispatch: # Allows the workflow to be run manually from the GitHub UI 7 | 8 | jobs: 9 | run_script: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up Python 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: '3.x' # Specify the Python version you need 20 | 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 25 | 26 | - name: Run make_fmhy_bookmarks.py 27 | run: python make_fmhy_bookmarks.py 28 | 29 | - name: Commit changes 30 | run: | 31 | git config --global user.name 'github-actions[bot]' 32 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 33 | git add fmhy_in_bookmarks.html 34 | git add fmhy_in_bookmarks_starred_only.html 35 | git commit -m 'Update fmhy_in_bookmarks.html' 36 | git push 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Archiving this one. Use https://github.com/fmhy/bookmarks instead.** 2 | _______________________________________________________________________________ 3 | 4 | This repository is programmed to automatically generate browser bookmarks for the link collection FMHY. 5 | 6 | Bookmarks are generated as HTML files which can be imported into any web browser. 7 | ![](https://i.imgur.com/N2Wfngc.png) 8 | 9 | The HTML files are automatically updated weekly with the new changes from FMHY. 10 | 11 | ## Why? 12 | Web browsers have auto-complete and search functions that are based on bookmarked pages, so its helpful to have interesting sites bookmarked, so you can find them quicker and make sure you are on the right URL. 13 | 14 | 15 | ## How does it look once imported? 16 | ![](https://i.imgur.com/h1GfL1W.png) 17 | 18 | 19 | ## How to download the importable HTML files? 20 | ![](https://i.imgur.com/e4xN3wy.png) 21 | 22 | 23 | ## How to import them into the browser? 24 | ![](https://i.imgur.com/6BpWb1q.png) 25 | -------------------------------------------------------------------------------- /make_fmhy_bookmarks.py: -------------------------------------------------------------------------------- 1 | 2 | import requests 3 | 4 | def addPretext(lines, sectionName, baseURL, subURL): 5 | modified_lines = [] 6 | currMdSubheading = "" 7 | currSubCat = "" 8 | currSubSubCat = "" 9 | 10 | #Remove from the lines any line that isnt a heading and doesnt contain the character `⭐` 11 | #lines = [line for line in lines if line.startswith("#") or '⭐' in line] 12 | 13 | #Parse headings 14 | for line in lines: 15 | if line.startswith("#"): #Title Lines 16 | if not subURL=="storage": 17 | if line.startswith("# ►"): 18 | currMdSubheading = "#" + line.replace("# ►", "").strip().replace(" / ", "-").replace(" ", "-").lower() 19 | currSubCat = line.replace("# ►", "").strip() 20 | currSubSubCat = "/" 21 | elif line.startswith("## ▷"): 22 | if not subURL=="non-english": #Because non-eng section has multiple subsubcats with same names 23 | currMdSubheading = "#" + line.replace("## ▷", "").strip().replace(" / ", "-").replace(" ", "-").lower() 24 | currSubSubCat = line.replace("## ▷", "").strip() 25 | elif subURL=="storage": 26 | if line.startswith("## "): 27 | currMdSubheading = "#" + line.replace("## ", "").strip().replace(" / ", "-").replace(" ", "-").lower() 28 | currSubCat = line.replace("## ", "").strip() 29 | currSubSubCat = "/" 30 | elif line.startswith("### "): 31 | currMdSubheading = "#" + line.replace("### ", "").strip().replace(" / ", "-").replace(" ", "-").lower() 32 | currSubSubCat = line.replace("### ", "").strip() 33 | 34 | # Remove links from subcategory titles (because the screw the format) 35 | if 'http' in currSubCat: currSubCat = '' 36 | if 'http' in currSubSubCat: currSubSubCat = '' 37 | 38 | elif any(char.isalpha() for char in line): #If line has content 39 | preText = f"{{\"{sectionName.replace(".md", "")}\", \"{currSubCat}\", \"{currSubSubCat}\"}}" 40 | if line.startswith("* "): line = line[2:] 41 | modified_lines.append(preText + line) 42 | 43 | return modified_lines 44 | 45 | 46 | #----------------base64 page processing------------ 47 | import base64 48 | import re 49 | 50 | doBase64Decoding = True 51 | 52 | def fix_base64_string(encoded_string): 53 | missing_padding = len(encoded_string) % 4 54 | if missing_padding != 0: 55 | encoded_string += '=' * (4 - missing_padding) 56 | return encoded_string 57 | 58 | def decode_base64_in_backticks(input_string): 59 | def base64_decode(match): 60 | encoded_data = match.group(0)[1:-1] # Extract content within backticks 61 | decoded_bytes = base64.b64decode( fix_base64_string(encoded_data) ) 62 | return decoded_bytes.decode() 63 | 64 | pattern = r"`[^`]+`" # Regex pattern to find substrings within backticks 65 | decoded_string = re.sub(pattern, base64_decode, input_string) 66 | return decoded_string 67 | 68 | def remove_empty_lines(text): 69 | lines = text.split('\n') # Split the text into lines 70 | non_empty_lines = [line for line in lines if line.strip()] # Filter out empty lines 71 | return '\n'.join(non_empty_lines) # Join non-empty lines back together 72 | 73 | def extract_base64_sections(base64_page): 74 | sections = base64_page.split("***") # Split the input string by "***" to get sections 75 | formatted_sections = [] 76 | for section in sections: 77 | formatted_section = remove_empty_lines( section.strip().replace("#### ", "").replace("\n\n", " - ").replace("\n", ", ") ) 78 | if doBase64Decoding: formatted_section = decode_base64_in_backticks(formatted_section) 79 | formatted_section = '[🔑Base64](https://rentry.co/FMHYBase64) ► ' + formatted_section 80 | formatted_sections.append(formatted_section) 81 | lines = formatted_sections 82 | return lines 83 | #----------------base64 page processing------------ 84 | 85 | 86 | 87 | def dlWikiChunk(fileName, icon, redditSubURL): 88 | 89 | #first, try to get the chunk locally 90 | try: 91 | #First, try to get it from the local file 92 | print("Loading " + fileName + " from local file...") 93 | with open(fileName.lower(), 'r') as f: 94 | page = f.read() 95 | print("Loaded.\n") 96 | #if not available locally, download the chunk 97 | except: 98 | if not fileName=='base64.md': 99 | print("Local file not found. Downloading " + fileName + " from Github...") 100 | page = requests.get("https://raw.githubusercontent.com/fmhy/FMHYedit/main/docs/" + fileName.lower()).text 101 | elif fileName=='base64.md': 102 | print("Local file not found. Downloading rentry.co/FMHYBase64...") 103 | page = requests.get("https://rentry.co/FMHYBase64/raw").text.replace("\r", "") 104 | print("Downloaded") 105 | 106 | #add a pretext 107 | redditBaseURL = "https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/" 108 | siteBaseURL = "https://fmhy.net/" 109 | if not fileName=='base64.md': 110 | pagesDevSiteSubURL = fileName.replace(".md", "").lower() 111 | subURL = pagesDevSiteSubURL 112 | lines = page.split('\n') 113 | lines = addPretext(lines, fileName, siteBaseURL, subURL) 114 | elif fileName=='base64.md': 115 | lines = extract_base64_sections(page) 116 | 117 | return lines 118 | 119 | def cleanLineForSearchMatchChecks(line): 120 | siteBaseURL = "https://fmhy.net/" 121 | redditBaseURL = "https://www.reddit.com/r/FREEMEDIAHECKYEAH/wiki/" 122 | return line.replace(redditBaseURL, '/').replace(siteBaseURL, '/') 123 | 124 | def alternativeWikiIndexing(): 125 | wikiChunks = [ 126 | dlWikiChunk("VideoPiracyGuide.md", "📺", "video"), 127 | dlWikiChunk("AI.md", "🤖", "ai"), 128 | dlWikiChunk("Android-iOSGuide.md", "📱", "android"), 129 | dlWikiChunk("AudioPiracyGuide.md", "🎵", "audio"), 130 | dlWikiChunk("DownloadPiracyGuide.md", "💾", "download"), 131 | dlWikiChunk("EDUPiracyGuide.md", "🧠", "edu"), 132 | dlWikiChunk("GamingPiracyGuide.md", "🎮", "games"), 133 | dlWikiChunk("AdblockVPNGuide.md", "📛", "adblock-vpn-privacy"), 134 | dlWikiChunk("System-Tools.md", "💻", "system-tools"), 135 | dlWikiChunk("File-Tools.md", "🗃️", "file-tools"), 136 | dlWikiChunk("Internet-Tools.md", "🔗", "internet-tools"), 137 | dlWikiChunk("Social-Media-Tools.md", "💬", "social-media"), 138 | dlWikiChunk("Text-Tools.md", "📝", "text-tools"), 139 | dlWikiChunk("Video-Tools.md", "📼", "video-tools"), 140 | dlWikiChunk("MISCGuide.md", "📂", "misc"), 141 | dlWikiChunk("ReadingPiracyGuide.md", "📗", "reading"), 142 | dlWikiChunk("TorrentPiracyGuide.md", "🌀", "torrent"), 143 | dlWikiChunk("img-tools.md", "📷", "img-tools"), 144 | dlWikiChunk("gaming-tools.md", "👾", "gaming-tools"), 145 | dlWikiChunk("LinuxGuide.md", "🐧🍏", "linux"), 146 | dlWikiChunk("DEVTools.md", "🖥️", "dev-tools"), 147 | dlWikiChunk("Non-English.md", "🌏", "non-eng"), 148 | dlWikiChunk("STORAGE.md", "🗄️", "storage"), 149 | #dlWikiChunk("base64.md", "🔑", "base64"), 150 | dlWikiChunk("NSFWPiracy.md", "🌶", "https://saidit.net/s/freemediafuckyeah/wiki/index") 151 | ] 152 | return [item for sublist in wikiChunks for item in sublist] #Flatten a into a 153 | #-------------------------------- 154 | 155 | 156 | # Save the result of alternativeWikiIndexing to a .md file 157 | # with open('wiki_adapted.md', 'w') as f: 158 | # for line in alternativeWikiIndexing(): 159 | # f.write(line + '\n') 160 | 161 | # Instead of saving it to a file, save it into a string variable 162 | wiki_adapted_md = '\n'.join(alternativeWikiIndexing()) 163 | 164 | # Remove from the lines in wiki_adapted_md any line that doesnt contain the character `⭐` 165 | wiki_adapted_starred_only_md = '\n'.join([line for line in wiki_adapted_md.split('\n') if '⭐' in line]) 166 | 167 | 168 | 169 | import re 170 | 171 | def markdown_to_html_bookmarks(input_md_text, output_file): 172 | # Predefined folder name 173 | folder_name = "FMHY" 174 | 175 | # Read the input markdown file 176 | #with open(input_file, 'r', encoding='utf-8') as f: 177 | # markdown_content = f.read() 178 | 179 | # Instead of reading from a file, read from a string variable 180 | markdown_content = input_md_text 181 | 182 | # Regex pattern to extract URLs and titles from markdown 183 | url_pattern = re.compile(r'\[([^\]]+)\]\((https?://[^\)]+)\)') 184 | # Regex pattern to extract hierarchy levels 185 | hierarchy_pattern = re.compile(r'^\{"([^"]+)", "([^"]+)", "([^"]+)"\}') 186 | 187 | # Dictionary to hold bookmarks by hierarchy 188 | bookmarks = {} 189 | 190 | # Split the content by lines 191 | lines = markdown_content.split('\n') 192 | 193 | # Parse each line 194 | for line in lines: 195 | # Find hierarchy levels 196 | hierarchy_match = hierarchy_pattern.match(line) 197 | if not hierarchy_match: 198 | continue 199 | 200 | level1, level2, level3 = hierarchy_match.groups() 201 | 202 | # Initialize nested dictionaries for hierarchy levels 203 | if level1 not in bookmarks: 204 | bookmarks[level1] = {} 205 | if level2 not in bookmarks[level1]: 206 | bookmarks[level1][level2] = {} 207 | if level3 not in bookmarks[level1][level2]: 208 | bookmarks[level1][level2][level3] = [] 209 | 210 | # Find all matches in the line for URLs 211 | matches = url_pattern.findall(line) 212 | 213 | # If the input_md_text is wiki_adapted_starred_only_md, only add the first match of url_pattern in each line 214 | if input_md_text == wiki_adapted_starred_only_md: 215 | matches = matches[:1] 216 | 217 | # Extract the description (text after the last match) 218 | last_match_end = line.rfind(')') 219 | description = line[last_match_end+1:].replace('**', '').strip() if last_match_end != -1 else '' 220 | 221 | # When the description is empty, use as description the lowest hierachy level that is not empty 222 | if not description: 223 | description = '- ' + (level3 if level3 != '/' else level2 if level2 else level1) 224 | 225 | # Add matches to the appropriate hierarchy 226 | for title, url in matches: 227 | full_title = f"{title} {description}" if description else title 228 | bookmarks[level1][level2][level3].append((full_title, url)) 229 | 230 | # Function to generate HTML from nested dictionary 231 | def generate_html(bookmarks_dict, indent=1): 232 | html = '' 233 | for key, value in bookmarks_dict.items(): 234 | html += ' ' * indent + f'

{key}

\n' 235 | html += ' ' * indent + '

\n' 236 | if isinstance(value, dict): 237 | html += generate_html(value, indent + 1) 238 | else: 239 | for full_title, url in value: 240 | html += ' ' * (indent + 1) + f'

{full_title}\n' 241 | html += ' ' * indent + '

\n' 242 | return html 243 | 244 | # HTML structure 245 | html_content = ''' 246 | 247 | Bookmarks 248 |

Bookmarks

249 |

250 | ''' 251 | # Add the main folder 252 | html_content += f'

{folder_name}

\n' 253 | html_content += '

\n' 254 | 255 | # Add bookmarks to HTML content 256 | html_content += generate_html(bookmarks) 257 | 258 | html_content += '

\n' 259 | html_content += '

\n' 260 | 261 | # Write the HTML content to the output file 262 | with open(output_file, 'w', encoding='utf-8') as f: 263 | f.write(html_content) 264 | 265 | # Print success message 266 | #print(f'Successfully created bookmarks in {output_file}') 267 | 268 | # Example usage: 269 | markdown_to_html_bookmarks(wiki_adapted_md, 'fmhy_in_bookmarks.html') 270 | markdown_to_html_bookmarks(wiki_adapted_starred_only_md, 'fmhy_in_bookmarks_starred_only.html') 271 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | --------------------------------------------------------------------------------