├── LICENSE ├── README.md ├── Styles ├── Generic │ ├── Banner │ │ ├── Addons.png │ │ ├── Admin.png │ │ ├── Generic.png │ │ └── Themes.png │ └── Icon │ │ ├── Addons.png │ │ ├── Admin.png │ │ ├── Generic.png │ │ └── Themes.png ├── icon.png ├── index.css ├── index.js ├── index.mustache └── tweak.mustache ├── index.py ├── requirements.txt ├── setup.sh └── util ├── DebianPackager.py ├── DepictionGenerator.py ├── DpkgPy.py ├── PackageLister.py └── gpg.batchgen /LICENSE: -------------------------------------------------------------------------------- 1 | Silica MIT License 2 | 3 | Copyright (c) 2019 Shuga Holdings 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | The intended use case does not enable or encourage the violation of any laws 16 | of the United States of America, including (but not limited to) infringing 17 | on other's rights to distribute creative or practical works as they see fit 18 | given that they own the copyright for said work (also known as "piracy"). 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Silica 2 | 3 | **Silica** is a static repo generator for jailbroken iOS devices developed by [Shuga](https://shuga.co) and supported by [Ignition](https://ignition.fun). 4 | 5 | The goal behind Silica is simple: make it as easy as possible to make a personal repo that plays nicely with both Cydia and Sileo. Silica generates "static" repos, allowing for repos to be hosted on GitHub Pages for free. 6 | 7 | ## Getting Started 8 | 9 | Silica, by default, is relatively straight-forward to configure. This tutorial is going to assume that you have the following already installed on your machine: 10 | - [Python 3](https://www.python.org/downloads/release/python-373/) (to run Silica) 11 | - `pip3` (for installing Python dependencies) 12 | - [Homebrew](https://brew.sh/) (macOS users only) 13 | 14 | Silica is only officially supported on macOS and Debian-based OSes at the moment. We can not guarantee Windows support at this time. I'm sure you could get this working on other Linux distributions with a bit of tweaking, too. `btw i don't use arch` 15 | 16 | ### Silica for Windows Subsystem for Linux (WSL) 17 | 18 | **If you are not using Windows, skip down to Dependencies.** 19 | 20 | Due to Silica requiring dpkg-deb to properly work, you cannot run Silica in native Windows. 21 | As a result, you need to set your machine up WSL and Ubuntu for Windows, which will allow for Linux 22 | programs to run in Windows via a terminal. 23 | 24 | #### 1. Set up WSL 25 | Please follow [this official tutorial](https://docs.microsoft.com/en-us/windows/wsl/install-win10). Please use Ubuntu for ideal results. 26 | 27 | #### 2. Setting up WSL for `dpkg-deb` 28 | Due to how `dpkg-deb` handles permissions, we need to change some settings to make things work. 29 | 30 | 1. Run `sudo nano /etc/wsl.conf` to edit the WSL config. 31 | 2. Copy and paste the following text into the file. 32 | 33 | ```ini 34 | [automount] 35 | enabled = true 36 | root = /mnt/ 37 | options = "umask=22,fmask=11" 38 | ``` 39 | 40 | 3. Run the following command to update your `umask` to `0022`. 41 | ```bash 42 | echo "\numask 0022" >> ~/.bashrc 43 | ``` 44 | 45 | 4. Open up Windows PowerShell as an administrator by right-clicking on the Windows button. 46 | 47 | 5. Run this command to restart WSL: 48 | 49 | ```powershell 50 | Get-Service LxssManager | Restart-Service 51 | ``` 52 | 53 | (Note: Please always run Silica through the Ubuntu program or Windows Terminal.) 54 | 55 | *(Tip courtesy of [/u/zoredache on Reddit](https://www.reddit.com/r/bashonubuntuonwindows/comments/a7v5d8/problems_with_dpkgdeb_bad_permissions_how_do_i/).)* 56 | 57 | ##### 3. Install dependencies 58 | 59 | Like any Debian-based system (like Ubuntu), we need to install some dependencies before downloading Silica. 60 | 61 | ```bash 62 | apt-get install gnupg 63 | apt-get install git 64 | ``` 65 | 66 | ##### 3. Getting Silica 67 | 68 | Now it's time to get Silica! Because of how Windows deals with line breaks, we need to download Silica via Git. 69 | If you already downloaded Silica via your web browser, delete it. We'll redownload it in a second. 70 | 71 | Once you are in the directory you want to download Silica to, run this command: 72 | 73 | ```bash 74 | git clone https://github.com/Shugabuga/Silica/ 75 | ``` 76 | 77 | This will create a new folder for Silica. 78 | 79 | ##### 4. Text Editors and line breaks 80 | 81 | Due to how Windows deals with line breaks, if you wish to create Silica config files by hand, you need to make sure 82 | your text editor uses Unix line breaks. This varies by text editor and IDE, so look for a setting about line breaks 83 | or EOL characters and set it to Unix. 84 | 85 | 86 | **From here, you're all set** for Windows-specific instructions! Now follow the Debian instructions. 87 | 88 | ### Dependencies 89 | 90 | We know that some developers will already have these dependencies installed. If you know you already have a dependency, there is no need to re-install it. 91 | 92 | macOS users are going to want to install a couple of packages. To do so, run these commands in Terminal: 93 | ```bash 94 | brew install dpkg 95 | brew install gnupg 96 | ``` 97 | 98 | Users of Debian or its derivatives (like Ubuntu) need to run the following commands *as root* to install needed dependencies: 99 | ```bash 100 | apt-get install gnupg 101 | apt-get install git 102 | ``` 103 | 104 | We also use `find`, `bzip2`, and `xz`, but most people have these installed by default. 105 | 106 | (Windows Subsystem for Linux users already did this step.) 107 | 108 | ### Other Dependencies and Settings 109 | 110 | Now that any needed system dependencies are installed, we need to install some Python dependencies and configure `settings.json`. Thankfully, the included `setup.sh` script handles this for you! 111 | 112 | Run the following command and follow the on-screen prompts to set up Silica's core settings, including setting the repo name, default tint color, and whether it should automatically run Git when it finishes updating your repo. This script will also generate a key to sign the `Release.gpg` file which will be stored in your keyring. 113 | ```bash 114 | ./setup.sh 115 | ``` 116 | 117 | If this doesn't work, make sure the the file is set as an executable by running `chmod +x setup.sh`. 118 | 119 | The settings you input during the installation process can be later modified in the `Styles/settings.json` file. 120 | 121 | ### Customization 122 | 123 | Most users would like to customize their repos to fit their needs. Everything you need to do this is in the `Styles` folder. 124 | 125 | - `index.mustache` is the template file of the repo's main web page. 126 | - `tweak.mustache` is the template file of the web depictions of the tweaks. 127 | - `index.css` is a CSS file that is applied to every web page generated by Silica. 128 | - `index.js` is a JavaScript file that handles version detection and tab switching on web depictions. 129 | - `settings.json` is auto-generated by the setup script, but you can edit it to change your repo's name, description, default tint color, and more. 130 | - `icon.png` is the repo's icon as it will display in Cydia and Sileo. 131 | - The `Generic` folder includes default image assets. 132 | 133 | #### Image Assets 134 | 135 | All Silica image assets are `.png` files. 136 | 137 | - The `Generic` folder, which contains a `Banner` and `Icon` folder, includes default assets for when a package lacks a banner or an icon. These are named after the package's section. If a section does not have its own image, it will use whatever is stored in `Generic.png`. 138 | - `icon.png` is your repo's icon as displayed in Sileo and Cydia. 139 | 140 | ### Adding Packages 141 | 142 | Silica's `Packages` folder includes all the packages that will live on your repo. A folder in `Packages` represents an individual tweak. The name of the folder does not matter and changing it does nothing; Silica references packages via their "bundle id." 143 | 144 | Inside of a tweak or theme's folder should include a `silica_data` folder (which will be elaborated on later on) and either a .deb file (useful for tweak developers) or the hierarchy of the generated package (the latter which is useful for theme designers). If a .deb file is present, it will take precedence. 145 | 146 | The directory tree should look similar to the following. **Please note how that the .deb file is in its own folder *inside* of `Packages`.**: 147 | 148 | ``` 149 | Packages 150 | My Tweak 151 | tweak.deb 152 | silica_data 153 | index.json 154 | description.md 155 | icon.png 156 | banner.png 157 | screenshots 158 | 01.png 159 | 02.png 160 | a_screenshot.png 161 | scripts 162 | prerm 163 | extrainst_ 164 | My Theme 165 | Library 166 | Themes 167 | MyTheme.theme 168 | IconBundles 169 | com.anemonetheming.anemone-large.png 170 | silica_data 171 | index.json 172 | description.md 173 | icon.png 174 | banner.png 175 | screenshots 176 | 01.png 177 | 02.png 178 | a_screenshot.png 179 | ``` 180 | 181 | #### `silica_data` 182 | 183 | The `silica_data` folder is where the icon, description, screenshot, and other package (tweak/theme) information live. This package is *not* put in the final package file (the ".deb" file). The following files and folders can go in this folder: 184 | 185 | - `index.json` is the only required file. It is a JSON file including information such as the bundle ID, a short tagline description, version compatibility, developer information, changelog data, and more. This file is generated by Silica using user input if it does not exist. More information on this file is in the **Documentation** section of this document. 186 | - `description.md` is a Markdown file that houses the package's description. 187 | - `icon.png` is the package's icon as it appears in depictions and in Sileo. It should be a square PNG file. 188 | - `banner.png` is the package's banner as it appears in depictions and in Sileo. It should be a rectangular PNG file. 189 | - The `screenshots` folder houses any screenshots you want to be displayed alongside the package. 190 | - The `scripts` folder include any pre/post-installation scripts. The contents of this folder will be placed in the package's `DEBIAN` folder. 191 | 192 | The `index.json` file, if missing, will be generated when running Silica. 193 | 194 | ### Generating a Repo 195 | 196 | Once everything is configured as you wish, run the following command to "compile" your repo. 197 | 198 | ```bash 199 | python3 index.py 200 | ``` 201 | 202 | From here, Silica will automatically generate a repo and put the output in the `docs` folder. If a package does not have a `silica_data` file, you will be asked some information to help automatically generate it. 203 | 204 | From here, a service such as [GitHub Pages](https://pages.github.com) can be used to host the repo for free. 205 | 206 | ### Hosting with GitHub Pages 207 | 208 | Silica is designed to be used with [GitHub Pages](https://pages.github.com), allowing you to operate a repo for free. 209 | 210 | There are multiple ways to go about this, but this method is recommended for those who are totally unfamiliar with `git` (if you can get `git` up and running, please use that). 211 | 212 | 1. [Create a GitHub account.](https://github.com/join?source=silica-readme) 213 | 2. [Create a new repository.](https://github.com/new) Initialize it with a README. 214 | 3. Drag-and-drop all of the contents of the `docs` directory to the newly-created repository. 215 | 4. Type something in the text box and click the "Commit Changes" button. 216 | 5. Go to your repository's settings and scroll down to the GitHub Pages section. 217 | 6. Set "Source" from "None" to "master branch." 218 | 7. Follow [these steps](https://help.github.com/en/articles/using-a-custom-domain-with-github-pages) to configure your domain with GitHub Pages 219 | 220 | From there, you should be done! Your compiled repo's "source" will have to be publicly viewable if you don't have a Pro account, but that shouldn't matter too much if you only upload the `docs` folder to GitHub. 221 | 222 | If you happen to have `git` installed on your computer and run `git clone https://github.com/your_username/repository_name/`, you can use "master branch /docs folder" as the site source and Silica's automatic Git pushing feature to automate the uploading process. This is recommended if you happen to have a GitHub Pro account. 223 | 224 | ## Documentation 225 | 226 | ### `index.json` 227 | Here is a comprehensive example of an `index.json` file. These reside in the package's `silica_data` folder and is required for the repo to properly compile. You **must** include the `bundle_id`, `name`, `version`, `tagline`, `section`, `works_min`, and `works_max`. All other values are optional, but are recommended (if applicable). 228 | 229 | ```json 230 | { 231 | "bundle_id": "co.shuga.elementary-lite", 232 | "name": "Elementary Lite", 233 | "version": "1.1.2-beta", 234 | "tagline": "A simplistic, glyph-based theme.", 235 | "homepage": "https://shuga.co/repo", 236 | "developer": { 237 | "name": "Shuga", 238 | "email": "sileo@shuga.co" 239 | }, 240 | "maintainer": { 241 | "name": "Shuga", 242 | "email": "sileo@shuga.co" 243 | }, 244 | "social": [ 245 | { 246 | "name": "Twitter", 247 | "url": "https://twitter.com/HeyItsShuga" 248 | }, 249 | { 250 | "name": "Website", 251 | "url": "https://shuga.co/" 252 | } 253 | ], 254 | "sponsor": { 255 | "name": "Shuga Studios", 256 | "email": "studios@shuga.co" 257 | }, 258 | "section": "Themes", 259 | "pre_dependencies": "", 260 | "dependencies": "com.anemonetheming.anemone", 261 | "conflicts": "", 262 | "replaces": "", 263 | "provides": "", 264 | "other_control": ["Tag: role::enduser", "SomeOtherEntryToControl: True"], 265 | 266 | "tint": "#55c6d3", 267 | "works_min": "8.0", 268 | "works_max": "13.0", 269 | "featured": "true", 270 | "source": "https://github.com/Shugabuga/Silica", 271 | "changelog": [ 272 | { 273 | "version": "1.1.2-beta", 274 | "changes": "Thank you for participating in the Elementary beta! All future updates will be given a descriptive changelog with a list of changes." 275 | } 276 | ] 277 | } 278 | ``` 279 | 280 | ### `settings.json` 281 | 282 | The `settings.json` file, located in the `Styles` folder, let you configure Silica as a while. Everything in this file is required except for `subfolder`, `social`, `footer`, and `enable_pgp`. 283 | 284 | ```json 285 | { 286 | "name": "Silica Beta Repo", 287 | "description": "A repo used to help develop the Silica static repo compiler.", 288 | "tint": "#2da9f3", 289 | "cname": "silica.shuga.co", 290 | "maintainer": { 291 | "name": "Shuga", 292 | "email": "sileo@shuga.co" 293 | }, 294 | "social": [ 295 | { 296 | "name": "Twitter", 297 | "url": "https://twitter.com/HeyItsShuga" 298 | }, 299 | { 300 | "name": "Website", 301 | "url": "https://shuga.co/" 302 | } 303 | ], 304 | "automatic_git": "false", 305 | "subfolder": "repo", 306 | "footer": "{{repo_name}} Powered by Silica {{silica_version}}", 307 | "enable_gpg": "false" 308 | } 309 | ``` 310 | 311 | ### Templating 312 | 313 | The repo's home page and web-based tweak depictions can be customized using a [Mustache-based](https://mustache.github.io/mustache.5.html) templating system. If you want to customize these pages, you can use the following placeholders to extend or personalize your Silica install. 314 | 315 | Customizing your repo is optional, but those migrating from a different repo who want to preserve the look of their old depictions may want to do this, as may those who want to push a brand identity. 316 | 317 | ### `index.mustache` Placeholders 318 | 319 | |Placeholder|Description| 320 | |---|---| 321 | |`{{repo_name}}`|The name of the repo.| 322 | |`{{repo_desc}}`|A short tagline of the repo.| 323 | |`{{repo_url}}`|The domain the repo is hosted on.| 324 | |`{{repo_tint}}`|The repo's default tint color.| 325 | |`{{tint_color}}`|An alias to `{{repo_tint}}`.| 326 | |`{{{repo_carousel}}}`|A scrollable carousel that lists any featured packages.| 327 | |`{{{repo_packages}}}`|A list of every package on the repo.| 328 | |`{{silica_compile_date}}`|The date the repo was compiled on (in YYYY-MM-DD).| 329 | |`{{silica_version}}`|The version of Silica that the repo runs on.| 330 | |`{{footer}}`|The footer.| 331 | |`{{tweak_release}}`|An object including all of the tweaks on the repo and their settings.| 332 | 333 | ### `tweak.mustache` Placeholders 334 | 335 | |Placeholder|Description| 336 | |---|---| 337 | |`{{repo_name}}`|The name of the repo.| 338 | |`{{repo_desc}}`|A short tagline of the repo.| 339 | |`{{repo_url}}`|The domain the repo is hosted on.| 340 | |`{{repo_tint}}`|The repo's default tint color.| 341 | |`{{tint_color}}`|The package's tint color. Defaults to the repo's default tint color.| 342 | |`{{silica_compile_date}}`|The date the repo was compiled on (in YYYY-MM-DD).| 343 | |`{{silica_version}}`|The version of Silica that the repo runs on.| 344 | |`{{footer}}`|The footer.| 345 | |`{{tweak_release}}`|An object including all of the tweaks on the repo and their settings.| 346 | |`{{tweak_name}}`|The name of the package to display.| 347 | |`{{tweak_developer}}`|The author of the displayed package.| 348 | |`{{tweak_version}}`|The current version of the displayed package.| 349 | |`{{tweak_section}}`|The category of the displayed package.| 350 | |`{{tweak_bundle_id}}`|The "Bundle ID" of the displayed package.| 351 | |`{{tweak_compatibility}}`|A string to state what iOS versions a package is compatible with.| 352 | |`{{works_min}}`|The minimum iOS version the package is compatible with.| 353 | |`{{works_max}}`|The maximum iOS version the package is compatible with.| 354 | |`{{tweak_tagline}}`|A short tagline of the displayed package.| 355 | |`{{{tweak_carousel}}}`|A scrollable carousel of the displayed package's screenshots.| 356 | |`{{{tweak_description}}}`|A formatted description of the displayed package. Defaults to `{{tweak_tagline}}`.| 357 | |`{{{changelog}}}`|A formatted list of changes to the displayed package.| 358 | |`{{source}}`|A URL to the package's source code.| 359 | 360 | ### File Structure 361 | This is the structure of user-defined files and folders in Silica. Some of these, such as `settings.json`, may be automatically generated on set-up. Others (such as the `.mustache` template files) don't need to be changed if you want to use the default settings. 362 | 363 | ``` 364 | Packages // All tweak/theme information/data will go here, AKA what will mainly be messed with. 365 | [Tweak Folder] 366 | silica_data 367 | index.json // This will allow you to edit data in the generated Control file, as well as the data that web and native depictions will use (such as compatibility information and color tint; icons and screenshots are automatically dealt with.). 368 | description.md // A separate description file, because a one-liner in some JSON isn't any fun! 369 | icon.png // The package icon. 370 | banner.png // The header image. 371 | screenshots 372 | [Assorted images].png 373 | scripts 374 | [Everything here will be put in DEBIAN and is for pre/postinst scripts] 375 | [Mirror of iOS directory listing OR a .deb file] 376 | Styles // Allows users to customize how Silica looks 377 | index.mustache // This controls the compiled index.html file. 378 | tweak.mustache // This controls the per-tweak web depictions generated. 379 | settings.json // This allows you to edit data in the generated Release file, maintainer links, and some other things that Sileo native depictions may need 380 | index.css // Stylesheet 381 | index.js // JavaScript 382 | icon.png // Repo icon. 383 | ``` 384 | -------------------------------------------------------------------------------- /Styles/Generic/Banner/Addons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/Generic/Banner/Addons.png -------------------------------------------------------------------------------- /Styles/Generic/Banner/Admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/Generic/Banner/Admin.png -------------------------------------------------------------------------------- /Styles/Generic/Banner/Generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/Generic/Banner/Generic.png -------------------------------------------------------------------------------- /Styles/Generic/Banner/Themes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/Generic/Banner/Themes.png -------------------------------------------------------------------------------- /Styles/Generic/Icon/Addons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/Generic/Icon/Addons.png -------------------------------------------------------------------------------- /Styles/Generic/Icon/Admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/Generic/Icon/Admin.png -------------------------------------------------------------------------------- /Styles/Generic/Icon/Generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/Generic/Icon/Generic.png -------------------------------------------------------------------------------- /Styles/Generic/Icon/Themes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/Generic/Icon/Themes.png -------------------------------------------------------------------------------- /Styles/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shugabuga/Silica/cae0e2956c77e40609a2d5b6aeffe04a36f20dc7/Styles/icon.png -------------------------------------------------------------------------------- /Styles/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | } 4 | 5 | .body { 6 | font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Helvetica", sans-serif; 7 | margin-top: 20px; 8 | margin-bottom: 20px; 9 | 10 | } 11 | 12 | @media only screen and (max-width: 600px) { 13 | .body { 14 | margin-left: 20px; 15 | margin-right: 20px; 16 | } 17 | .scroll_view { 18 | width: calc(100vw - 60px); 19 | } 20 | } 21 | @media only screen and (min-width: 601px) { 22 | .body { 23 | width: 500px; 24 | margin-left: auto; 25 | margin-right: auto; 26 | } 27 | .scroll_view { 28 | width: calc(500px - 40px); 29 | } 30 | } 31 | 32 | h1, h2, h3, h4, h5, h6, p { 33 | margin: 2px; 34 | } 35 | 36 | .caption { 37 | color: #bbb; 38 | font-size: 16px; 39 | } 40 | 41 | .header { 42 | margin-top: 40px; 43 | margin-bottom: 25px; 44 | } 45 | 46 | .scroll_view { 47 | overflow-x: scroll; 48 | overflow-y: hidden; 49 | margin: 10px; 50 | white-space: nowrap; 51 | -webkit-overflow-scrolling: touch; 52 | } 53 | 54 | .scroll_view > .card { 55 | background-color: #ccc; 56 | background-position: center; 57 | background-repeat: no-repeat; 58 | background-size: cover; 59 | padding: 10px; 60 | margin: 10px; 61 | border-radius: 10px; 62 | font-weight: bold; 63 | height: 125px; 64 | width: 275px; 65 | position: relative; 66 | display: inline-block; 67 | } 68 | 69 | .scroll_view > .img_card { 70 | background-color: #ccc; 71 | width: 250px; 72 | margin: 5px; 73 | border-radius: 10px; 74 | font-weight: bold; 75 | position: relative; 76 | display: inline-block; 77 | } 78 | 79 | .card > p { 80 | color: white; 81 | bottom: 15px; 82 | left: 15px; 83 | font-size: 25px; 84 | position: absolute; 85 | text-shadow: 0 0 10px rgba(0,0,0,0.8); 86 | } 87 | 88 | .center { 89 | text-align: center; 90 | } 91 | 92 | .left_float { 93 | float: left; 94 | } 95 | 96 | .right_float { 97 | float: right; 98 | } 99 | 100 | .add_sileo { 101 | font-size: 2em; 102 | cursor: pointer; 103 | text-decoration: none; 104 | } 105 | 106 | .add_sileo:hover, a:hover { 107 | opacity: 0.8; 108 | cursor: pointer; 109 | } 110 | 111 | .package { 112 | margin-bottom: 40px; 113 | } 114 | 115 | .package > img { 116 | border-radius: 20px; 117 | width: 75px; 118 | height: 75px; 119 | float: left; 120 | } 121 | 122 | .package_info { 123 | margin-left: 90px; 124 | } 125 | 126 | .package_name { 127 | font-weight: bold; 128 | font-size: 21px; 129 | } 130 | 131 | .package_caption { 132 | margin-top: 7px; 133 | color: #999; 134 | } 135 | 136 | .cell > .title { 137 | float: left; 138 | color: #999; 139 | } 140 | 141 | .cell > .text { 142 | float: right; 143 | } 144 | 145 | h3 { 146 | margin-top: 15px; 147 | margin-bottom: 15px; 148 | } 149 | 150 | .footer { 151 | margin-top: 20px; 152 | } 153 | 154 | .subtle_link, .subtle_link > div > div, .subtle_link > div > div > p { 155 | text-decoration: none; 156 | color: black; 157 | } 158 | 159 | .subtle_link:hover, .subtle_link > *:hover { 160 | opacity: 0.8; 161 | } 162 | 163 | .green { 164 | color: #00b90c; 165 | } 166 | 167 | .red { 168 | color: #ba0000; 169 | } 170 | 171 | .banner_underlay { 172 | background-size: cover; 173 | background-position: center; 174 | height: 150px; 175 | width: 100%; 176 | border-radius: 8px; 177 | z-index: -2; 178 | margin-bottom: 25px; 179 | top: 0; 180 | } 181 | 182 | .changelog { 183 | display: none; 184 | } 185 | 186 | .nav { 187 | display: flex; 188 | margin: 20px; 189 | float: center; 190 | } 191 | 192 | .nav_btn { 193 | width: 50%; 194 | display:inline; 195 | text-align: center; 196 | color: #999; 197 | font-weight: 500; 198 | padding-bottom: 10px; 199 | cursor: pointer; 200 | } 201 | 202 | .changelog_entry { 203 | margin-bottom: 18px; 204 | } 205 | 206 | a { 207 | text-decoration: none; 208 | } 209 | 210 | .table-btn, .table-btn > div { 211 | padding-top: 10px; 212 | padding-bottom: 10px; 213 | } 214 | 215 | .table-btn:after { 216 | content: "›"; 217 | font-size:30px; 218 | transform: translateY(-40px); 219 | float: right; 220 | } 221 | 222 | @media (prefers-color-scheme: dark) { 223 | body { 224 | background: #161616; 225 | color: white; 226 | } 227 | 228 | .subtle_link, .subtle_link > div > div, .subtle_link > div > div > p { 229 | color: white; 230 | } 231 | } -------------------------------------------------------------------------------- /Styles/index.js: -------------------------------------------------------------------------------- 1 | function compatible(works_min, works_max, tweak_compatibility) { 2 | let currentiOS = parseFloat(('' + (/CPU.*OS ([0-9_]{1,})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1]).replace('undefined', '3_2').replace('_', '.').replace('_', '')); 3 | works_min = numerize(works_min); 4 | works_max = numerize(works_max); 5 | let el = document.querySelector(".compatibility"); 6 | if (currentiOS < works_min) { 7 | el.innerHTML = "Your version of iOS is too old for this package. This package works on " + tweak_compatibility + "."; 8 | el.classList.add("red") 9 | } else if(currentiOS > works_max) { 10 | el.innerHTML = "Your version of iOS is too new for this package. This package works on " + tweak_compatibility + "."; 11 | el.classList.add("red") 12 | } else if(String(currentiOS) != "NaN") { 13 | el.innerHTML = "This package works on your device!"; 14 | el.classList.add("green") 15 | } 16 | } 17 | function numerize(x) { 18 | return x.substring(0,x.indexOf(".")) + "." + x.substring(x.indexOf(".")+1).replace(".","") 19 | } 20 | function swap(hide, show) { 21 | for (var i = document.querySelectorAll(hide).length - 1; i >= 0; i--) { 22 | document.querySelectorAll(hide)[i].style.display = "none"; 23 | } 24 | for (var i = document.querySelectorAll(show).length - 1; i >= 0; i--) { 25 | document.querySelectorAll(show)[i].style.display = "block"; 26 | } 27 | document.querySelector(".nav_btn" + show + "_btn").classList.add("active"); 28 | document.querySelector(".nav_btn" + hide + "_btn").classList.remove("active") 29 | } 30 | 31 | function externalize() { 32 | for (var i = document.querySelectorAll("a").length - 1; i >= 0; i--) { 33 | document.querySelectorAll("a")[0].setAttribute("target","blank") 34 | } 35 | } 36 | function darkMode(isOled) { 37 | var darkColor = isOled ? "black" : "#161616"; 38 | document.querySelector("body").style.color = "white"; 39 | document.querySelector("body").style.background = darkColor; 40 | for (var i = document.querySelectorAll(".subtle_link, .subtle_link > div > div, .subtle_link > div > div > p").length - 1; i >= 0; i--) { 41 | document.querySelectorAll(".subtle_link, .subtle_link > div > div, .subtle_link > div > div > p")[i].style.color = "white"; 42 | } 43 | } 44 | if (navigator.userAgent.toLowerCase().indexOf("dark") != -1) { 45 | darkMode(navigator.userAgent.toLowerCase().indexOf("oled") != -1 || navigator.userAgent.toLowerCase().indexOf("pure-black") != -1); 46 | } -------------------------------------------------------------------------------- /Styles/index.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{repo_name}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |

{{repo_name}}

22 | + 23 |
24 | {{{repo_carousel}}} 25 |
26 |

Packages

27 |
28 | {{#tweak_release}} 29 | 30 |
31 | 32 |
33 |

{{name}}

34 |

{{developer.name}}

35 |
36 |
37 |
38 | {{/tweak_release}} 39 |
40 | 41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Styles/tweak.mustache: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{tweak_name}} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 |
26 |

{{tweak_name}}

27 |

{{tweak_developer}}

28 |
29 |
30 | 34 |
35 |

Compatibility: This package is compatible with iOS {{tweak_compatibility}}.

36 | {{{tweak_carousel}}} 37 |
38 |

{{{tweak_description}}}

39 |
40 |

Information

41 |
42 |
43 |
Developer
44 |
{{tweak_developer}}
45 |

46 |
47 |
48 |
Version
49 |
{{tweak_version}}
50 |

51 |
52 |
53 |
Compatibility
54 |
{{tweak_compatibility}}
55 |

56 |
57 |
58 |
Section
59 |
{{tweak_section}}
60 |

61 |
62 |
63 | {{#source}} 64 | 65 |
View Source Code
66 |
67 | {{/source}} 68 |
69 |
70 | {{{tweak_changelog}}} 71 |
72 | 73 |
74 | 75 | 76 | -------------------------------------------------------------------------------- /index.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Import stuff. 4 | import shutil # Used to copy files 5 | import os # Used for identifying project root 6 | from pathlib import Path # Identify if folder exists. 7 | import re # Regular expressions 8 | import json # API endpoint generation 9 | 10 | # Import Silica classes. 11 | from util.DepictionGenerator import DepictionGenerator 12 | from util.PackageLister import PackageLister 13 | from util.DebianPackager import DebianPackager 14 | 15 | version = "1.2.2" 16 | 17 | 18 | def main(): 19 | print("Silica Compiler {0}".format(version)) 20 | 21 | ########### 22 | # Step 0: Clean up "docs" and "temp" folder 23 | ########### 24 | 25 | root = os.path.dirname(os.path.abspath(__file__)) + "/" 26 | 27 | # Remove everything except for the DEBs. 28 | DepictionGenerator.CleanUp() 29 | 30 | try: 31 | shutil.rmtree(root + "temp/") 32 | except Exception: 33 | pass 34 | 35 | ########### 36 | # Step 1: Generate folders, files, and variables needed (including temp) 37 | ########### 38 | PackageLister.CreateFolder("docs") 39 | PackageLister.CreateFolder("docs/web") 40 | PackageLister.CreateFolder("docs/depiction") 41 | PackageLister.CreateFolder("docs/depiction/web") 42 | PackageLister.CreateFolder("docs/depiction/native") 43 | PackageLister.CreateFolder("docs/depiction/native/help") 44 | PackageLister.CreateFolder("docs/pkg") 45 | PackageLister.CreateFolder("docs/assets") 46 | PackageLister.CreateFolder("docs/api") 47 | 48 | # Make sure all index.json files are generated (if using DEBs) 49 | DebianPackager.CheckForSilicaData() 50 | 51 | tweak_release = PackageLister.GetTweakRelease() 52 | repo_settings = PackageLister.GetRepoSettings() 53 | 54 | # Create folder for each tweak 55 | for tweak in tweak_release: 56 | PackageLister.CreateFolder("docs/assets/" + tweak['bundle_id']) 57 | 58 | ########### 59 | # Step 2: Copy all images 60 | ########### 61 | for package_name in PackageLister.ListDirNames(): 62 | package_bundle_id = PackageLister.DirNameToBundleID(package_name) 63 | 64 | try: 65 | shutil.copy(root + "Packages/" + package_name + "/silica_data/icon.png", 66 | root + "docs/assets/" + package_bundle_id + "/icon.png") 67 | except Exception: 68 | category = PackageLister.ResolveCategory(tweak_release, package_bundle_id) 69 | category = re.sub(r'\([^)]*\)', '', category).strip() 70 | try: 71 | shutil.copy(root + "Styles/Generic/Icon/" + category + ".png", 72 | root + "docs/assets/" + package_bundle_id + "/icon.png") 73 | except Exception: 74 | try: 75 | shutil.copy(root + "Styles/Generic/Icon/Generic.png", 76 | root + "docs/assets/" + package_bundle_id + "/icon.png") 77 | except Exception: 78 | PackageLister.ErrorReporter("Configuration Error!", "You are missing a file at " + root + 79 | "Styles/Generic/Icon/Generic.png. Please place an icon here to be the repo's default.") 80 | 81 | try: 82 | shutil.copy(root + "Packages/" + package_name + "/silica_data/banner.png", 83 | root + "docs/assets/" + package_bundle_id + "/banner.png") 84 | except Exception: 85 | category = PackageLister.ResolveCategory(tweak_release, package_bundle_id) 86 | category = re.sub(r'\([^)]*\)', '', category).strip() 87 | try: 88 | shutil.copy(root + "Styles/Generic/Banner/" + category + ".png", 89 | root + "docs/assets/" + package_bundle_id + "/banner.png") 90 | except Exception: 91 | try: 92 | shutil.copy(root + "Styles/Generic/Banner/Generic.png", 93 | root + "docs/assets/" + package_bundle_id + "/banner.png") 94 | except Exception: 95 | PackageLister.ErrorReporter("Configuration Error!", "You are missing a file at " + root + 96 | "Styles/Generic/Banner/Generic.png. Please place a banner here to be the repo's default.") 97 | 98 | try: 99 | shutil.copy(root + "Packages/" + package_name + "/silica_data/description.md", 100 | root + "docs/assets/" + package_bundle_id + "/description.md") 101 | except Exception: 102 | pass 103 | 104 | try: 105 | shutil.copytree(root + "Packages/" + package_name + "/silica_data/screenshots", 106 | root + "docs/assets/" + package_bundle_id + "/screenshot") 107 | except Exception: 108 | pass 109 | try: 110 | shutil.copy(root + "Styles/icon.png", root + "docs/CydiaIcon.png") 111 | except Exception: 112 | PackageLister.ErrorReporter("Configuration Error!", "You are missing a file at " + root + "Styles/icon.png. " 113 | "Please add a PNG here to act as the repo's icon.") 114 | 115 | ########### 116 | # Step 3: Generate HTML depictions and copy stylesheet 117 | ########### 118 | 119 | # Copy CSS and JS over 120 | shutil.copy(root + "Styles/index.css", root + "docs/web/index.css") 121 | shutil.copy(root + "Styles/index.js", root + "docs/web/index.js") 122 | 123 | # Generate index.html 124 | index_html = DepictionGenerator.RenderIndexHTML() 125 | PackageLister.CreateFile("docs/index.html", index_html) 126 | PackageLister.CreateFile("docs/404.html", index_html) 127 | 128 | # Generate per-tweak depictions 129 | for tweak_data in tweak_release: 130 | tweak_html = DepictionGenerator.RenderPackageHTML(tweak_data) 131 | PackageLister.CreateFile("docs/depiction/web/" + tweak_data['bundle_id'] + ".html", tweak_html) 132 | 133 | if "github.io" not in repo_settings['cname'].lower(): 134 | PackageLister.CreateFile("docs/CNAME", repo_settings['cname']) 135 | 136 | ########### 137 | # Step 4: Generate Sileo depictions and featured JSON 138 | ########### 139 | 140 | # Generate sileo-featured.json 141 | carousel_obj = DepictionGenerator.NativeFeaturedCarousel(tweak_release) 142 | PackageLister.CreateFile("docs/sileo-featured.json", carousel_obj) 143 | 144 | # Generate per-tweak depictions 145 | for tweak_data in tweak_release: 146 | tweak_json = DepictionGenerator.RenderPackageNative(tweak_data) 147 | PackageLister.CreateFile("docs/depiction/native/" + tweak_data['bundle_id'] + ".json", tweak_json) 148 | help_depiction = DepictionGenerator.RenderNativeHelp(tweak_data) 149 | PackageLister.CreateFile("docs/depiction/native/help/" + tweak_data['bundle_id'] + ".json", help_depiction) 150 | 151 | ########### 152 | # Step 5: Generate Release file from settings.json. 153 | ########### 154 | 155 | release_file = DebianPackager.CompileRelease(repo_settings) 156 | PackageLister.CreateFile("docs/Release", release_file) 157 | 158 | ########### 159 | # Step 6: Copy packages to temp 160 | ########### 161 | 162 | # You should remove .DS_Store files AFTER copying to temp. 163 | PackageLister.CreateFolder("temp") 164 | for package_name in PackageLister.ListDirNames(): 165 | bundle_id = PackageLister.DirNameToBundleID(package_name) 166 | try: 167 | shutil.copytree(root + "Packages/" + package_name, root + "temp/" + bundle_id) 168 | shutil.rmtree(root + "temp/" + bundle_id + "/silica_data") 169 | except Exception: 170 | try: 171 | shutil.rmtree(root + "temp/" + bundle_id + "/silica_data") 172 | except Exception: 173 | pass 174 | 175 | script_check = Path(root + "Packages/" + package_name + "/silica_data/scripts/") 176 | if script_check.is_dir(): 177 | shutil.copytree(root + "Packages/" + package_name + "/silica_data/scripts", root + "temp/" + bundle_id 178 | + "/DEBIAN") 179 | else: 180 | PackageLister.CreateFolder("temp/" + bundle_id + "/DEBIAN") 181 | 182 | ########### 183 | # Step 7: Generate CONTROL and DEB files and move them to docs/ 184 | ########### 185 | 186 | for tweak_data in tweak_release: 187 | control_file = DebianPackager.CompileControl(tweak_data, repo_settings) 188 | PackageLister.CreateFile("temp/" + tweak_data['bundle_id'] + "/DEBIAN/control", control_file) 189 | DebianPackager.CreateDEB(tweak_data['bundle_id'], tweak_data['version']) 190 | shutil.copy(root + "temp/" + tweak_data['bundle_id'] + ".deb", root + "docs/pkg/" + tweak_data['bundle_id'] 191 | + ".deb") 192 | 193 | ########### 194 | # Step 8: Generate Package file and hash/sign the Release file. 195 | ########### 196 | 197 | DebianPackager.CompilePackages() 198 | DebianPackager.SignRelease() 199 | 200 | ########### 201 | # Step 9: Make API endpoints 202 | ########### 203 | 204 | PackageLister.CreateFile("docs/api/tweak_release.json", json.dumps(tweak_release, separators=(',', ':'))) 205 | PackageLister.CreateFile("docs/api/repo_settings.json", json.dumps(repo_settings, separators=(',', ':'))) 206 | PackageLister.CreateFile("docs/api/about.json", json.dumps(DepictionGenerator.SilicaAbout(), separators=(',', ':'))) 207 | 208 | ########### 209 | # Step 10: Push to GitHub 210 | ########### 211 | 212 | shutil.rmtree(root + "temp/") # Clean-up the now-unneeded temp folder. 213 | 214 | try: 215 | if repo_settings['automatic_git'].lower() == "true": 216 | DebianPackager.PushToGit() # Push the repo to GitHub automatically. 217 | except Exception: 218 | pass 219 | 220 | 221 | if __name__ == '__main__': 222 | DepictionGenerator = DepictionGenerator(version) 223 | PackageLister = PackageLister(version) 224 | DebianPackager = DebianPackager(version) 225 | main() 226 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pathlib 2 | pystache 3 | datetime 4 | mistune 5 | arpy 6 | Pillow 7 | pydpkg -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Silica Configuration Utility" 3 | echo "" 4 | if [ ! -f .is_setup ]; then 5 | echo "We'll ask you a few questions and set the basics up for you!" 6 | echo "Please read the README file for a complete list of dependencies and how to install them." 7 | 8 | echo "Give us a second to install the Python dependencies." 9 | pip3 install -r requirements.txt 10 | if [ $? -eq 2 ]; then 11 | echo "Error: Something went wrong. We may need root to continue. Please put in your password to continue." 12 | sudo pip3 install -r requirements.txt 13 | if [ $? -eq 2 ]; then 14 | echo "Error: Cannot install Python dependencies. Check your permissions and try again." 15 | exit 16 | fi 17 | fi 18 | echo "Installed all required packages! Now, just a few questions about you." 19 | echo "" 20 | printf "What should your repo be called? " 21 | read silica_repo_name 22 | printf "Can you briefly describe what your repo is about? " 23 | read silica_repo_description 24 | printf "What domain are you going to host the repo on (don't include https://, just the domain)? " 25 | read silica_repo_cname 26 | printf "What is *your* name? " 27 | read silica_repo_maintainer_name 28 | printf "What's your email? " 29 | read silica_repo_maintainer_email 30 | printf "With Sileo, you can customize your repo's tint color. Can you provide a hex code to do so? " 31 | read silica_repo_tint 32 | printf "Would you like Silica to automatically push the repo to a Git server when run? (true/false) " 33 | read silica_auto_git 34 | 35 | mkdir "Packages" 36 | 37 | printf "{ 38 | \"name\": \"$silica_repo_name\", 39 | \"description\": \"$silica_repo_description\", 40 | \"tint\": \"$silica_repo_tint\", 41 | \"cname\": \"$silica_repo_cname\", 42 | \"maintainer\": { 43 | \"name\": \"$silica_repo_maintainer_name\", 44 | \"email\": \"$silica_repo_maintainer_email\" 45 | }, 46 | \"automatic_git\": \"$silica_auto_git\" 47 | }" > Styles/settings.json 48 | 49 | echo "" 50 | echo "Thank you! Now let us generate you some GPG keys. Keep these somewhere safe or you may not be able to edit your repo anymore!" 51 | echo " ENTROPY NOTICE: Please do stuff in the meantime like spam some keys or wiggling your mouse. We need entropy!" 52 | gpg --batch --gen-key util/gpg.batchgen 53 | echo "Exported key into your GPG keyring. This computer is the only computer that can be used for Silica (under default settings)." 54 | 55 | echo "" 56 | echo "Remember: for GitHub Pages, you have to go into your repository's settings on github.com and enable GitHub Pages for /docs." 57 | echo "Silica was successfully configured!" 58 | echo "" 59 | echo "To add packages to your repo, take a look at the pre-bundled file in Packages/ or use the UI to manage/create them from DEBs." 60 | touch .is_setup 61 | else 62 | echo "Silica has already been set up. If you are moving machines, please delete .is_setup and ensure the GPG keyring is carried over. Aborting..." 63 | fi -------------------------------------------------------------------------------- /util/DebianPackager.py: -------------------------------------------------------------------------------- 1 | import os # For file path correction 2 | import hashlib # SHA256 for Release file 3 | import re # Regular expressions. 4 | import json # Used to parse various JSON files 5 | import platform # Detect use of WSL. 6 | from subprocess import call # Call dpkg-deb 7 | from pydpkg import Dpkg # Retrieve data from DEBs 8 | from util.PackageLister import PackageLister 9 | from util.DpkgPy import DpkgPy 10 | import shutil # Used to copy files 11 | 12 | 13 | class DebianPackager(object): 14 | """ 15 | DebianPackager deals with making a functional repo and deals 16 | with dpkg-deb and dpkg-scanpackages. 17 | """ 18 | 19 | def __init__(self, version): 20 | super(DebianPackager, self).__init__() 21 | self.version = version 22 | self.root = os.path.dirname(os.path.abspath(__file__)) + "/../" 23 | self.PackageLister = PackageLister(self.version) 24 | 25 | def CompileRelease(self, repo_settings): 26 | """ 27 | Compiles a Release file from a repo_settings object 28 | 29 | Object repo_settings: An object of repo settings. 30 | """ 31 | release_file = "Origin: " + repo_settings['name'] + "\n" 32 | release_file += "Label: " + repo_settings['name'] + "\n" 33 | release_file += "Suite: stable\n" 34 | release_file += "Version: 1.0\n" 35 | release_file += "Codename: ios\n" 36 | release_file += "Architectures: iphoneos-arm\n" 37 | release_file += "Components: main\n" 38 | release_file += "Description: " + repo_settings['description'].replace("\n\n", "\n .\n ").replace("\n", "\n ") + "\n" 39 | 40 | return release_file 41 | 42 | def CompileControl(self, tweak_data, repo_settings): 43 | """ 44 | Compiles a CONTROL file from a tweak_data object 45 | 46 | Object tweak_data: A single index of a "tweak release" object. 47 | Object repo_settings: An object of repo settings. 48 | """ 49 | subfolder = PackageLister.FullPathCname(self, repo_settings) 50 | 51 | control_file = "Architecture: iphoneos-arm\n" 52 | # Mandatory properties include name, bundle id, and version. 53 | control_file += "Package: " + tweak_data['bundle_id'] + "\n" 54 | control_file += "Name: " + tweak_data['name'] + "\n" 55 | control_file += "Version: " + tweak_data['version'] + "\n" 56 | # Known properties 57 | control_file += "Depiction: https://" + repo_settings['cname'] + subfolder + "/depiction/web/" + tweak_data[ 58 | 'bundle_id'] \ 59 | + ".html\n" 60 | control_file += "SileoDepiction: https://" + repo_settings['cname'] + subfolder + "/depiction/native/" \ 61 | + tweak_data['bundle_id'] + ".json\n" 62 | control_file += "ModernDepiction: https://" + repo_settings['cname'] + subfolder + "/depiction/native/" \ 63 | + tweak_data['bundle_id'] + ".json\n" 64 | control_file += "Icon: https://" + repo_settings['cname'] + subfolder + "/assets/" + tweak_data[ 65 | 'bundle_id'] + "/icon.png\n" 66 | 67 | # Optional properties 68 | try: 69 | if tweak_data['tagline']: 70 | # APT note: Multi-line descriptions are in the spec, but must be indicated with a leading space. 71 | control_file += "Description: " + tweak_data['tagline'].replace("\n\n", "\n .\n ").replace("\n", "\n ") + "\n" 72 | except Exception: 73 | control_file += "Description: An awesome package!\n" 74 | 75 | try: 76 | if tweak_data['homepage']: 77 | control_file += "Homepage: " + tweak_data['homepage'] + "\n" 78 | except Exception: 79 | pass 80 | 81 | try: 82 | if tweak_data['section']: 83 | control_file += "Section: " + tweak_data['section'] + "\n" 84 | except Exception: 85 | control_file += "Section: Unknown\n" 86 | 87 | try: 88 | if tweak_data['pre_dependencies']: 89 | control_file += "Pre-Depends: " + tweak_data['pre_dependencies'] + "\n" 90 | except Exception: 91 | pass 92 | 93 | try: 94 | if tweak_data['dependencies']: 95 | control_file += "Depends: firmware (>=" + tweak_data['works_min'] + "), " + tweak_data[ 96 | 'dependencies'] + "\n" 97 | except Exception: 98 | control_file += "Depends: firmware (>=" + tweak_data['works_min'] + ")\n" 99 | 100 | try: 101 | if tweak_data['conflicts']: 102 | control_file += "Conflicts: " + tweak_data['conflicts'] + "\n" 103 | except Exception: 104 | pass 105 | 106 | try: 107 | if tweak_data['replaces']: 108 | control_file += "Replaces: " + tweak_data['replaces'] + "\n" 109 | except Exception: 110 | pass 111 | 112 | try: 113 | if tweak_data['provides']: 114 | control_file += "Provides: " + tweak_data['provides'] + "\n" 115 | except Exception: 116 | pass 117 | 118 | try: 119 | if tweak_data['build_depends']: 120 | control_file += "Build-Depends: " + tweak_data['build_depends'] + "\n" 121 | except Exception: 122 | pass 123 | 124 | try: 125 | if tweak_data['recommends']: 126 | control_file += "Recommends: " + tweak_data['recommends'] + "\n" 127 | except Exception: 128 | pass 129 | 130 | try: 131 | if tweak_data['suggests']: 132 | control_file += "Suggests: " + tweak_data['suggests'] + "\n" 133 | except Exception: 134 | pass 135 | 136 | try: 137 | if tweak_data['enhances']: 138 | control_file += "Enhances: " + tweak_data['enhances'] + "\n" 139 | except Exception: 140 | pass 141 | 142 | try: 143 | if tweak_data['breaks']: 144 | control_file += "Breaks: " + tweak_data['breaks'] + "\n" 145 | except Exception: 146 | pass 147 | try: 148 | if tweak_data['tags']: 149 | control_file += "Tags: compatible_min::ios" + tweak_data['works_min'] + ", compatible_max::ios" + tweak_data['works_max'] + ", " + tweak_data['tags'] + "\n" 150 | except Exception: 151 | control_file += "Tags: compatible_min::ios" + tweak_data['works_min'] + ", compatible_max::ios" + tweak_data['works_max'] + "\n" 152 | 153 | try: 154 | if tweak_data['developer']: 155 | try: 156 | if tweak_data['developer']['email']: 157 | control_file += "Author: " + tweak_data['developer']['name'] + " <" + tweak_data['developer'][ 158 | 'email'] + ">\n" 159 | except Exception: 160 | control_file += "Author: " + tweak_data['developer']['name'] + "\n" 161 | except Exception: 162 | control_file += "Author: Unknown\n" 163 | 164 | try: 165 | if tweak_data['maintainer']['email']: 166 | control_file += "Maintainer: " + tweak_data['maintainer']['name'] + " <" \ 167 | + tweak_data['maintainer']['email'] + ">\n" 168 | except Exception: 169 | try: 170 | control_file += "Maintainer: " + tweak_data['maintainer']['name'] + "\n" 171 | except Exception: 172 | try: 173 | if tweak_data['developer']['email']: 174 | control_file += "Maintainer: " + tweak_data['developer']['name'] + " <" \ 175 | + tweak_data['developer']['email'] + ">\n" 176 | except Exception: 177 | try: 178 | control_file += "Maintainer: " + tweak_data['developer']['name'] + "\n" 179 | except Exception: 180 | control_file += "Maintainer: Unknown\n" 181 | 182 | try: 183 | if tweak_data['sponsor']: 184 | try: 185 | if tweak_data['sponsor']['email']: 186 | control_file += "Sponsor: " + tweak_data['sponsor']['name'] + " <" + tweak_data['sponsor'][ 187 | 'email'] + ">\n" 188 | except Exception: 189 | control_file += "Sponsor: " + tweak_data['sponsor']['name'] + "\n" 190 | except Exception: 191 | pass 192 | 193 | # other_control 194 | try: 195 | if tweak_data['other_control']: 196 | for line in tweak_data['other_control']: 197 | control_file += line + "\n" 198 | except Exception: 199 | pass 200 | 201 | return control_file 202 | 203 | def CreateDEB(self, bundle_id, recorded_version): 204 | """ 205 | Creates a DEB from information stored in the "temp" folder. 206 | 207 | String bundle_id: The bundle id of the package to compress. 208 | String recorded_version: 209 | Object tweak_release: A "tweak release" object. 210 | """ 211 | # TODO: Find a Python-based method to safely delete all DS_Store files. 212 | call(["find", ".", "-name", ".DS_Store", "-delete"], 213 | cwd=self.root + "temp/" + bundle_id) # Remove .DS_Store. Kinda finicky. 214 | for file_name in os.listdir(self.root + "temp/" + bundle_id): 215 | if file_name.endswith(".deb"): 216 | # Check if the DEB is a newer version 217 | deb = Dpkg(self.root + "temp/" + bundle_id + "/" + file_name) 218 | if Dpkg.compare_versions(recorded_version, deb.version) == -1: 219 | # Update package stuff 220 | package_name = PackageLister.BundleIdToDirName(self, bundle_id) 221 | with open(self.root + "Packages/" + package_name + "/silica_data/index.json", "r") as content_file: 222 | update_json = json.load(content_file) 223 | update_json['version'] = deb.version 224 | changelog_entry = input("The DEB provided for \"" + update_json['name'] + 225 | "\" has a new version available (" + recorded_version + " -> " + 226 | deb.version + "). What changed in this version?\n(Add multiple lines" + 227 | " by using newline characters [\\n\\n] and use valid Markdown syntax): " 228 | ) 229 | 230 | try: 231 | update_json['changelog'].append( 232 | { 233 | "version": deb.version, 234 | "changes": changelog_entry 235 | } 236 | ) 237 | except Exception: 238 | # Make it a list! 239 | update_json['changelog'] = [] 240 | 241 | update_json['changelog'].append( 242 | { 243 | "version": deb.version, 244 | "changes": changelog_entry 245 | } 246 | ) 247 | 248 | # A small note: We already created the variables that contain the changelogs and, to 249 | # make matters worse, all the web assets. The only way to mitigate this is to re-create the 250 | # tweak_release variable again, which wrecks a lot of things (ie runtime). 251 | # A Silica rewrite is required to properly fix this bug. 252 | print("\nA small warning about adding changelogs mid-run:\n") 253 | print("Due to some less-than-ideal design decisions with Silica, for the changelog to show") 254 | print("up, you're going to have to run Silica again. Yes, I know this is annoying, and a proper") 255 | print("solution is in the works, but the under-the-hood changes that'll be needed to fix") 256 | print("it properly would require a rewrite [see issue #22].\n") 257 | print("I'm deeply sorry about this.\n - Shuga.\n") 258 | 259 | # Get human-readable folder name 260 | folder = PackageLister.BundleIdToDirName(self, bundle_id) 261 | deb_path = self.root + "Packages/" + folder + "/" + file_name 262 | # Extract Control file and scripts from DEB 263 | DpkgPy.control_extract(self, deb_path, self.root + "Packages/" + folder + 264 | "/silica_data/scripts/") 265 | # Remove the Control; it's not needed. 266 | os.remove(self.root + "Packages/" + folder + "/silica_data/scripts/control") 267 | if not os.listdir(self.root + "Packages/" + folder + "/silica_data/scripts/"): 268 | os.rmdir(self.root + "Packages/" + folder + "/silica_data/scripts/") 269 | 270 | return_str = json.dumps(update_json) 271 | print("Updating package index.json...") 272 | PackageLister.CreateFile(self, "Packages/" + package_name + 273 | "/silica_data/index.json", return_str) 274 | pass 275 | DpkgPy.extract(self, self.root + "temp/" + bundle_id + "/" + file_name, self.root + "temp/" + bundle_id) 276 | try: 277 | os.remove(self.root + "temp/" + bundle_id + "/" + file_name) 278 | except: 279 | pass 280 | try: 281 | os.remove(self.root + "temp/" + bundle_id + "/control") 282 | except: 283 | pass 284 | else: 285 | # TODO: Update DpkgPy to generate DEB files without dependencies (for improved win32 support) 286 | # If the version is consistent, then assume the package is unchanged. Don't regenerate it. 287 | try: 288 | # Check for a DEB that already exists. 289 | docs_deb = Dpkg(self.root + "docs/pkg/" + bundle_id + ".deb") 290 | if docs_deb.version == recorded_version: 291 | shutil.copy(self.root + "docs/pkg/" + bundle_id + ".deb", self.root + "temp/" + bundle_id + ".deb") 292 | call_result = 0; 293 | else: 294 | # Sneaky swap. 295 | call_result = call(["dpkg-deb", "-b", "-Zgzip", self.root + "temp/" + bundle_id], cwd=self.root + "temp/") # Compile DEB 296 | except: 297 | # Create the DEB again. 298 | call_result = call(["dpkg-deb", "-b", "-Zgzip", self.root + "temp/" + bundle_id], cwd=self.root + "temp/") # Compile DEB 299 | if call_result != 0: 300 | # Did we run within WSL? 301 | if "Microsoft" in platform.release(): 302 | PackageLister.ErrorReporter(self, "Platform Error!", "dpkg-deb failed to run. " 303 | "This is likely due to improper configuration of WSL. Please check the Silcia README for " 304 | "how to set up WSL for dpkg-deb.") 305 | else: 306 | PackageLister.ErrorReporter(self, "DPKG Error!", "dpkg-deb failed to run. " 307 | "This could be due to a faulty system configuration.") 308 | 309 | def CheckForSilicaData(self): 310 | """ 311 | Ensures that a silica_data file exists and if it doesn't, try to create one with as much data as we have. 312 | If there is a DEB file, it will take data from its CONTROL file. It will also auto-update the version number. 313 | If there is no DEB file, it will use the name of the folder, version 1.0.0, try to guess some dependencies, 314 | and add some placeholder data. 315 | :return: 316 | """ 317 | for folder in os.listdir(self.root + "Packages"): 318 | if folder.lower() != ".ds_store": 319 | if not os.path.isdir(self.root + "Packages/" + folder + "/silica_data"): 320 | print("It seems like the package \"" + folder + "\" is not configured. Let's set it up!") 321 | is_deb = False 322 | deb_path = "" 323 | try: 324 | for file_name in os.listdir(self.root + "Packages/" + folder): 325 | if file_name.endswith(".deb"): 326 | is_deb = True 327 | deb_path = self.root + "Packages/" + folder + "/" + file_name 328 | except Exception: 329 | PackageLister.ErrorReporter(self, "Configuration Error!", "Please put your .deb file inside of " 330 | "its own folder. The \"Packages\" directory should be made of multiple folders that each " 331 | "contain data for a single package.\n Please fix this issue and try again.") 332 | 333 | # This will be the default scaffolding for our package. Eventually I'll neuter it to only be the 334 | # essential elements; it's also kinda a reference to me. 335 | output = { 336 | "bundle_id": "co.shuga.silica.unknown", 337 | "name": "Unknown Package", 338 | "version": "1.0.0", 339 | "tagline": "An unknown package.", 340 | "homepage": "https://shuga.co/", 341 | "developer": { 342 | "name": "Unknown", 343 | "email": "idk@example.com" 344 | }, 345 | "maintainer": { 346 | "name": "Unknown", 347 | "email": "idk@example.com" 348 | }, 349 | "section": "Themes", 350 | 351 | "works_min": "8.0", 352 | "works_max": "13.0", 353 | "featured": "false" 354 | } 355 | 356 | if is_deb: 357 | print("Extracting data from DEB...") 358 | deb = Dpkg(deb_path) 359 | output['name'] = deb.headers['Name'] 360 | output['bundle_id'] = deb.headers['Package'] 361 | try: 362 | output['tagline'] = deb.headers['Description'] 363 | except Exception: 364 | output['tagline'] = input("What is a brief description of the package? ") 365 | try: 366 | output['homepage'] = deb.headers['Homepage'] 367 | except Exception: 368 | pass 369 | try: 370 | remove_email_regex = re.compile('<.*?>') 371 | output['developer']['name'] = remove_email_regex.sub("", deb.headers['Author']) 372 | except Exception: 373 | output['developer']['name'] = input("Who originally made this package? This may be" 374 | " your name. ") 375 | output['developer']['email'] = input("What is the original author's email address? ") 376 | try: 377 | remove_email_regex = re.compile('<.*?>') 378 | output['maintainer']['name'] = remove_email_regex.sub("", deb.headers['Maintainer']) 379 | except Exception: 380 | output['maintainer']['name'] = input("Who maintains this package now?" 381 | " This is likely your name. ") 382 | output['maintainer']['email'] = input("What is the maintainer's email address? ") 383 | try: 384 | output['sponsor']['name'] = remove_email_regex.sub("", deb.headers['Sponsor']) 385 | except Exception: 386 | pass 387 | try: 388 | output['dependencies'] = deb.headers['Depends'] 389 | except Exception: 390 | pass 391 | try: 392 | output['section'] = deb.headers['Section'] 393 | except Exception: 394 | pass 395 | try: 396 | output['version'] = deb.headers['Version'] 397 | except Exception: 398 | output['version'] = "1.0.0" 399 | try: 400 | output['conflicts'] = deb.headers['Conflicts'] 401 | except Exception: 402 | pass 403 | try: 404 | output['replaces'] = deb.headers['Replaces'] 405 | except Exception: 406 | pass 407 | try: 408 | output['provides'] = deb.headers['Provides'] 409 | except Exception: 410 | pass 411 | try: 412 | output['build_depends'] = deb.headers['Build-Depends'] 413 | except Exception: 414 | pass 415 | try: 416 | output['recommends'] = deb.headers['Recommends'] 417 | except Exception: 418 | pass 419 | try: 420 | output['suggests'] = deb.headers['Suggests'] 421 | except Exception: 422 | pass 423 | try: 424 | output['enhances'] = deb.headers['Enhances'] 425 | except Exception: 426 | pass 427 | try: 428 | output['breaks'] = deb.headers['Breaks'] 429 | except Exception: 430 | pass 431 | try: 432 | output['tags'] = deb.headers['Tag'] 433 | except Exception: 434 | pass 435 | try: 436 | output['suggests'] = deb.headers['Suggests'] 437 | except Exception: 438 | pass 439 | # These still need data. 440 | output['works_min'] = input("What is the lowest iOS version the package works on? ") 441 | output['works_max'] = input("What is the highest iOS version the package works on? ") 442 | output['featured'] = input("Should this package be featured on your repo? (true/false) ") 443 | set_tint = input("What would you like this package's tint color to be? To keep it at" 444 | " the default, leave this blank: ") 445 | if set_tint != "": 446 | output['tint'] = set_tint 447 | print("All done! Please look over the generated \"index.json\" file and consider populating the" 448 | " \"silica_data\" folder with a description, screenshots, and an icon.") 449 | # Extract Control file and scripts from DEB 450 | DpkgPy.control_extract(self, deb_path, self.root + "Packages/" + folder + 451 | "/silica_data/scripts/") 452 | # Remove the Control; it's not needed. 453 | os.remove(self.root + "Packages/" + folder + "/silica_data/scripts/Control") 454 | if not os.listdir(self.root + "Packages/" + folder + "/silica_data/scripts/"): 455 | os.rmdir(self.root + "Packages/" + folder + "/silica_data/scripts/") 456 | else: 457 | print("Estimating dependencies...") 458 | # Use the filesystem to see if Zeppelin, Anemone, LockGlyph, XenHTML, and similar. 459 | # If one of these are found, set it as a dependency. 460 | # If multiple of these are found, use a hierarchy system, with Anemone as the highest priority, 461 | # for determining the category. 462 | output['dependencies'] = "" 463 | output['section'] = "Themes" 464 | 465 | if os.path.isdir(self.root + "Packages/" + folder + "/Library/Zeppelin"): 466 | output['section'] = "Themes (Zeppelin)" 467 | output['dependencies'] += "com.alexzielenski.zeppelin, " 468 | 469 | if os.path.isdir(self.root + "Packages/" + folder + "/Library/Application Support/LockGlyph"): 470 | output['section'] = "Themes (LockGlyph)" 471 | output['dependencies'] += "com.evilgoldfish.lockglypgh, " 472 | 473 | if os.path.isdir(self.root + "Packages/" + folder + "/var/mobile/Library/iWidgets"): 474 | output['section'] = "Widgets" 475 | output['dependencies'] += "com.matchstic.xenhtml, " 476 | 477 | if os.path.isdir(self.root + "Packages/" + folder + "/Library/Wallpaper"): 478 | output['section'] = "Wallpapers" 479 | 480 | if os.path.isdir(self.root + "Packages/" + folder + "/Library/Themes"): 481 | output['section'] = "Themes" 482 | output['dependencies'] += "com.anemonetheming.anemone, " 483 | 484 | if output['dependencies'] != "": 485 | output['dependencies'] = output['dependencies'][:-2] 486 | 487 | repo_settings = PackageLister.GetRepoSettings(self) 488 | # Ask for name 489 | output['name'] = input("What should we name this package? ") 490 | # Automatically generate a bundle ID from the package name. 491 | domain_breakup = repo_settings['cname'].split(".")[::-1] 492 | only_alpha_regex = re.compile('[^a-zA-Z]') 493 | machine_safe_name = only_alpha_regex.sub("", output['name']).lower() 494 | output['bundle_id'] = ".".join(str(x) for x in domain_breakup) + "." + machine_safe_name 495 | output['tagline'] = input("What is a brief description of the package? ") 496 | output['homepage'] = "https://" + repo_settings['cname'] 497 | # I could potentially default this to what is in settings.json but attribution may be an issue. 498 | output['developer']['name'] = input("Who made this package? This is likely your name. ") 499 | output['developer']['email'] = input("What is the author's email address? ") 500 | output['works_min'] = input("What is the lowest iOS version the package works on? ") 501 | output['works_max'] = input("What is the highest iOS version the package works on? ") 502 | output['featured'] = input("Should this package be featured on your repo? (true/false) ") 503 | PackageLister.CreateFolder(self, "Packages/" + folder + "/silica_data/") 504 | PackageLister.CreateFile(self, "Packages/" + folder + "/silica_data/index.json", json.dumps(output)) 505 | 506 | def CompilePackages(self): 507 | """ 508 | Creates a Packages.bz2 file. 509 | """ 510 | # TODO: Update DpkgPy to generate DEB files without dependencies (for improved win32 support) 511 | call(["dpkg-scanpackages", "-m", "."], cwd=self.root + "docs/", stdout=open(self.root + "docs/Packages", "w")) 512 | # For this, we're going to have to run it and then get the output. From here, we can make a new file. 513 | shutil.copy(self.root + "docs/Packages", self.root + "docs/Packages2") 514 | call(["bzip2", "Packages"], cwd=self.root + "docs/") 515 | call(["mv", "Packages2", "Packages"], cwd=self.root + "docs/") 516 | call(["xz", "Packages"], cwd=self.root + "docs/") 517 | 518 | def SignRelease(self): 519 | """ 520 | Signs Release to create Release.gpg. Also adds hash for Packages.bz2 in Release. 521 | """ 522 | with open(self.root + "docs/Packages.bz2", "rb") as content_file,\ 523 | open(self.root + "docs/Packages.xz", "rb") as content_file_xz: 524 | bzip_raw = content_file.read() 525 | bzip_sha256_hash = hashlib.sha256(bzip_raw).hexdigest() 526 | bzip_size = os.path.getsize(self.root + "docs/Packages.bz2") 527 | xz_raw = content_file_xz.read() 528 | xz_sha256_hash = hashlib.sha256(xz_raw).hexdigest() 529 | xz_size = os.path.getsize(self.root + "docs/Packages.xz") 530 | with open(self.root + "docs/Release", "a") as text_file: 531 | text_file.write("\nSHA256:\n " + str(bzip_sha256_hash) + " " + str(bzip_size) + " Packages.bz2" 532 | "\n " + str(xz_sha256_hash) + " " + str(xz_size) + " Packages.xz\n") 533 | repo_settings = PackageLister.GetRepoSettings(self) 534 | try: 535 | if repo_settings['enable_gpg'].lower() == "true": 536 | print("Signing repository with GPG...") 537 | key = "Silica MobileAPT Repository" # Most of the time, this is acceptable. 538 | call(["gpg", "-abs", "-u", key, "-o", "Release.gpg", "Release"], cwd=self.root + "docs/") 539 | print("Generated Release.gpg!") 540 | except Exception: 541 | pass 542 | 543 | def PushToGit(self): 544 | """ 545 | Commit and push the repo to a git server (which would likely be GitHub). 546 | """ 547 | # TODO: use GitPython instead of calling Git directly. 548 | call(["git", "add", "."], cwd=self.root) 549 | call(["git", "commit", "-am", "Repo contents updated via Silica"], cwd=self.root) 550 | call(["git", "push"], cwd=self.root) 551 | -------------------------------------------------------------------------------- /util/DepictionGenerator.py: -------------------------------------------------------------------------------- 1 | import pystache # Used for templating of HTML files 2 | import json # Used to parse various JSON files 3 | import datetime # For getting the compile date 4 | import os # For file path correction 5 | import mistune # Markdown parser 6 | import random # If no packages are featured, feature a random one. 7 | from subprocess import check_output # Get upstream URL for API 8 | from util.PackageLister import PackageLister 9 | 10 | 11 | class DepictionGenerator: 12 | """ 13 | DepictionGenerator deals with the rendering and generating of depictions. 14 | 15 | """ 16 | 17 | def __init__(self, version): 18 | super(DepictionGenerator, self).__init__() 19 | self.version = version 20 | self.root = os.path.dirname(os.path.abspath(__file__)) + "/../" 21 | self.PackageLister = PackageLister(self.version) 22 | 23 | def CleanUp(self): 24 | """ 25 | Cleans up some stuff. 26 | """ 27 | # Remove all Silica-generated folders in docs/ except for docs/pkg/. 28 | try: 29 | shutil.rmtree(root + "docs/api") 30 | except Exception: 31 | pass 32 | 33 | try: 34 | shutil.rmtree(root + "docs/assets") 35 | except Exception: 36 | pass 37 | 38 | try: 39 | shutil.rmtree(root + "docs/depiction") 40 | except Exception: 41 | pass 42 | 43 | try: 44 | shutil.rmtree(root + "docs/web") 45 | except Exception: 46 | pass 47 | 48 | # Delete all Silica-generated files in root. 49 | try: 50 | os.remove(self.root + "docs/404.html") 51 | except Exception: 52 | pass 53 | try: 54 | os.remove(self.root + "docs/CNAME") 55 | except Exception: 56 | pass 57 | try: 58 | os.remove(self.root + "docs/CydiaIcon.png") 59 | except Exception: 60 | pass 61 | try: 62 | os.remove(self.root + "docs/index.html") 63 | except Exception: 64 | pass 65 | try: 66 | os.remove(self.root + "docs/Packages") 67 | except Exception: 68 | pass 69 | try: 70 | os.remove(self.root + "docs/Packages.bz2") 71 | except Exception: 72 | pass 73 | try: 74 | os.remove(self.root + "docs/Packages.xz") 75 | except Exception: 76 | pass 77 | try: 78 | os.remove(self.root + "docs/Release") 79 | except Exception: 80 | pass 81 | try: 82 | os.remove(self.root + "docs/sileo-featured.json") 83 | except Exception: 84 | pass 85 | 86 | # Clean up temp. 87 | try: 88 | shutil.rmtree(root + "temp/") 89 | except Exception: 90 | pass 91 | 92 | def RenderPackageHTML(self, tweak_data): 93 | """ 94 | Renders a package's depiction. 95 | 96 | Object tweak_data: A single index of a "tweak release" object. 97 | """ 98 | with open(self.root + "Styles/tweak.mustache", "r") as content_file: 99 | index = content_file.read() 100 | replacements = DepictionGenerator.RenderDataHTML(self) 101 | try: 102 | replacements['tweak_name'] = tweak_data['name'] 103 | except: 104 | PackageLister.ErrorReporter(self, "Configuration Error!", "You are missing a package " 105 | "name in its index.json. Make sure this and other required properties are set.") 106 | try: 107 | replacements['tweak_developer'] = tweak_data['developer']['name'] 108 | replacements['tweak_compatibility'] = "iOS " + tweak_data['works_min'] + " to " + tweak_data['works_max'] 109 | replacements['tweak_version'] = tweak_data['version'] 110 | replacements['tweak_section'] = tweak_data['section'] 111 | replacements['tweak_bundle_id'] = tweak_data['bundle_id'] 112 | replacements['works_min'] = tweak_data['works_min'] 113 | replacements['works_max'] = tweak_data['works_max'] 114 | replacements['tweak_tagline'] = tweak_data['tagline'] 115 | except: 116 | PackageLister.ErrorReporter(self, "Configuration Error!", "You are missing an essential " 117 | "property in " + tweak_data['name'] + "'s index.json. Make sure developer, version, section, " 118 | "bundle id, and tagline are set properly.") 119 | replacements['tweak_carousel'] = DepictionGenerator.ScreenshotCarousel(self, tweak_data) 120 | replacements['tweak_changelog'] = DepictionGenerator.RenderChangelogHTML(self, tweak_data) 121 | replacements['footer'] = DepictionGenerator.RenderFooter(self) 122 | try: 123 | if tweak_data['source'] != "": 124 | replacements['source'] = tweak_data['source'] 125 | except Exception: 126 | pass 127 | 128 | try: 129 | replacements['tint_color'] = tweak_data['tint'] 130 | except Exception: 131 | try: 132 | repo_settings = PackageLister.GetRepoSettings(self) 133 | replacements['tint_color'] = repo_settings['tint'] 134 | except Exception: 135 | replacements['tint_color'] = "#2cb1be" 136 | 137 | try: 138 | with open(self.root + "docs/assets/" + tweak_data['bundle_id'] + "/description.md", "r") as md_file: 139 | raw_md = md_file.read() 140 | desc_md = mistune.markdown(raw_md) 141 | replacements['tweak_description'] = desc_md 142 | except Exception: 143 | replacements['tweak_description'] = tweak_data['tagline'] 144 | 145 | # tweak_carousel 146 | 147 | return pystache.render(index, replacements) 148 | 149 | def RenderPackageNative(self, tweak_data): 150 | """ 151 | Renders a package's depiction using Sileo's "native depiction" format. 152 | 153 | Object tweak_data: A single index of a "tweak release" object. 154 | """ 155 | repo_settings = PackageLister.GetRepoSettings(self) 156 | try: 157 | tint = tweak_data['tint'] 158 | except Exception: 159 | try: 160 | tint = repo_settings['tint'] 161 | except Exception: 162 | tint = "#2cb1be" 163 | 164 | try: 165 | with open(self.root + "docs/assets/" + tweak_data['bundle_id'] + "/description.md", "r") as md_file: 166 | md_txt = md_file.read() 167 | except Exception: 168 | md_txt = tweak_data['tagline'] 169 | 170 | date = datetime.datetime.now().strftime("%Y-%m-%d") 171 | 172 | screenshot_obj = [] 173 | image_list = self.PackageLister.GetScreenshots(tweak_data) 174 | subfolder = PackageLister.FullPathCname(self, repo_settings) 175 | if len(image_list) > 0: 176 | for image in image_list: 177 | screenshot_entry = { 178 | "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + tweak_data['bundle_id'] + "/screenshot/" 179 | + image, 180 | "accessibilityText": "Screenshot" 181 | } 182 | screenshot_obj.append(screenshot_entry) 183 | # The following code is evil, but is actually easier to maintain. My humblest apologies. 184 | screenshot_view_title = "DepictionHeaderView" 185 | screenshot_view_carousel = "DepictionScreenshotsView" 186 | else: 187 | # The following code is evil, but is actually easier to maintain. My humblest apologies. 188 | screenshot_view_title = "HiddenDepictionHeaderView" 189 | screenshot_view_carousel = "HiddenDepictionScreenshotsView" 190 | 191 | changelog = DepictionGenerator.RenderNativeChangelog(self, tweak_data) 192 | screenshot_size = PackageLister.GetScreenshotSize(self, tweak_data) 193 | 194 | depiction = { 195 | "minVersion": "0.1", 196 | "headerImage": "https://" + repo_settings['cname'] + subfolder + "/assets/" + tweak_data['bundle_id'] + "/banner.png", 197 | "tintColor": tint, 198 | "tabs": [ 199 | { 200 | "tabname": "Details", 201 | "views": [ 202 | { 203 | "class": screenshot_view_carousel, 204 | "screenshots": screenshot_obj, 205 | "itemCornerRadius": 8, 206 | "itemSize": screenshot_size 207 | }, 208 | { 209 | "markdown": md_txt, 210 | "useSpacing": "true", 211 | "class": "DepictionMarkdownView" 212 | }, 213 | { 214 | "class": "DepictionSpacerView" 215 | }, 216 | { 217 | "class": "DepictionHeaderView", 218 | "title": "Information", 219 | }, 220 | { 221 | "class": "DepictionTableTextView", 222 | "title": "Developer", 223 | "text": tweak_data['developer']['name'] 224 | }, 225 | { 226 | "class": "DepictionTableTextView", 227 | "title": "Version", 228 | "text": tweak_data['version'] 229 | }, 230 | { 231 | "class": "DepictionTableTextView", 232 | "title": "Compatibility", 233 | "text": "iOS " + tweak_data['works_min'] + " to " + tweak_data['works_max'] 234 | }, 235 | { 236 | "class": "DepictionTableTextView", 237 | "title": "Section", 238 | "text": tweak_data['section'] 239 | }, 240 | { 241 | "class": "DepictionSpacerView" 242 | }, 243 | { 244 | "class": "DepictionTableButtonView", 245 | "title": "Contact Support", 246 | "action": "depiction-https://" + repo_settings['cname'] + subfolder + 247 | "/depiction/native/help/" + tweak_data['bundle_id'] + ".json", 248 | "openExternal": "true", 249 | "tintColor": tint 250 | }, 251 | { 252 | "class": "DepictionLabelView", 253 | "text": DepictionGenerator.RenderFooter(self), 254 | "textColor": "#999999", 255 | "fontSize": "10.0", 256 | "alignment": 1 257 | } 258 | ], 259 | "class": "DepictionStackView" 260 | }, 261 | { 262 | "tabname": "Changelog", 263 | "views": changelog, 264 | "class": "DepictionStackView" 265 | } 266 | ], 267 | "class": "DepictionTabView" 268 | } 269 | 270 | blank = { 271 | "class": "DepictionSpacerView" 272 | } 273 | 274 | try: 275 | if tweak_data['source'] != "": 276 | source_btn = { 277 | "class": "DepictionTableButtonView", 278 | "title": "View Source Code", 279 | "action": tweak_data['source'], 280 | "openExternal": "true", 281 | "tintColor": tint 282 | } 283 | depiction['tabs'][0]['views'].insert(8, source_btn) 284 | depiction['tabs'][0]['views'].insert(8, blank) 285 | pass 286 | except Exception: 287 | pass 288 | 289 | return json.dumps(depiction, separators=(',', ':')) 290 | 291 | def RenderNativeChangelog(self, tweak_data): 292 | """ 293 | Generates a changelog for use in native depictions. 294 | 295 | Object tweak_data: A single index of a "tweak release" object. 296 | """ 297 | try: 298 | changelog = [] 299 | for version in tweak_data['changelog'][::-1]: 300 | ver_entry = { 301 | "class": "DepictionMarkdownView", 302 | "markdown": "#### Version {0}\n\n{1}".format(version['version'], version['changes']), 303 | } 304 | changelog.append(ver_entry) 305 | changelog.append({ 306 | "class": "DepictionLabelView", 307 | "text": DepictionGenerator.RenderFooter(self), 308 | "textColor": "#999999", 309 | "fontSize": "10.0", 310 | "alignment": 1 311 | }) 312 | return changelog 313 | except Exception: 314 | return [ 315 | { 316 | "class": "DepictionHeaderView", 317 | "title": "Changelog" 318 | }, 319 | { 320 | "class": "DepictionMarkdownView", 321 | "markdown": "This package has no changelog.", 322 | }, 323 | { 324 | "class": "DepictionLabelView", 325 | "text": DepictionGenerator.RenderFooter(self), 326 | "textColor": "#999999", 327 | "fontSize": "10.0", 328 | "alignment": 1 329 | } 330 | ] 331 | 332 | def ChangelogEntry(self, version, raw_md): 333 | """ 334 | Generates a div for changelog entries. 335 | 336 | String version: The version number. 337 | String raw_md: The changelog entry text (Markdown-compatible). 338 | """ 339 | return '''
340 |

{0}

341 |
{1}
342 |
'''.format(version, mistune.markdown(raw_md)) 343 | 344 | def RenderChangelogHTML(self, tweak_data): 345 | """ 346 | Generates a div of changelog entries. 347 | 348 | Object tweak_data: A single index of a "tweak release" object. 349 | """ 350 | element = "" 351 | try: 352 | for version in tweak_data['changelog'][::-1]: 353 | element += DepictionGenerator.ChangelogEntry(self, version['version'], version['changes']) 354 | return element 355 | except Exception: 356 | return "This package has no changelog." 357 | 358 | def RenderIndexHTML(self): 359 | """ 360 | Renders the home page (index.html). 361 | """ 362 | repo_settings = PackageLister.GetRepoSettings(self) 363 | with open(self.root + "Styles/index.mustache", "r") as content_file: 364 | index = content_file.read() 365 | replacements = DepictionGenerator.RenderDataHTML(self) 366 | replacements['tint_color'] = repo_settings['tint'] 367 | replacements['footer'] = DepictionGenerator.RenderFooter(self) 368 | replacements['tweak_release'] = PackageLister.GetTweakRelease(self) 369 | return pystache.render(index, replacements) 370 | 371 | def RenderFooter(self): 372 | """ 373 | Renders the footer. 374 | """ 375 | repo_settings = PackageLister.GetRepoSettings(self) 376 | data = DepictionGenerator.RenderDataHTML(self) 377 | try: 378 | footer = pystache.render(repo_settings['footer'], data) 379 | except Exception: 380 | footer = pystache.render("Silica {{silica_version}} – Updated {{silica_compile_date}}", data) 381 | return footer 382 | 383 | def RenderDataBasic(self): 384 | """ 385 | Gets the value of basic repo data to pass to Pystache. 386 | """ 387 | repo_settings = PackageLister.GetRepoSettings(self) 388 | with open(self.root + "Styles/settings.json", "r") as content_file: 389 | data = json.load(content_file) 390 | date = datetime.datetime.now().strftime("%Y-%m-%d") 391 | subfolder = PackageLister.FullPathCname(self, repo_settings) 392 | return { 393 | "silica_version": self.version, 394 | "silica_compile_date": date, 395 | "repo_name": data['name'], 396 | "repo_url": data['cname'] + subfolder, 397 | "repo_desc": data['description'], 398 | "repo_tint": data['tint'] 399 | } 400 | 401 | def RenderDataHTML(self): 402 | data = DepictionGenerator.RenderDataBasic(self) 403 | 404 | tweak_release = PackageLister.GetTweakRelease(self) 405 | 406 | data['repo_packages'] = DepictionGenerator.PackageEntryList(self, tweak_release) 407 | 408 | data['repo_carousel'] = DepictionGenerator.CarouselEntryList(self, tweak_release) 409 | 410 | return data 411 | 412 | def PackageEntry(self, name, author, icon, bundle_id): 413 | """ 414 | Generates a package entry div. 415 | 416 | String name: The package's name 417 | String author: The author's name 418 | String (URL) icon: A URL to an image of the package icon. 419 | 420 | Scope: HTML > Generation > Helpers 421 | """ 422 | 423 | if (bundle_id != "silica_do_not_hyperlink"): 424 | return '''
425 | 426 |
427 |

{1}

428 |

{2}

429 |
430 |
'''.format(icon, name, author, bundle_id) 431 | else: 432 | return '''
433 | 434 |
435 |

{1}

436 |

{2}

437 |
438 |
'''.format(icon, name, author) 439 | 440 | def ScreenshotCarousel(self, tweak_data): 441 | """ 442 | Generates a screenshot div. 443 | 444 | Object tweak_data: A single index of a "tweak release" object. 445 | """ 446 | repo_settings = PackageLister.GetRepoSettings(self) 447 | screenshot_div = "
" 448 | image_list = self.PackageLister.GetScreenshots(tweak_data) 449 | if (len(image_list) > 0): 450 | for image in image_list: 451 | screenshot_div += ''''''.format( 452 | repo_settings['cname'], tweak_data['bundle_id'], image) 453 | screenshot_div += "
" 454 | else: 455 | screenshot_div = "" 456 | return screenshot_div 457 | 458 | def CarouselEntry(self, name, banner, bundle_id): 459 | """ 460 | Generates a card to be used in Featured carousels. 461 | 462 | String name: The package's name 463 | String (URL) banner: A URL to an image of the package banner. 464 | """ 465 | if len(name) > 18: 466 | name = name[:18] + "…" 467 | return ''' 468 |

{2}

469 |
'''.format(bundle_id, banner, name) 470 | 471 | def NativeFeaturedCarousel(self, tweak_release): 472 | """ 473 | Generate a sileo-featured.json file for featured packages. 474 | 475 | Object carousel_entry_list: A "tweak release" object. 476 | """ 477 | repo_settings = PackageLister.GetRepoSettings(self) 478 | subfolder = PackageLister.FullPathCname(self, repo_settings) 479 | banners = [] 480 | for package in tweak_release: 481 | try: 482 | if package['featured'].lower() == "true": 483 | ar_el = { 484 | "package": package['bundle_id'], 485 | "title": package['name'], 486 | "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + package['bundle_id'] + "/banner.png", 487 | "hideShadow": "false" 488 | 489 | } 490 | banners.append(ar_el) 491 | except Exception: 492 | pass 493 | if len(banners) == 0: 494 | try: 495 | featured_int = random.randint(0,(len(tweak_release)-1)) 496 | except Exception: 497 | PackageLister.ErrorReporter(self, "Configuration Error!", "You have no packages added to this repo. " 498 | "Make sure a folder is created at \"" + self.version + "/Packages\" that contains folders with " 499 | "package data inside of them and run Silica again.") 500 | featured_package = tweak_release[featured_int] 501 | ar_el = { 502 | "package": featured_package['bundle_id'], 503 | "title": featured_package['name'], 504 | "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + featured_package['bundle_id'] + "/banner.png", 505 | "hideShadow": "false" 506 | 507 | } 508 | banners.append(ar_el) 509 | 510 | featured_json = { 511 | "class": "FeaturedBannersView", 512 | "itemSize": "{263, 148}", 513 | "itemCornerRadius": 8, 514 | "banners": banners 515 | } 516 | return json.dumps(featured_json, separators=(',', ':')) 517 | 518 | def PackageEntryList(self, tweak_release): 519 | """ 520 | Generate a user-friendly list of packages on the repo. 521 | 522 | Object tweak_release: A "tweak release" object. 523 | """ 524 | list_el = "" 525 | for package in tweak_release: 526 | list_el += DepictionGenerator.PackageEntry(self, package['name'], package['developer']['name'], 527 | "assets/" + package['bundle_id'] + "/icon.png", 528 | package['bundle_id']) 529 | return list_el 530 | 531 | def CarouselEntryList(self, tweak_release): 532 | """ 533 | Generate a carousel of featured packages on the repo. 534 | 535 | Object tweak_release: A "tweak release" object. 536 | """ 537 | list_el = "" 538 | for package in tweak_release: 539 | try: 540 | if package['featured'].lower() == "true": 541 | list_el += DepictionGenerator.CarouselEntry(self, package['name'], 542 | "assets/" + package['bundle_id'] + "/banner.png", 543 | package['bundle_id']) 544 | except Exception: 545 | pass 546 | if list_el == "": 547 | try: 548 | featured_int = random.randint(0,(len(tweak_release)-1)) 549 | except Exception: 550 | PackageLister.ErrorReporter(self, "Configuration Error!", "You have no packages added to this repo." 551 | " Make sure a folder is created at \"" + self.version + 552 | "/Packages\" that contains folders with package data inside of them and run Silica again.") 553 | 554 | featured_package = tweak_release[featured_int] 555 | list_el += DepictionGenerator.CarouselEntry(self, featured_package['name'], 556 | "assets/" + featured_package['bundle_id'] + "/banner.png", 557 | featured_package['bundle_id']) 558 | return list_el 559 | 560 | def SilicaAbout(self): 561 | """ 562 | Returns a JSON object that describes information about the Silica install. 563 | """ 564 | 565 | compile_date = datetime.datetime.now().isoformat() 566 | try: 567 | upstream_url = check_output(["git", "config", "--get", "remote.origin.url"], cwd=self.root).decode("utf-8") 568 | except Exception: 569 | upstream_url = "undefined" 570 | return { 571 | "software": "Silica", 572 | "version": self.version, 573 | "compile_date": compile_date, 574 | "upstream_url": upstream_url 575 | } 576 | 577 | def RenderNativeHelp(self, tweak_data): 578 | """ 579 | Generates a help view for Sileo users. 580 | 581 | Object tweak_data: A single index of a "tweak release" object. 582 | """ 583 | 584 | repo_settings = PackageLister.GetRepoSettings(self) 585 | 586 | try: 587 | tint = tweak_data['tint'] 588 | except Exception: 589 | try: 590 | tint = repo_settings['tint'] 591 | except Exception: 592 | tint = "#2cb1be" 593 | 594 | view = [] 595 | try: 596 | if tweak_data['developer']['email']: 597 | view.append( 598 | { 599 | "class": "DepictionMarkdownView", 600 | "markdown": "If you need help with \"" + tweak_data['name'] + "\", you can contact " 601 | + tweak_data['developer']['name'] + ", the developer, via e-mail." 602 | } 603 | ) 604 | view.append( 605 | { 606 | "class": "DepictionTableButtonView", 607 | "title": "Email Developer", 608 | "action": "mailto:" + tweak_data['developer']['email'], 609 | "openExternal": "true", 610 | "tintColor": tint 611 | } 612 | ) 613 | except Exception: 614 | try: 615 | view.append( 616 | { 617 | "class": "DepictionMarkdownView", 618 | "markdown": "If you need help with \"" + tweak_data['name'] + "\", you can contact " 619 | + tweak_data['developer']['name'] 620 | + ", who is the developer. Sadly, we don't know their email." 621 | } 622 | ) 623 | except Exception: 624 | view.append( 625 | { 626 | "class": "DepictionMarkdownView", 627 | "markdown": "The developer of the package \"" + tweak_data['name'] 628 | + "\" is not known. Try contacting the repo owner for more information." 629 | } 630 | ) 631 | 632 | try: 633 | if tweak_data['social']: 634 | view.append( 635 | { 636 | "class": "DepictionMarkdownView", 637 | "markdown": "You can also contact " + tweak_data['developer']['name'] + " using the following" + 638 | " sites:" 639 | } 640 | ) 641 | for entry in tweak_data['social']: 642 | view.append({ 643 | "class": "DepictionTableButtonView", 644 | "title": entry['name'], 645 | "action": entry['url'], 646 | "openExternal": "true", 647 | "tintColor": tint 648 | }) 649 | except Exception: 650 | pass 651 | 652 | try: 653 | if tweak_data['maintainer']['email']: 654 | view.append( 655 | { 656 | "class": "DepictionMarkdownView", 657 | "markdown": tweak_data['maintainer']['name'] + " is the maintainer of the package \"" + 658 | tweak_data['name'] + "\". Please contact them via email for any questions on this" 659 | " version of the package." 660 | } 661 | ) 662 | view.append( 663 | { 664 | "class": "DepictionTableButtonView", 665 | "title": "Email Maintainer", 666 | "action": "mailto:" + tweak_data['maintainer']['email'], 667 | "openExternal": "true", 668 | "tintColor": tint 669 | } 670 | ) 671 | except Exception: 672 | try: 673 | view.append( 674 | { 675 | "class": "DepictionMarkdownView", 676 | "markdown": "If you need help with \"" + tweak_data['name'] + "\", you should contact " 677 | + tweak_data['maintainer']['name'] 678 | + ", who is the package's current maintainer. Sadly, we don't know their email." 679 | } 680 | ) 681 | except Exception: 682 | pass 683 | 684 | view.append( 685 | { 686 | "class": "DepictionMarkdownView", 687 | "markdown": "If you found a mistake in the depiction or cannot download the package, you can reach out" 688 | + " to the maintainer of the \"" + repo_settings['name'] + "\" repo, " 689 | + repo_settings['maintainer']['name'] + "." 690 | } 691 | ) 692 | view.append( 693 | { 694 | "class": "DepictionTableButtonView", 695 | "title": "Email Repo Maintainer", 696 | "action": "mailto:" + repo_settings['maintainer']['email'], 697 | "openExternal": "true", 698 | "tintColor": tint 699 | } 700 | ) 701 | 702 | try: 703 | if repo_settings['social']: 704 | view.append( 705 | { 706 | "class": "DepictionMarkdownView", 707 | "markdown": "You can also contact the repo owner via the following" + 708 | " sites:" 709 | } 710 | ) 711 | for entry in repo_settings['social']: 712 | view.append({ 713 | "class": "DepictionTableButtonView", 714 | "title": entry['name'], 715 | "action": entry['url'], 716 | "openExternal": "true", 717 | "tintColor": tint 718 | }) 719 | except Exception: 720 | pass 721 | 722 | return json.dumps({ 723 | "class": "DepictionStackView", 724 | "tintColor": tint, 725 | "title": "Contact Support", 726 | "views": view 727 | }, separators=(',', ':')) 728 | -------------------------------------------------------------------------------- /util/DpkgPy.py: -------------------------------------------------------------------------------- 1 | import arpy 2 | import tarfile 3 | 4 | 5 | class DpkgPy: 6 | """ 7 | DpkgPy is a Python library designed to create and manipulate Debian packages in pure Python. 8 | It has no dependencies besides other Python libraries. 9 | 10 | (c) 2019 Shuga Holdings. All rights reserved! 11 | """ 12 | def __init__(self): 13 | super(DpkgPy, self).__init__() 14 | 15 | def extract(self, input_path, output_path): 16 | """ 17 | Extracts data from a DEB file. 18 | :param input_path: A String of the file path of the DEB to extract. 19 | :param output_path: A String of the file path to put the extracted DEB. Folder must already exist. 20 | :return: A Boolean on whether the extraction succeeded or failed. 21 | """ 22 | try: 23 | root_ar = arpy.Archive(input_path) 24 | root_ar.read_all_headers() 25 | try: 26 | data_bin = root_ar.archived_files[b'data.tar.gz'] 27 | data_tar = tarfile.open(fileobj=data_bin) 28 | data_tar.extractall(output_path) 29 | except Exception: 30 | try: 31 | data_theos_bin = root_ar.archived_files[b'data.tar.lzma'] 32 | data_theos_bin.seekable = lambda: True 33 | data_theos_tar = tarfile.open(fileobj=data_theos_bin, mode='r:xz') 34 | data_theos_tar.extractall(output_path) 35 | except Exception: 36 | try: 37 | data_theos_bin = root_ar.archived_files[b'data.tar.xz'] 38 | data_theos_bin.seekable = lambda: True 39 | data_theos_tar = tarfile.open(fileobj=data_theos_bin, mode='r:xz') 40 | data_theos_tar.extractall(output_path) 41 | except Exception: 42 | print("\033[91m- DEB Extraction Error -\n" 43 | "The DEB file inserted for one of your packages is invalid. Please report this as a bug " 44 | "and attach the DEB file at \"" + output_path + "\".\033[0m") 45 | 46 | control_bin = root_ar.archived_files[b'control.tar.gz'] 47 | control_tar = tarfile.open(fileobj=control_bin) 48 | control_tar.extractall(output_path) 49 | return True 50 | except Exception: 51 | return False 52 | 53 | def control_extract(self, input_path, output_path): 54 | """ 55 | Extracts only the Control file(s) from a DEB 56 | :param input_path: A String of the file path of the DEB to extract. 57 | :param output_path: A String of the file path to put the extracted DEB. Folder must already exist. 58 | :return: A Boolean on whether the extraction succeeded or failed. 59 | """ 60 | try: 61 | root_ar = arpy.Archive(input_path) 62 | root_ar.read_all_headers() 63 | control_bin = root_ar.archived_files[b'control.tar.gz'] 64 | control_tar = tarfile.open(fileobj=control_bin) 65 | control_tar.extractall(output_path) 66 | return True 67 | except Exception: 68 | return False 69 | 70 | # TODO: Add support for the creation of DEB files without any dependencies, allowing for improved Windows support. 71 | -------------------------------------------------------------------------------- /util/PackageLister.py: -------------------------------------------------------------------------------- 1 | import json # Used to parse various JSON files 2 | import os # Used to navigate files so we know what tweak folders exist. 3 | from PIL import Image # Used to get image screenshot size. 4 | 5 | 6 | class PackageLister: 7 | """ 8 | PackageLister gathers data on packages that will be in the repo. 9 | It also includes some helper functions related to os. 10 | """ 11 | 12 | def __init__(self, version): 13 | super(PackageLister, self).__init__() 14 | self.version = version 15 | self.root = os.path.dirname(os.path.abspath(__file__)) + "/../" 16 | 17 | def CreateFile(self, path, contents): 18 | """ 19 | Creates a text file (properly). 20 | 21 | String path: A file location to create a file at. Is relative to project root. 22 | String contents: The contents of the file to be created. 23 | """ 24 | with open(self.root + path, "w") as text_file: 25 | text_file.write(contents) 26 | 27 | def CreateFolder(self, path): 28 | """ 29 | Creates a folder. 30 | 31 | String path: A file location to create a folder at. Is relative to project root. 32 | """ 33 | if not os.path.exists(self.root + path): 34 | os.makedirs(self.root + path) 35 | else: 36 | pass 37 | 38 | def ListDirNames(self): 39 | """ 40 | List the file names for package entries. 41 | """ 42 | package_list = [] 43 | for folder in os.listdir(self.root + "Packages"): 44 | if folder.lower() != ".ds_store": 45 | package_list.append(folder) 46 | return package_list 47 | 48 | def GetTweakRelease(self): 49 | """ 50 | Create a "tweak release" object that combines the index.json of every package. 51 | Analogous to Packages.bz2. 52 | """ 53 | tweak_release = [] 54 | for tweakEntry in PackageLister.ListDirNames(self): 55 | with open(self.root + "Packages/" + tweakEntry + "/silica_data/index.json", "r") as content_file: 56 | try: 57 | data = json.load(content_file) 58 | except Exception: 59 | PackageLister.ErrorReporter(self, "Configuration Error!", "The package configuration file at \"" + 60 | self.root + "Packages/" + tweakEntry + "/silica_data/index.json\" is malformatted. Please check" 61 | " for any syntax errors in a JSON linter and run Silica again.") 62 | tweak_release.append(data) 63 | return tweak_release 64 | 65 | def GetScreenshots(self, tweak_data): 66 | """ 67 | Get an array of screenshot names copied over to the static site. 68 | 69 | Object tweak_data: A single index of a "tweak release" object. 70 | """ 71 | image_list = [] 72 | try: 73 | for folder in os.listdir(self.root + "docs/assets/" + tweak_data['bundle_id'] + "/screenshot/"): 74 | if folder.lower() != ".ds_store": 75 | image_list.append(folder) 76 | except: 77 | pass 78 | return image_list 79 | 80 | def GetScreenshotSize(self, tweak_data): 81 | """ 82 | Get the size of a screenshot. 83 | 84 | Object tweak_data: A single index of a "tweak release" object. 85 | """ 86 | try: 87 | for folder in os.listdir(self.root + "docs/assets/" + tweak_data['bundle_id'] + "/screenshot/"): 88 | if folder.lower() != ".ds_store": 89 | with Image.open(self.root + "docs/assets/" + tweak_data['bundle_id'] + "/screenshot/" + folder) as img: 90 | width, height = img.size 91 | # Make sure it's not too big. 92 | # If height > width, make height 300, width proportional. 93 | # If height < width, make width 160, height proportional. 94 | if height > width: 95 | width = round((400 * width)/height) 96 | height = 400 97 | else: 98 | height = round((200 * height) / width) 99 | width = 200 100 | return "{" + str(width) + "," + str(height) + "}" 101 | except Exception: 102 | return False 103 | 104 | def DirNameToBundleID(self, package_name): 105 | """ 106 | Take a human-readable directory name and find the corresponding bundle id. 107 | 108 | String package_name: The name of a folder in Packages/ that holds tweak information. 109 | """ 110 | 111 | with open(self.root + "Packages/" + package_name + "/silica_data/index.json", "r") as content_file: 112 | data = json.load(content_file) 113 | return data['bundle_id'] 114 | 115 | def BundleIdToDirName(self, bundle_id): 116 | """ 117 | Take a bundle id and find the corresponding human-readable directory name. 118 | 119 | String bundle_id: The bundle ID of the tweak. 120 | """ 121 | for package_name in PackageLister.ListDirNames(self): 122 | new_bundle = PackageLister.DirNameToBundleID(self, package_name) 123 | if new_bundle == bundle_id: 124 | return package_name 125 | return None 126 | 127 | def GetRepoSettings(self): 128 | with open(self.root + "Styles/settings.json", "r") as content_file: 129 | try: 130 | return json.load(content_file) 131 | except Exception: 132 | PackageLister.ErrorReporter(self, "Configuration Error!", "The Silica configuration file at \"" + 133 | self.root + "Styles/settings.json\" is malformatted. Please check for any syntax errors in a JSON" 134 | " linter and run Silica again.") 135 | 136 | def FullPathCname(self, repo_settings): 137 | """ 138 | Some people may use a sub-folder like "repo" to put repo contents in. 139 | While this is not recommended, Silica does support this. 140 | 141 | Object repo_settings: An object of repo settings. 142 | """ 143 | try: 144 | if repo_settings['subfolder'] != "": 145 | subfolder = "/" + repo_settings['subfolder'] 146 | except Exception: 147 | subfolder = "" 148 | return subfolder 149 | 150 | def ResolveCategory(self, tweak_release, bundle_id): 151 | """ 152 | Returns the category name when given a bundle ID. 153 | 154 | Object tweak_release: A "tweak release" object. 155 | String bundle_id: The bundle ID of the tweak. 156 | """ 157 | for tweak in tweak_release: 158 | if tweak['bundle_id'] == bundle_id: 159 | return tweak['section'] 160 | 161 | def ResolveVersion(self, tweak_release, bundle_id): 162 | """ 163 | Returns the version when given a bundle ID. 164 | 165 | Object tweak_release: A "tweak release" object. 166 | String bundle_id: The bundle ID of the tweak. 167 | """ 168 | for tweak in tweak_release: 169 | if tweak['bundle_id'] == bundle_id: 170 | return tweak['version'] 171 | 172 | def ErrorReporter(self, title, message): 173 | print('\033[91m- {0} -\n{1}\033[0m'.format(title, message)) 174 | quit() -------------------------------------------------------------------------------- /util/gpg.batchgen: -------------------------------------------------------------------------------- 1 | Key-Type: RSA 2 | Key-Length: 4096 3 | Subkey-Length: 4096 4 | Name-Real: Silica MobileAPT Repository 5 | Expire-Date: 0 --------------------------------------------------------------------------------