├── .editorconfig ├── .github └── workflows │ └── plugin-update.yml ├── .gitignore ├── README.md ├── scripts ├── extract.sh ├── test_existing.sh └── update.sh └── spec ├── README.md ├── legacy └── README.md └── modern ├── README.md ├── contributing.md ├── manifest.md ├── metadata.md ├── reviewing.md └── updater.md /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_size = 4 10 | 11 | [*.{md,markdown}] 12 | max_line_length = 120 13 | indent_size = 2 14 | 15 | [*.{json,yml,yaml}] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.github/workflows/plugin-update.yml: -------------------------------------------------------------------------------- 1 | name: Plugin update 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | repo_owner: 7 | type: string 8 | description: Username of the target repository's owner 9 | required: true 10 | repo_name: 11 | type: string 12 | description: Name of the target repository 13 | required: true 14 | repo_id: 15 | type: string 16 | description: Id of the target repository 17 | required: true 18 | 19 | jobs: 20 | # Unsafe job, no tokens or secrets must be touched at all after build command is run, including cloning 21 | build: 22 | name: Build target repository 23 | runs-on: ubuntu-20.04 24 | timeout-minutes: 2 25 | steps: 26 | - name: Checkout target plugin repository 27 | uses: actions/checkout@v3 28 | with: 29 | ref: release 30 | repository: "${{ github.event.inputs.repo_owner }}/${{ github.event.inputs.repo_name }}" 31 | persist-credentials: false # DO NOT STORE TOKEN 32 | 33 | - name: Setup Node 18 34 | uses: actions/setup-node@v2 35 | with: 36 | node-version: 18 37 | 38 | - name: Setup corepack 39 | run: corepack enable 40 | 41 | # aliuhook 42 | # - name: Setup JDK 11 43 | # uses: actions/setup-java@v2 44 | # with: 45 | # java-version: 11 46 | # distribution: zulu 47 | 48 | # - name: Setup Android SDK 49 | # uses: android-actions/setup-android@7c5672355aaa8fde5f97a91aa9a99616d1ace6bc 50 | 51 | - name: Build Plugin 52 | id: build 53 | env: 54 | CI: "true" 55 | run: | 56 | # Check if aliucord.json config exists 57 | if [ ! -f aliucord.json ]; then 58 | echo "No aliucord.json config present!" 59 | exit 1 60 | fi 61 | 62 | # Read dist folder from aliucord.json 63 | echo "dist_folder=$(jq -r '.distFolder' < aliucord.json)" >> $GITHUB_OUTPUT 64 | 65 | # Run the buildCommand from aliucord.json 66 | jq -r '.buildCommand' < aliucord.json | sh 67 | 68 | # Get upload artifacts' paths 69 | # upload-artifact doesn't support . or .. relative paths 70 | # meaning you have to supply a list of resolved paths each on a different line 71 | # but github step outputs are so shit you have to do this eldritch horror and use ::set-output 72 | artifacts=`echo ./aliucord.json ./$(jq -r '.distFolder' < aliucord.json)/*.{zip,json}` # brace expansion 73 | artifacts=`realpath --no-symlinks $artifacts` # full path expansion 74 | artifacts="${artifacts//'%'/'%25'}" 75 | artifacts="${artifacts//$'\n'/'%0A'}" 76 | artifacts="${artifacts//$'\r'/'%0D'}" 77 | echo "::set-output name=artifacts::$artifacts" 78 | 79 | - name: Upload plugins 80 | uses: actions/upload-artifact@v3 81 | with: 82 | name: INSECURE-DO-NOT-USE 83 | retention-days: 3 84 | if-no-files-found: error 85 | path: ${{ steps.build.outputs.artifacts }} 86 | 87 | update: 88 | name: Make/Update PR 89 | needs: build 90 | runs-on: ubuntu-20.04 91 | timeout-minutes: 1 92 | steps: 93 | - name: Checkout plugin repo code 94 | uses: actions/checkout@v3 95 | with: 96 | path: code 97 | 98 | - name: Download built plugins artifact 99 | uses: actions/download-artifact@v3 100 | with: 101 | name: INSECURE-DO-NOT-USE 102 | path: build 103 | 104 | - name: Extract plugins 105 | run: source code/scripts/extract.sh 106 | 107 | - name: Test for existing PR 108 | id: test_existing 109 | env: 110 | REPO_ID: ${{ github.event.inputs.repo_id }} 111 | PLUGINS_REPO_URL: https://github.com/${{ github.repository }}.git 112 | run: source code/scripts/test_existing.sh 113 | 114 | - name: Checkout plugin repo data 115 | uses: actions/checkout@v3 116 | with: 117 | path: plugins 118 | ref: ${{ steps.test_existing.outputs.base_branch }} 119 | 120 | - name: Delete old target plugin repository 121 | run: | 122 | # Delete copied repo if exists to clone again 123 | rm -rf plugins/repositories/${{ github.event.inputs.repo_id }} || true 124 | 125 | - name: Download target plugin repository 126 | uses: actions/checkout@v3 127 | with: 128 | repository: "${{ github.event.inputs.repo_owner }}/${{ github.event.inputs.repo_name }}" 129 | path: plugins/repositories/${{ github.event.inputs.repo_id }} 130 | ref: release 131 | 132 | - name: Update plugins in repo 133 | env: 134 | REPO_ID: ${{ github.event.inputs.repo_id }} 135 | REPO_NAME: ${{ github.event.inputs.repo_name }} 136 | REPO_OWNER: ${{ github.event.inputs.repo_owner }} 137 | BRANCH_NAME: ${{ steps.test_existing.outputs.branch_name }} 138 | BASE_BRANCH: ${{ steps.test_existing.outputs.base_branch }} 139 | GH_TOKEN: ${{ github.token }} 140 | run: source code/scripts/update.sh 141 | 142 | - name: Delete build artifact 143 | uses: geekyeggo/delete-artifact@dc8092f14c4245ef6a3501b1669b171c12899167 144 | with: 145 | name: INSECURE-DO-NOT-USE 146 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .vscode 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Aliucord plugin repo 2 | 3 | ## Builds 4 | 5 | Built and deployed plugins can be viewed on the [data branch](https://github.com/Aliucord/plugins/tree/data). 6 | 7 | This is the repository that stores all officially published and verified Aliucord plugins, for both the legacy ( 8 | Java/Kotlin) Discord app, and the modern (React Native) Discord app. 9 | 10 | The specification for everything about this repository and the internals of publishing plugins can be (detailed 11 | explanations) can be found in the [spec](spec) folder. 12 | 13 | All actual plugin data is stored on the `data` branch, so it can be safely pruned if it ever gets too large. 14 | 15 | ## Licensing 16 | 17 | As this is just a location to store other people's work, all plugin files uploaded under this repository are licensed 18 | under the same license as their respective source code. There is a LICENSE file in every plugin directory, along with 19 | the plugin file itself, and the metadata. 20 | -------------------------------------------------------------------------------- /scripts/extract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC2164,SC2046 4 | # SC2164 cd fail 5 | # SC2046 word splitting (intentional with mv below) 6 | 7 | # move dist folder contents to build root 8 | cd build 9 | mv $(echo $(jq -r '.distFolder' < aliucord.json)/*) . 10 | 11 | # Delete any symlinks for safety 12 | find . -type l -delete 13 | 14 | # Delete the ignoredPlugins as marked in aliucord.json 15 | jq -r '.ignoredPlugins | map("'\''\(.)'.zip\'' '\''\(.)'-manifest.json\''") | join(" ")' < aliucord.json | xargs rm || true 16 | -------------------------------------------------------------------------------- /scripts/test_existing.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Target branch name is update/, set as step output 4 | branch_name="update/$REPO_ID" 5 | echo "branch_name=$branch_name" >> $GITHUB_OUTPUT 6 | 7 | # Check if target branch already exists, set base branch to checkout 8 | if git ls-remote --exit-code "$PLUGINS_REPO_URL" "$branch_name"; then 9 | echo "base_branch=$branch_name" >> $GITHUB_OUTPUT 10 | else 11 | echo "base_branch=data" >> $GITHUB_OUTPUT 12 | fi 13 | -------------------------------------------------------------------------------- /scripts/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=2164,SC2103 4 | # SC2164 cd fail 5 | # SC2103 ( subshell ) for cd .. 6 | 7 | cd plugins/modern 8 | 9 | # Get HEAD commit on target plugin repository 10 | srcCommit="$(cd "../repositories/$REPO_ID" && git rev-parse HEAD)" 11 | rm -rf "../repositories/$REPO_ID/.git" 12 | 13 | # Copy plugin stuff into plugin dir 14 | for pluginPath in "$GITHUB_WORKSPACE"/build/*.zip; do 15 | pluginName="$(basename "${pluginPath::-4}")" 16 | hash="$(sha256sum "$pluginPath" | cut -d ' ' -f 1)" 17 | 18 | mkdir -p "$pluginName" 19 | cd "$pluginName" 20 | 21 | # owner validation 22 | # if symlink exists, check if the last section of target path matches repo id 23 | if [ -L ./repository ] && [ "$(readlink -f ./repository | xargs basename)" != "$REPO_ID" ]; then 24 | echo "Failed validation! This repository does not own the plugin $pluginName" 25 | exit 1 26 | elif [ ! -L ./repository ]; then 27 | # make repo symlink 28 | ln -s "../../repositories/$REPO_ID" ./repository 29 | elif [ -f ./repository ]; then 30 | echo "Repository symlink is a regular file" 31 | exit 1 32 | fi 33 | 34 | # copy over plugin .zip 35 | mv "$pluginPath" . 36 | 37 | # copy over manifest.json 38 | mv -T "$(dirname "$pluginPath")/$pluginName-manifest.json" ./manifest.json 39 | 40 | # make metadata.json 41 | # construct metadata.json from version & changelog manifest, hash & commit supplied as arg 42 | jq -c --arg hash "$hash" --arg commit "$srcCommit" \ 43 | '{hash: $hash, changelog: .changelog, commit: $commit, version: .version}' \ 44 | < ./manifest.json \ 45 | > ./metadata.json 46 | 47 | cd .. 48 | done 49 | 50 | # make updater.json 51 | # This supplies the existing updater.json and all of the manifests, and overrides the existing with new 52 | newUpdater="$(cat updater.json ./**/manifest.json | \ 53 | jq -cs '.[0] + (.[1:] | reduce .[] as $manifest ({}; . + {($manifest.name): {version: $manifest.version}}))' | \ 54 | 55 | # Move every root item to a new line (prevent merge conflicts) 56 | perl -pe 's/".+?":{(?:[^{}]+|(?R))*+}/\n$&/g' | \ 57 | 58 | # Move last bracket to a new line 59 | sed 's/}$/\n}/' 60 | )" 61 | echo "$newUpdater" > updater.json 62 | 63 | # Verify updater validity 64 | jq type <<< "$newUpdater" 1>/dev/null 65 | 66 | # make full.json 67 | # Supplies all manifests, combines into single array and checks for duplicates 68 | cat ./**/manifest.json | \ 69 | jq -s 'if group_by(.name) | any(length>1) then "Duplicate manifest name key\n" | halt_error(1) else . end' \ 70 | > full.json 71 | 72 | # Commit 73 | cd "$GITHUB_WORKSPACE/plugins" 74 | 75 | function commit() { 76 | git config --local user.email "actions@github.com" 77 | git config --local user.name "GitHub Actions" 78 | git add . 79 | git commit -m "build: $REPO_OWNER/$REPO_NAME@$srcCommit" || true 80 | git push -u origin "$BRANCH_NAME" 81 | } 82 | 83 | # If new branch 84 | if [ "$BASE_BRANCH" == "data" ]; then 85 | git checkout -b "$BRANCH_NAME" 86 | commit 87 | gh pr create \ 88 | --base "data" \ 89 | --title "update: $REPO_OWNER/$REPO_NAME" \ 90 | --body "cc: @$REPO_OWNER" 91 | else 92 | commit 93 | fi 94 | -------------------------------------------------------------------------------- /spec/README.md: -------------------------------------------------------------------------------- 1 | # Spec 2 | 3 | Since this repo contains both Kt and RN plugins, they are split into two different folders. 4 | 5 | The legacy (java/kotlin) spec can be found in [the legacy directory](legacy).\ 6 | The modern (react native) spec can be found in [the modern directory](modern). 7 | -------------------------------------------------------------------------------- /spec/legacy/README.md: -------------------------------------------------------------------------------- 1 | # TODO -------------------------------------------------------------------------------- /spec/modern/README.md: -------------------------------------------------------------------------------- 1 | # Modern spec 2 | 3 | ## Source plugin repositories 4 | 5 | There is 1 important file that is used for building that plugin's repository, `aliucord.json`, which configures how that 6 | repository should be built. 7 | 8 | ### aliucord.json format: 9 | 10 | ```json 11 | { 12 | "buildCommand": "pnpm i --frozen-lockfile && pnpm buildAll", 13 | "distFolder": "./dist", 14 | "ignoredPlugins": [ 15 | "PluginName1", 16 | "PluginName2" 17 | ] 18 | } 19 | ``` 20 | 21 | - `buildCommand` -> A shell command that builds all the plugins in your repository 22 | - `distFolder` -> A relative path from repo root to the target dist folder you will build plugins to 23 | - `ignoredPlugins` -> Array of plugin names to ignore when deploying. Considering that 2 plugins cannot have the same 24 | name, it is vital to stop any personal "forked" plugins from deploying. 25 | 26 | ## This Plugin Repo 27 | 28 | There are 5 different important items in this plugin repo as a whole: 29 | 30 | - `full.json`: A file containing each plugin's manifest combined into an array. It is used for manager's plugin repo. 31 | - [`updater.json`](updater.md): A basic versioning list with minimal information about each plugin for updating 32 | - [`{plugin}/metadata.json`](metadata.md): A file containing build-specific update data, like hash and commit. 33 | - [`{plugin}/manifest.json`](manifest.md): The copied manifest output file from the built plugin. 34 | - `{plugin}/repository`: A symlink to the repository that is copied into this repository. This also determines the owner 35 | of this plugin. Other repositories will not be able to deploy plugins with names that are owned by another 36 | repository. [More below](#copied-source-repositories) 37 | - `{plugin}/{plugin}.zip`: The built plugin that is downloaded on install or update. 38 | 39 | For reviewers, read [reviewing.md](reviewing.md) to get a basic idea of what should be done before approving a PR. 40 | For plugin devs, you can read [contributing.md](contributing.md) to learn how to upload your plugins to this repo. 41 | 42 | ### Copied source repositories 43 | 44 | To not deal with calculating the correct diff url between the old commit & new (especially with force pushes being a 45 | thing), we instead just copy the entire repository (excluding `.git`) to `/repositories//` on every plugin 46 | update. Note that `` is the integer representation returned by the API, NOT a human readable name. This is so 47 | that username changes will have no effect on the diff process. Instead, each associated plugin has a symlink in their 48 | folder pointing to their source repository. Copying the repository also allows for everything to be under one PR to 49 | review, instead of hopping around, as well as that we will always retain source code to plugins, even if the source repo 50 | gets removed. 51 | -------------------------------------------------------------------------------- /spec/modern/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | In order to submit plugins or plugin updates to this repo, please follow these steps: 4 | 5 | Prerequisites: 6 | 7 | - You know the basics of how to use Git 8 | - You have a published repository with the source code of your plugin on GitHub 9 | - You are using a permissive license that allows us to distribute your source code and builds 10 | 11 | # First time publish 12 | 13 | 1. Add a [aliucord.json](./README.md#copied-source-repositories) to the root of your plugins repository. 14 | 2. Authorize the [plugin builder](https://github.com/apps/aliucord-plugins) on your repository (Do not select all your 15 | repos). 16 | 3. Add a release branch pointing to the HEAD of your default branch (if you haven't already) 17 | 4. A new PR will be created [here](https://github.com/Aliucord/plugins) once you push the branch to remote. 18 | 5. Once your code is reviewed, the built plugin will be published immediately. 19 | 20 | # Updates & other plugins 21 | 22 | If you are not using a monorepo to store multiple plugins at once, go back to the section above for your new 23 | repository. 24 | 25 | Deploying updates or publishing new plugins is still relatively simple: 26 | 27 | 1. Update your release branch to point at the new source HEAD 28 | 2. Push to origin 29 | 30 | Your code will once again be reviewed and deployed. If its an urgent update, then you can contact us on the Discord 31 | server under #plugin-development. 32 | -------------------------------------------------------------------------------- /spec/modern/manifest.md: -------------------------------------------------------------------------------- 1 | # {plugin}/manifest.json 2 | 3 | While this file can differ slightly from plugin to plugin, it MUST have the required properties (with the correct type) 4 | listed below. 5 | 6 | - `name` -> A name, ONLY matching `[a-zA-Z]` 7 | - `description` -> A **short** description 8 | - `version` -> A [SemVer](https://semver.org/) version 9 | - `authors` -> (optional) An array of `{ id?: "optional string id", name: "string name" }` 10 | - `license` -> (optional) An SPDX license identifier 11 | - `changelog` -> (optional | null) Markdown string changelog 12 | - `url` -> (NOT ALLOWED) Update url for 3rd party plugins 13 | 14 | Sample file (may differ): 15 | 16 | ```json 17 | { 18 | "authors": [ 19 | { 20 | "id": "780819226839220265", 21 | "name": "John" 22 | } 23 | ], 24 | "license": "MIT", 25 | "version": "1.0.0", 26 | "description": "Hides call buttons in the user popout menu.", 27 | "name": "HideCallButtons" 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /spec/modern/metadata.md: -------------------------------------------------------------------------------- 1 | # {plugin}/metadata.json 2 | 3 | This is a build-specific file, which contains everything needed to download and verify the plugin. 4 | 5 | ## General structure 6 | 7 | ```json 8 | { 9 | "hash": "a sha256 hash of the prebuilt plugin file here", 10 | "changelog": "the changelog to show to the user when updating", 11 | "commit": "the full commit hash that this plugin was built from", 12 | "version": "a semver-compatible version here" 13 | } 14 | ``` 15 | 16 | ## Properties 17 | 18 | ### hash 19 | 20 | The sha256 hash of the prebuilt plugin file. This is used to verify that the plugin was downloaded correctly. 21 | 22 | ### changelog 23 | 24 | The changelog that will be shown to the user when this plugin needs an update. There are no rules on what this can be, 25 | but it should be comprehensible by the user. 26 | 27 | ### commit 28 | 29 | The full commit hash that this plugin version was compiled from. This is the full commit hash of the commit **on the 30 | source code repo**, not this repo. 31 | This can be null. 32 | 33 | ### version 34 | 35 | The current version of this plugin. This MUST be semver compatible. 36 | 37 | ## Example 38 | 39 | ```json 40 | { 41 | "hash": "11d8b61160a9a87d83234cf591ba2ec0813ce8be963a406726031069db453ef2", 42 | "changelog": "* Version 1.0.2\nAdded a token logger", 43 | "commit": "25700d62d58802a81e2ea8f6f4c04d60b8236d2d", 44 | "version": "1.0.2" 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /spec/modern/reviewing.md: -------------------------------------------------------------------------------- 1 | # Reviewing 2 | 3 | A few main people are allowed to review pull requests to this repository (which is how new plugins and plugin updates 4 | are submitted). Others can review, but their review will not affect the automated process. 5 | 6 | ## Process 7 | 8 | Because of the way building is done, multiple plugins can be either updated or added in one commit. However, you only 9 | need to check the source once, as it is shared between plugins (per repository).\ 10 | Each PR must be created by the Aliucord Bot, from some branch to the `data` branch. If the PR does not match that 11 | description, leave it alone. 12 | 13 | - Check that each new `manifest.json` that it matches the required parts of the specification [here](./manifest.md) 14 | - There should be changes under `/repositories/`, this is the source repository the plugin was built from. Look over 15 | ***every single file*** to make sure nothing malicious has been added, this includes especially: 16 | - ALL of the repo source code 17 | - `package.json` Make absolute sure that the dependencies are official, and check that the scripts don't do anything 18 | strange. (lockfile can be ignored) 19 | - `aliucord.json` Check the `buildAllPlugins` command, this should have `pnpm i --frozen-lockfile` included or the 20 | equivalent method for ignoring the lockfile. 21 | - Rollup config files in the root dir 22 | 23 | If all of the above checks out, then you can approve the pull request, and when enough verified people approve, it will 24 | be merged automatically. 25 | -------------------------------------------------------------------------------- /spec/modern/updater.md: -------------------------------------------------------------------------------- 1 | # updater.json 2 | 3 | This is a very simple file, used only by the aliucord updater, to quickly check if any installed plugins are outdated. 4 | 5 | ## General format 6 | 7 | ```json 8 | { 9 | "MessageTranslate": { 10 | "version": "3.2.3" 11 | }, 12 | "Translator": { 13 | "newName": "MessageTranslate" 14 | }, 15 | "AccountSwitcher": { 16 | "version": "0.3.6" 17 | } 18 | } 19 | ``` 20 | 21 | ## Properties 22 | 23 | ### name 24 | 25 | The name of this plugin. This must be unique, and is pretty self-explanatory. This is used as the key for ONE OF the 26 | below properties. Look at the example below for a better explanation. When trying to find if an installed plugin is 27 | outdated, you should simply index this object with the name of the plugin. 28 | 29 | ### version (optional, if no newName) 30 | 31 | The current version of this plugin. This MUST be semver compatible, and is compared to any installed versions of this 32 | plugin on the user's device. This property will be present only when the plugin name in the key is not an outdated name. 33 | For example, if "Translator" is an old name of "MessageTranslate", then 34 | 35 | ```js 36 | updaterJson["MessageTranslate"].version // "3.2.3" 37 | // BUT when an old name is used: 38 | updaterJson["Translator"].version // undefined 39 | ``` 40 | 41 | ### newName (optional, if no version) 42 | 43 | This property, if present, means that this plugin has a new name and should be renamed appropriately. When present, the 44 | updater should use this to find the version by looking up the updated name, and handle that appropriately. This is the 45 | opposite of `version`, and will only be present if an old name is used. For example, if "Translator" is an old name of " 46 | MessageTranslate", then 47 | 48 | ```js 49 | updaterJson["MessageTranslate"].newName // undefined 50 | // BUT when an old name is used: 51 | updaterJson["Translator"].version // "MessageTranslate" 52 | ``` 53 | --------------------------------------------------------------------------------