├── .gitignore ├── requirements.in ├── requirements.txt ├── README.md ├── .github └── workflows │ └── flipperdump.yaml ├── LICENSE └── flipperdump.py /.gitignore: -------------------------------------------------------------------------------- 1 | files/** 2 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | requests 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.11 3 | # by the following command: 4 | # 5 | # pip-compile 6 | # 7 | certifi==2023.7.22 8 | # via requests 9 | charset-normalizer==3.2.0 10 | # via requests 11 | idna==3.4 12 | # via requests 13 | requests==2.31.0 14 | # via -r requirements.in 15 | urllib3==2.0.4 16 | # via requests 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # awesome-flipperzero-pack 2 | 3 | Downloadable zip files for all [awesome-flipperzero](https://github.com/djsime1/awesome-flipperzero) resources ready to put in your microSD. 4 | 5 | ## Last release 6 | 7 | See 8 | 9 | ### microsd.zip 10 | 11 | the idea is to unpack the microsd zip and put it in the flipper's microsd 12 | 13 | ### microsd-wavplayer.zip 14 | 15 | microsd-wavplayer is part of the UberGuidoZ/Flipper repo, but as it's about 2GB, and github releases max is 2GB, it's split off its own zip. 16 | 17 | ### tweaks.zip 18 | 19 | All the other files from the awesome-flipperzero list that do not belong on an SD card, 3D printed cases, and other tooling that does not run on the Flipper. 20 | -------------------------------------------------------------------------------- /.github/workflows/flipperdump.yaml: -------------------------------------------------------------------------------- 1 | # Flipperdump gets all resources for a flipper zero, 2 | # from https://github.com/djsime1/awesome-flipperzero 3 | # and downloads them to create a zip file with all the resources 4 | # for easy installation on the SD card. 5 | # The zip file is then uploaded to the releases page of this repo. 6 | 7 | on: 8 | schedule: 9 | # Every day 10 | - cron: '34 2 * * *' 11 | # But also on push 12 | push: 13 | branches: 14 | - main 15 | 16 | 17 | jobs: 18 | update: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Flipperdump 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | run: | 26 | set -ex 27 | export OUTDIR=$(mktemp -d) 28 | 29 | sudo apt-get update 30 | sudo apt-get install -y unzip git curl wget ca-certificates perl sed git bash p7zip zip tree python3-pip python3-dev 31 | 32 | pip install --no-cache-dir -r requirements.txt 33 | python3 flipperdump.py 34 | 35 | wget -qO- https://github.com/c4milo/github-release/releases/download/v1.1.0/github-release_v1.1.0_linux_amd64.tar.gz | tar zxvf - 36 | mv github-release /usr/local/bin/github-release 37 | chmod +x /usr/local/bin/github-release 38 | # get the version from the filename 39 | # - awesome-flipperzero-pack-{self.VERSION}-microsd.zip 40 | export VERSION=$(ls files/awesome-flipperzero-pack-*-microsd.zip | sed -e 's/.*-\(.*\)-.*/\1/'| head -n 1) 41 | 42 | # create the release 43 | github-release katlol/awesome-flipperzero-pack "$VERSION" main "$(cat files/body.md)" "files/awesome-flipperzero-pack-*.zip" 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /flipperdump.py: -------------------------------------------------------------------------------- 1 | # awesome-flipperzero-pack gets all resources for a flipper zero, 2 | # from https://github.com/djsime1/awesome-flipperzero 3 | # and downloads them to create a zip file with all the resources 4 | # for easy installation on the SD card. 5 | # The zip file is then uploaded to the releases page of this repo. 6 | 7 | import requests 8 | import re 9 | import os 10 | import json 11 | import tempfile 12 | import pathlib 13 | import subprocess 14 | import datetime 15 | import asyncio 16 | import concurrent.futures 17 | import shutil 18 | 19 | 20 | class Main: 21 | def __init__(self): 22 | self.TMPDIR = pathlib.Path(tempfile.mkdtemp()) 23 | self.REGEX_CATEGORY = re.compile(r"^## (.*)") 24 | self.REGEX_LINK = re.compile(r"^- \[(.*)\]\((.*)\)") 25 | self.REGEX_GITHUB_REPO = re.compile(r"^https://github.com/(.*)/(.*)$") 26 | self.NOW = datetime.datetime.now() 27 | self.VERSION = self.NOW.strftime("v0.0.%Y%m%d%H%M") 28 | self.SEMA = asyncio.Semaphore(30) 29 | 30 | self.PACKAGES = { 31 | "microsd": [ 32 | "databases-dumps", 33 | "applications-plugins", 34 | "graphics-animations", 35 | ], 36 | "tweaks": [ 37 | "firmwares-tweaks", 38 | "modules-cases", 39 | "off-device-debugging", 40 | "notes-references", 41 | ], 42 | "microsd-wavplayer": [], 43 | } 44 | 45 | def _urlify(self, string): 46 | # this removes all non-alphanumeric characters from a string to a dash 47 | # and makes it lowercase 48 | # additionally it removes all double dashes and leading and trailing dashes 49 | return re.sub( 50 | r"-+", 51 | "-", 52 | re.sub(r"^-+|-+$", "", re.sub(r"[^a-zA-Z0-9]+", "-", string.lower())), 53 | ) 54 | 55 | async def _exec_wait(self, *args, **kwargs): 56 | proc = await asyncio.create_subprocess_exec( 57 | *args, 58 | **kwargs, 59 | ) 60 | await proc.wait() 61 | return proc.returncode 62 | 63 | def _get_awesome_list(self): 64 | r = requests.get( 65 | "https://raw.githubusercontent.com/djsime1/awesome-flipperzero/main/README.md" 66 | ) 67 | # We want to parse the markdown with mistletoe, 68 | # then we get a list of links per category. 69 | # We then want to download all the links. 70 | # Most links are github, so we can clone the latest default branch, 71 | # some are some specific path of a github repo, so we can download the latest default branch and only the path. 72 | # Some are just a link to a zip file, or a PDF, so we can download that. 73 | 74 | # Let's not use a markdown parser, but just regex. 75 | 76 | # Get all item number, names and links per category 77 | categories = [] 78 | current_category = None 79 | for i, line in enumerate(r.text.splitlines()): 80 | if category := self.REGEX_CATEGORY.match(line): 81 | current_category = category.group(1) 82 | categories.append( 83 | { 84 | "name": current_category, 85 | "url": self._urlify(current_category), 86 | "line": line, 87 | "items": [], 88 | "_i": str(i + 1).zfill(4), 89 | } 90 | ) 91 | elif link := self.REGEX_LINK.match(line): 92 | categories[-1]["items"].append( 93 | { 94 | "name": link.group(1), 95 | "url": self._urlify(link.group(1)), 96 | "line": line, 97 | "link": link.group(2), 98 | "_i": str(i + 1).zfill(4), 99 | } 100 | ) 101 | return categories 102 | 103 | async def _download_github_repo(self, url, path): 104 | async with self.SEMA: 105 | os.makedirs(path, exist_ok=True) 106 | proc = await asyncio.create_subprocess_exec( 107 | "git", 108 | "clone", 109 | "--depth", 110 | "1", 111 | url, 112 | str(path), 113 | stdout=asyncio.subprocess.DEVNULL, 114 | stderr=asyncio.subprocess.DEVNULL, 115 | ) 116 | # wait until exit 117 | await proc.wait() 118 | print("downloaded", proc.returncode, url, path) 119 | # get the return code 120 | return proc.returncode 121 | 122 | async def _safe_download(self, package, category, item): 123 | # mkdir /self.TMPDIR/package/i-category/i-link 124 | # path = pathlib.Path(self.TMPDIR) / package / item["_i"] + "-" + item["url"] 125 | # not work PosixPath + str 126 | path = ( 127 | pathlib.Path(self.TMPDIR) 128 | / package 129 | / (category["_i"] + "-" + category["url"]) 130 | / (item["_i"] + "-" + item["url"]) 131 | ) 132 | # if the link is a github repo, clone it with depth 1 133 | if github := self.REGEX_GITHUB_REPO.match(item["link"]): 134 | res = await self._download_github_repo(item["link"], path) 135 | if res == 0: 136 | # success. downloaded.md 137 | with open(self.TMPDIR / package / "downloaded.md", "a") as f: 138 | f.write(item["line"] + "\n") 139 | # remove .git 140 | shutil.rmtree(path / ".git") 141 | 142 | else: 143 | # failed. skipped.md 144 | with open(self.TMPDIR / package / "skipped.md", "a") as f: 145 | f.write(item["line"] + " (failed: " + str(res) + ")\n") 146 | print("failed", item["link"], path, res) 147 | 148 | async def run(self): 149 | links = self._get_awesome_list() 150 | 151 | # now we have categories and links 152 | # let's create a zipfile with all the resources 153 | # print only categories 154 | items = [] 155 | for package, categories in self.PACKAGES.items(): 156 | for category_filter in categories: 157 | for category in links: 158 | if category["url"] != category_filter: 159 | continue 160 | for item in category["items"]: 161 | items.append((package, category, item)) 162 | 163 | await asyncio.gather(*[self._safe_download(*item) for item in items]) 164 | # move $(find . -type d -name Wav_Player) 165 | # to microsd-wavplayer 166 | dir_wavplayer = subprocess.run( 167 | [ 168 | "find", 169 | self.TMPDIR / "microsd", 170 | "-type", 171 | "d", 172 | "-name", 173 | "Wav_Player", 174 | ], 175 | capture_output=True, 176 | text=True, 177 | ).stdout.strip() 178 | os.rename( 179 | dir_wavplayer, 180 | self.TMPDIR / "microsd-wavplayer", 181 | ) 182 | # zip every package 183 | tasks = [] 184 | for package in self.PACKAGES.keys(): 185 | # with lowest compression 186 | tasks.append( 187 | self._exec_wait( 188 | "zip", 189 | "-r", 190 | package + ".zip", 191 | package, 192 | cwd=self.TMPDIR, 193 | stdout=asyncio.subprocess.DEVNULL, 194 | stderr=asyncio.subprocess.DEVNULL, 195 | ) 196 | ) 197 | await asyncio.gather(*tasks) 198 | 199 | for package in self.PACKAGES.keys(): 200 | # move the file to proper awesome-flipperzero-pack release name 201 | new_name = self.TMPDIR / ( 202 | "awesome-flipperzero-pack-" + self.VERSION + "-" + package + ".zip" 203 | ) 204 | 205 | os.rename( 206 | self.TMPDIR / (package + ".zip"), 207 | new_name, 208 | ) 209 | # we want a file tree that shows 210 | # the full size but not 211 | # the individual file lines. 212 | file_tree = subprocess.run( 213 | [ 214 | "tree", 215 | "-h", 216 | "--du", 217 | "-L", 218 | "3", 219 | self.TMPDIR / package, 220 | ], 221 | capture_output=True, 222 | text=True, 223 | ).stdout 224 | # write it to files.txt 225 | with open(self.TMPDIR / package / "files.txt", "w") as f: 226 | f.write(file_tree) 227 | 228 | body = "\n".join( 229 | [ 230 | f"# awesome-flipperzero-pack {self.VERSION}\n", 231 | "awesome-flipperzero-pack is a downloadable toolkit of [the awesome-flipperzero resources](https://github.com/djsime1/awesome-flipperzero).", 232 | "These zip files are available:", 233 | f"1. awesome-flipperzero-pack-{self.VERSION}-microsd.zip: for the microsd card (firmwares, databases, plugins, animations)", 234 | f"2. awesome-flipperzero-pack-{self.VERSION}-tweaks.zip: for the tweaks (firmwares, modules, cases, notes)", 235 | f"3. awesome-flipperzero-pack-{self.VERSION}-microsd-wavplayer.zip: for the microsd card (wavplayer, part of [UberGuidoZ/Flipper](https://github.com/UberGuidoZ/Flipper))", 236 | "\n## microsd", 237 | "\n### downloaded", 238 | open(self.TMPDIR / "microsd" / "downloaded.md").read(), 239 | "\n### skipped", 240 | open(self.TMPDIR / "microsd" / "skipped.md").read(), 241 | "\n### files", 242 | # spoiler it 243 | "
click to expand\n\n```\n\n", 244 | open(self.TMPDIR / "microsd" / "files.txt").read(), 245 | "\n\n```\n\n
\n", 246 | "\n## tweaks", 247 | "\n### downloaded", 248 | open(self.TMPDIR / "tweaks" / "downloaded.md").read(), 249 | "\n### skipped", 250 | open(self.TMPDIR / "tweaks" / "skipped.md").read(), 251 | "\n### files", 252 | "
click to expand\n\n```\n\n", 253 | open(self.TMPDIR / "tweaks" / "files.txt").read(), 254 | "\n\n```\n\n
\n", 255 | "\n## microsd-wavplayer", 256 | "\npart of [UberGuidoZ/Flipper](https://github.com/UberGuidoZ/Flipper), but very big, so it's in a separate zip file", 257 | "\n### files", 258 | "\nSee ", 259 | ] 260 | ) 261 | # write body.txt to tmpdir/body.txt 262 | with open(self.TMPDIR / "body.md", "w") as f: 263 | f.write(body) 264 | # move tmpdir to cwd/files 265 | shutil.move(self.TMPDIR, "files") 266 | 267 | 268 | if __name__ == "__main__": 269 | loop = asyncio.get_event_loop() 270 | loop.run_until_complete(Main().run()) 271 | --------------------------------------------------------------------------------