diff --git a/.docker_platforms b/.docker_platforms new file mode 100644 index 0000000..fa66d0d --- /dev/null +++ b/.docker_platforms @@ -0,0 +1 @@ +linux/386,linux/amd64,linux/arm64/v8 \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 67345da..1285370 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,3 +1,7 @@ +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + version: 2 updates: - package-ecosystem: "github-actions" @@ -6,7 +10,15 @@ updates: interval: "daily" time: "00:00" target-branch: "nightly" - open-pull-requests-limit: 20 + open-pull-requests-limit: 10 + + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + time: "00:00" + target-branch: "nightly" + open-pull-requests-limit: 10 - package-ecosystem: "pip" directory: "/" @@ -14,4 +26,4 @@ updates: interval: "daily" time: "00:00" target-branch: "nightly" - open-pull-requests-limit: 20 + open-pull-requests-limit: 10 diff --git a/.github/label-actions.yml b/.github/label-actions.yml index a8e91f2..1b6d70f 100644 --- a/.github/label-actions.yml +++ b/.github/label-actions.yml @@ -1,13 +1,15 @@ +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + # Configuration for Label Actions - https://github.com/dessant/label-actions added: comment: > This feature has been added and will be available in the next release. - fixed: comment: > This issue has been fixed and will be available in the next release. - invalid:duplicate: comment: > :wave: @{issue-author}, this appears to be a duplicate of a pre-existing issue. @@ -22,8 +24,8 @@ invalid:duplicate: invalid:support: comment: > :wave: @{issue-author}, we use the issue tracker exclusively for bug reports. - However, this issue appears to be a support request. Please use our - [Discord Server](https://retroarcher.github.io/discord) to get help with RetroArcher. Thanks. + However, this issue appears to be a support request. Please use + [Discord](https://docs.lizardbyte.dev/about/support.html#discord) for support issues. Thanks. close: true lock: true lock-reason: 'off-topic' diff --git a/.github/pr_release_template.md b/.github/pr_release_template.md new file mode 100644 index 0000000..7c96c6b --- /dev/null +++ b/.github/pr_release_template.md @@ -0,0 +1,24 @@ +## Description + +This PR was created automatically. + + +### Screenshot + + + +### Issues Fixed or Closed + + + + + +## Type of Change +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update (changes to documentation) +- [ ] Repository update (changes to repository files) + +## Changelog Summary + diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4c78974..da61d0e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -3,7 +3,7 @@ name: CI on: pull_request: branches: [master, nightly] - types: [opened, synchronize, edited, reopened] + types: [opened, synchronize, reopened] push: branches: [master, nightly] workflow_dispatch: @@ -14,13 +14,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} uses: actions/checkout@v3 - name: Verify Changelog id: verify_changelog if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} # base_ref for pull request check, ref for push - uses: RetroArcher/actions/verify_changelog@master + uses: LizardByte/.github/actions/verify_changelog@master with: token: ${{ secrets.GITHUB_TOKEN }} outputs: @@ -28,113 +29,9 @@ jobs: last_version: ${{ steps.verify_changelog.outputs.latest_release_tag_name }} release_body: ${{ steps.verify_changelog.outputs.changelog_parser_description }} - docker: - runs-on: ubuntu-latest - permissions: - packages: write - contents: write - needs: check_changelog - - env: - BASE_TAG: retroarcher/discord-bot - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Prepare - id: prepare - env: - NEXT_VERSION: ${{ needs.check_changelog.outputs.next_version }} - run: | - # determine to push image to dockerhub and ghcr or not - if [[ $GITHUB_EVENT_NAME == "push" ]]; then - PUSH=true - else - PUSH=false - fi - - # setup the tags - TAGS="${BASE_TAG}:${GITHUB_SHA},ghcr.io/${BASE_TAG}:${GITHUB_SHA}" - - if [[ $GITHUB_REF == refs/heads/master ]]; then - TAGS="${TAGS},${BASE_TAG}:latest,ghcr.io/${BASE_TAG}:latest" - TAGS="${TAGS},${BASE_TAG}:master,ghcr.io/${BASE_TAG}:master" - elif [[ $GITHUB_REF == refs/heads/nightly ]]; then - TAGS="${TAGS},${BASE_TAG}:nightly,ghcr.io/${BASE_TAG}:nightly" - else - TAGS="${TAGS},${BASE_TAG}:test,ghcr.io/${BASE_TAG}:test" - fi - - if [[ ${NEXT_VERSION} != "" ]]; then - TAGS="${TAGS},${BASE_TAG}:${NEXT_VERSION},ghcr.io/${BASE_TAG}:${NEXT_VERSION}" - fi - - echo ::set-output name=branch::${GITHUB_REF#refs/heads/} - echo ::set-output name=build_date::$(date -u +'%Y-%m-%dT%H:%M:%SZ') - echo ::set-output name=commit::${GITHUB_SHA} - echo ::set-output name=platforms::linux/386,linux/amd64,linux/arm64/v8 - echo ::set-output name=push::${PUSH} - echo ::set-output name=tags::${TAGS} - - - name: Set Up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - id: buildx - - - name: Cache Docker Layers - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Log in to Docker Hub - if: ${{ steps.prepare.outputs.push == 'true' }} # PRs do not have access to secrets - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Log in to the Container registry - if: ${{ steps.prepare.outputs.push == 'true' }} # PRs do not have access to secrets - uses: docker/login-action@v1 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v2 - with: - context: ./ - file: ./Dockerfile - push: ${{ steps.prepare.outputs.push }} - platforms: ${{ steps.prepare.outputs.platforms }} - build-args: | - BRANCH=${{ steps.prepare.outputs.branch }} - COMMIT=${{ steps.prepare.outputs.commit }} - BUILD_DATE=${{ steps.prepare.outputs.build_date }} - tags: ${{ steps.prepare.outputs.tags }} - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - - name: Update Docker Hub Description - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} - uses: peter-evans/dockerhub-description@v3 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_PASSWORD }} # token is not currently supported - repository: ${{ env.BASE_TAG }} - short-description: ${{ github.event.repository.description }} - readme-filepath: ./DOCKER_README.md - create_release: runs-on: ubuntu-latest - needs: [check_changelog, docker] + needs: [check_changelog] steps: - name: Create/Update GitHub Release @@ -143,6 +40,6 @@ jobs: with: name: ${{ needs.check_changelog.outputs.next_version }} tag: ${{ needs.check_changelog.outputs.next_version }} - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GH_BOT_TOKEN }} allowUpdates: true body: ${{ needs.check_changelog.outputs.release_body }} diff --git a/.github/workflows/auto-create-pr.yml b/.github/workflows/auto-create-pr.yml new file mode 100644 index 0000000..ef32f2b --- /dev/null +++ b/.github/workflows/auto-create-pr.yml @@ -0,0 +1,33 @@ +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: Auto create PR + +on: + pull_request: + types: + - closed + branches: + - 'nightly' + +jobs: + create_pr: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Create Pull Request + uses: repo-sync/pull-request@v2 + with: + source_branch: "" # should be "nightly" as it's the triggering branch + destination_branch: "master" + pr_title: "Pulling ${{ github.ref }} into master" + pr_template: ".github/pr_release_template.md" + pr_assignee: "${{ secrets.GH_BOT_NAME }}" + pr_draft: true + pr_allow_empty: false + github_token: ${{ secrets.GH_BOT_TOKEN }} diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..da7b4b0 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,58 @@ +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: Automerge PR + +on: + pull_request: + types: + - opened + - synchronize + +jobs: + autoapprove: + if: > + contains(fromJson('["LizardByte-bot"]'), github.event.pull_request.user.login) && + contains(fromJson('["LizardByte-bot"]'), github.actor) + runs-on: ubuntu-latest + steps: + - name: Autoapproving + uses: hmarr/auto-approve-action@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Label autoapproved + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GH_BOT_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['autoapproved', 'autoupdate'] + }) + + automerge: + needs: [autoapprove] + runs-on: ubuntu-latest + concurrency: + group: automerge-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Automerging + uses: pascalgn/automerge-action@v0.15.3 + env: + BASE_BRANCHES: nightly + GITHUB_TOKEN: ${{ secrets.GH_BOT_TOKEN }} + GITHUB_LOGIN: ${{ secrets.GH_BOT_NAME }} + MERGE_LABELS: "" + MERGE_METHOD: "squash" + MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})" + MERGE_DELETE_BRANCH: true + MERGE_ERROR_FAIL: true + MERGE_FILTER_AUTHOR: ${{ secrets.GH_BOT_NAME }} + MERGE_RETRIES: "240" # 1 hour + MERGE_RETRY_SLEEP: "15000" # 15 seconds diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml new file mode 100644 index 0000000..76af290 --- /dev/null +++ b/.github/workflows/ci-docker.yml @@ -0,0 +1,149 @@ +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + +name: CI Docker + +on: + pull_request: + branches: [master, nightly] + types: [opened, synchronize, reopened] + push: + branches: [master, nightly] + workflow_dispatch: + +jobs: + check_changelog: + name: Check Changelog + runs-on: ubuntu-latest + steps: + - name: Checkout + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + uses: actions/checkout@v3 + + - name: Verify Changelog + id: verify_changelog + if: ${{ github.ref == 'refs/heads/master' || github.base_ref == 'master' }} + # base_ref for pull request check, ref for push + uses: LizardByte/.github/actions/verify_changelog@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + outputs: + next_version: ${{ steps.verify_changelog.outputs.changelog_parser_version }} + + docker: + runs-on: ubuntu-latest + permissions: + packages: write + contents: write + needs: [check_changelog] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Prepare + id: prepare + env: + NEXT_VERSION: ${{ needs.check_changelog.outputs.next_version }} + run: | + # get branch name + BRANCH=${GITHUB_HEAD_REF} + + if [ -z "$BRANCH" ] + then + echo "This is a PUSH event" + BRANCH=${{ github.ref_name }} + fi + + # determine to push image to dockerhub and ghcr or not + if [[ $GITHUB_EVENT_NAME == "push" ]]; then + PUSH=true + else + PUSH=false + fi + + # setup the tags + REPOSITORY=${{ github.repository }} + BASE_TAG=$(echo $REPOSITORY | tr '[:upper:]' '[:lower:]') + COMMIT=${{ github.sha }} + + TAGS="${BASE_TAG}:${COMMIT:0:7},ghcr.io/${BASE_TAG}:${COMMIT:0:7}" + + if [[ $GITHUB_REF == refs/heads/master ]]; then + TAGS="${TAGS},${BASE_TAG}:latest,ghcr.io/${BASE_TAG}:latest" + TAGS="${TAGS},${BASE_TAG}:master,ghcr.io/${BASE_TAG}:master" + elif [[ $GITHUB_REF == refs/heads/nightly ]]; then + TAGS="${TAGS},${BASE_TAG}:nightly,ghcr.io/${BASE_TAG}:nightly" + else + TAGS="${TAGS},${BASE_TAG}:test,ghcr.io/${BASE_TAG}:test" + fi + + if [[ ${NEXT_VERSION} != "" ]]; then + TAGS="${TAGS},${BASE_TAG}:${NEXT_VERSION},ghcr.io/${BASE_TAG}:${NEXT_VERSION}" + fi + + # read the platforms from `.docker_platforms` + PLATFORMS=$(<.docker_platforms) + + echo ::set-output name=branch::${BRANCH} + echo ::set-output name=build_date::$(date -u +'%Y-%m-%dT%H:%M:%SZ') + echo ::set-output name=commit::${COMMIT} + echo ::set-output name=platforms::${PLATFORMS} + echo ::set-output name=push::${PUSH} + echo ::set-output name=tags::${TAGS} + + - name: Set Up QEMU + uses: docker/setup-qemu-action@v1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + id: buildx + + - name: Cache Docker Layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Log in to Docker Hub + if: ${{ steps.prepare.outputs.push == 'true' }} # PRs do not have access to secrets + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Log in to the Container registry + if: ${{ steps.prepare.outputs.push == 'true' }} # PRs do not have access to secrets + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ secrets.GH_BOT_NAME }} + password: ${{ secrets.GH_BOT_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: ./ + file: ./Dockerfile + push: ${{ steps.prepare.outputs.push }} + platforms: ${{ steps.prepare.outputs.platforms }} + build-args: | + BRANCH=${{ steps.prepare.outputs.branch }} + COMMIT=${{ steps.prepare.outputs.commit }} + BUILD_DATE=${{ steps.prepare.outputs.build_date }} + tags: ${{ steps.prepare.outputs.tags }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache + + - name: Update Docker Hub Description + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} # token is not currently supported + repository: ${{ env.BASE_TAG }} + short-description: ${{ github.event.repository.description }} + readme-filepath: ./DOCKER_README.md diff --git a/.github/workflows/issues-stale.yml b/.github/workflows/issues-stale.yml index 32da318..57b1112 100644 --- a/.github/workflows/issues-stale.yml +++ b/.github/workflows/issues-stale.yml @@ -1,3 +1,7 @@ +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + name: Stale Issues / PRs on: @@ -12,37 +16,38 @@ jobs: - name: Stale uses: actions/stale@v5 with: - stale-issue-message: > - This issue is stale because it has been open for 30 days with no activity. - Remove the stale label or comment, otherwise this will be closed in 5 days. close-issue-message: > This issue was closed because it has been stalled for 5 days with no activity. - stale-issue-label: 'stale' - exempt-issue-labels: 'added,fixed,type:enhancement,status:awaiting-triage,status:in-progress' - stale-pr-message: > - This PR is stale because it has been open for 90 days with no activity. - Remove the stale label or comment, otherwise this will be closed in 10 days. close-pr-message: > This PR was closed because it has been stalled for 10 days with no activity. - stale-pr-label: 'stale' - exempt-pr-labels: 'status:in-progress' days-before-stale: 90 days-before-close: 10 + exempt-all-assignees: true + exempt-issue-labels: 'added,fixed' + exempt-pr-labels: 'dependencies,l10n' + stale-issue-label: 'stale' + stale-issue-message: > + This issue is stale because it has been open for 30 days with no activity. + Comment or remove the stale label, otherwise this will be closed in 5 days. + stale-pr-label: 'stale' + stale-pr-message: > + This PR is stale because it has been open for 90 days with no activity. + Comment or remove the stale label, otherwise this will be closed in 10 days. - name: Invalid Template uses: actions/stale@v5 with: - stale-issue-message: > - Invalid issues template. close-issue-message: > This issue was closed because the the template was not completed after 5 days. - stale-issue-label: 'invalid:template-incomplete' - stale-pr-message: > - Invalid PR template. close-pr-message: > This PR was closed because the the template was not completed after 5 days. - stale-pr-label: 'invalid:template-incomplete' - exempt-pr-labels: 'status:in-progress' - only-labels: 'invalid:template-incomplete' days-before-stale: 0 days-before-close: 5 + exempt-pr-labels: 'dependencies,l10n' + only-labels: 'invalid:template-incomplete' + stale-issue-label: 'invalid:template-incomplete' + stale-issue-message: > + Invalid issues template. + stale-pr-label: 'invalid:template-incomplete' + stale-pr-message: > + Invalid PR template. diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 5f152f3..9f4a08d 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -1,3 +1,7 @@ +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + name: Issues on: diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 36f597b..9954387 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -1,3 +1,7 @@ +# This action is centrally managed in https://github.com//.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in +# the above-mentioned repo. + name: Pull Requests on: @@ -17,5 +21,6 @@ jobs: exclude: nightly # Don't prevent going from nightly -> master change-to: nightly comment: | - Your PR was set to `master`, PRs should be sent to `nightly` - The base branch of this PR has been automatically changed to `nightly`, please check that there are no merge conflicts + Your PR was set to `master`, PRs should be sent to `nightly`. + The base branch of this PR has been automatically changed to `nightly`. + Please check that there are no merge conflicts diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d002b..8250904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [0.0.5] - 2022-07-31 +### Fixed +- Bot will now self updates username and avatar +- Fixed issue with duplicate guild ids +- Random quotes are now pulled from `uno` repo +### Added +- `docs` slash command +### Removed +- `wiki` slash command +### Dependencies +- Bump requests from 2.27.1 to 2.28.1 +- Bump py-cord from 2.0.0b3 to 2.0.0 +- Bump flask from 2.1.1 to 2.1.3 +### Misc. +- Rebrand to LizardByte +- Change License to AGPL v3 + ## [0.0.4] - 2022-04-24 ### Fixed - Corrected environment variable for daily release task diff --git a/DOCKER_README.md b/DOCKER_README.md index d7eabf5..b0302c1 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -5,7 +5,7 @@ Create and run the container (substitute your ``): ```bash docker run -d \ - --name=retroarcher-discord-bot \ + --name=lizardbyte-discord-bot \ --restart=unless-stopped \ -e BOT_TOKEN= \ -e DAILY_CHANNEL_ID= \ @@ -15,18 +15,18 @@ docker run -d \ -e GRAVATAR_EMAIL= \ -e IGDB_CLIENT_ID= \ -e IGDB_CLIENT_SECRET= \ - retroarcher/discord-bot + lizardbyte/discord-bot ``` To update the container it must be removed and recreated: ```bash # Stop the container -docker stop retroarcher-discord-bot +docker stop lizardbyte-discord-bot # Remove the container -docker rm retroarcher-discord-bot +docker rm lizardbyte-discord-bot # Pull the latest update -docker pull retroarcher/discord-bot +docker pull lizardbyte/discord-bot # Run the container with the same parameters as before docker run -d ... ``` @@ -38,9 +38,9 @@ Create a `docker-compose.yml` file with the following contents (substitute your ```yaml version: '3' services: - retroarcher-discord-bot: - image: retroarcher/discord-bot - container_name: retroarcher-discord-bot + lizardbyte-discord-bot: + image: lizardbyte/discord-bot + container_name: lizardbyte-discord-bot restart: unless-stopped environment: - BOT_TOKEN= diff --git a/Dockerfile b/Dockerfile index 02b4570..10eafac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ ARG DAILY_CHANNEL_ID ARG GRAVATAR_EMAIL ARG IGDB_CLIENT_ID ARG IGDB_CLIENT_SECRET +ARG READTHEDOCS_TOKEN # Environment variables ENV DAILY_TASKS=$DAILY_TASKS @@ -21,10 +22,10 @@ ENV BOT_TOKEN=$BOT_TOKEN ENV GRAVATAR_EMAIL=$GRAVATAR_EMAIL ENV IGDB_CLIENT_ID=$IGDB_CLIENT_ID ENV IGDB_CLIENT_SECRET=$IGDB_CLIENT_SECRET +ENV READTHEDOCS_TOKEN=$READTHEDOCS_TOKEN COPY requirements.txt . COPY *.py . -COPY commands.json . RUN pip install --no-cache-dir -r requirements.txt CMD ["python", "discord_bot.py"] diff --git a/LICENSE b/LICENSE index 94a9ed0..0ad25db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,23 +1,21 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 - Copyright (C) 2007 Free Software Foundation, Inc. + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to +our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. The precise terms and conditions for copying, distribution and modification follow. @@ -72,7 +60,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Use with the GNU Affero General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single +under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General +Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published +GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's +versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + GNU Affero General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/README.md b/README.md index fce40b3..72294a3 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@ -# RetroArcher.discord-bot -Discord bot written in python to help manage the RetroArcher discord server. +# discord-bot +Discord bot written in python to help manage the LizardByte discord server. ## Overview -This is a custom discord bot with some slash commands to help with support on the RetroArcher discord server. - -| command | argument 1 | argument 2 | description | -| ------- | ---------- | ---------- | -------------------------------- | -| /help | | | Return help message | -| /donate | user | | Return donation links | -| /random | | | Return a random video game quote | -| /wiki | page | user | Return the specified wiki page | +This is a custom discord bot with some slash commands to help with support on the LizardByte discord server. +| command | argument 1 | argument 2 | argument 3 | description | +|---------|------------|------------|------------|----------------------------------| +| /help | | | | Return help message | +| /docs | project | version | user | Return the specified wiki page | +| /donate | user | | | Return donation links | +| /random | | | | Return a random video game quote | ## Instructions @@ -24,16 +23,17 @@ This is a custom discord bot with some slash commands to help with support on th :exclamation: if using Docker these can be arguments. :warning: Never publicly expose your tokens, secrets, or ids. - | variable | required | default | description | - | -------------------- | -------- | ------- | ------------------------------------------------------------- | - | BOT_TOKEN | True | None | Token from Bot page on discord developer portal. | - | DAILY_TASKS | False | true | Daily tasks on or off. | - | DAILY_RELEASES | False | true | Send a message for each game released on this day in history. | - | DAILY_CHANNEL_ID | False | None | Required if daily_tasks is enabled. | - | DAILY_TASKS_UTC_HOUR | False | 12 | The hour to run daily tasks. | - | GRAVATAR_EMAIL | False | None | Gravatar email address for bot avatar. | - | IGDB_CLIENT_ID | False | None | Required if daily_releases is enabled. | - | IGDB_CLIENT_SECRET | False | None | Required if daily_releases is enabled. | +| variable | required | default | description | +|----------------------|----------|---------|---------------------------------------------------------------| +| BOT_TOKEN | True | None | Token from Bot page on discord developer portal. | +| DAILY_TASKS | False | true | Daily tasks on or off. | +| DAILY_RELEASES | False | true | Send a message for each game released on this day in history. | +| DAILY_CHANNEL_ID | False | None | Required if daily_tasks is enabled. | +| DAILY_TASKS_UTC_HOUR | False | 12 | The hour to run daily tasks. | +| GRAVATAR_EMAIL | False | None | Gravatar email address for bot avatar. | +| IGDB_CLIENT_ID | False | None | Required if daily_releases is enabled. | +| IGDB_CLIENT_SECRET | False | None | Required if daily_releases is enabled. | +| READTHEDOCS_TOKEN | False | None | Required for docs slash command. | * Running bot: * `python discord_bot.py` diff --git a/commands.json b/commands.json deleted file mode 100644 index d8ff4cc..0000000 --- a/commands.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "home" : "Home", - "wiki" : "_Sidebar", - "logs" : "Log-Files", - "server" : "Prerequisites,-Server", - "client" : "Prerequisites,-Client", - "clients" : "Supported-clients", - "install" : "Installation", - "python" : "Install-Python", - "tautulli" : "Configure-Tautulli", - "gamestreaming" : "Configure-Game-Streaming", - "settings" : "Configure-RetroArcher", - "platforms" : "Platform-Names", - "scan" : "Scanning-Games-and-Roms", - "library" : "Configure-Game-Library", - "config_client" : "Configure-Clients", - "meta-igdb" : "Metadata-(IGDB)", - "meta-local" : "Metadata-(Local)", - "config_retroarch" : "Configure-RetroArch", - "cores_retroarch" : "Default-Cores", - "gamepads_retroarch" : "Game-pads", - "config_cemu" : "Configure-CEMU", - "config_rpcs3" : "Configure-RPCS3", - "todo" : "To-Do" -} diff --git a/discord_bot.py b/discord_bot.py index 8c2b073..18109ac 100644 --- a/discord_bot.py +++ b/discord_bot.py @@ -1,16 +1,20 @@ -import discord -from discord.commands import Option, OptionChoice -from discord.ext import tasks +# standard imports from datetime import datetime -from igdb.wrapper import IGDBWrapper +from io import BytesIO import json -from libgravatar import Gravatar import os import random -import requests import sys from urllib import parse +# lib imports +import discord +from discord.commands import Option, OptionChoice +from discord.ext import tasks +from igdb.wrapper import IGDBWrapper +from libgravatar import Gravatar +import requests + # local imports import keep_alive @@ -18,7 +22,10 @@ from dotenv import load_dotenv load_dotenv(override=False) # environment secrets take priority over .env file -# convert month number to igdb human readable month +# env variables +READTHEDOCS_TOKEN = os.environ['READTHEDOCS_TOKEN'] + +# convert month number to igdb human-readable month month_dictionary = { 1: 'Jan', 2: 'Feb', @@ -48,20 +55,6 @@ def get_bot_avatar(gravatar: str) -> str: return image_url -def convert_wiki(git_user: str, git_repo: str, wiki_file: str) -> str: - """Return the text of a wiki file from a given github repo. - - :param git_user: Github username - :param git_repo: Github repo name - :param wiki_file: Wiki page filename - :return: Text of wiki page - """ - url = f'https://raw.githubusercontent.com/wiki/{git_user}/{git_repo}/{wiki_file}.md' - response = requests.get(url=url) - - return response.text - - def discord_message(git_user: str, git_repo: str, wiki_file: str, color: int): """Return the elements of a the discord message from the given parameters. @@ -117,12 +110,16 @@ def post_json(url: str, headers: dict) -> dict: bot_token = os.environ['BOT_TOKEN'] bot = discord.Bot(intents=discord.Intents.all(), auto_sync_commands=True) -bot_name = 'RetroArcher Bot' -bot_url = 'https://RetroArcher.github.io' +org_name = 'LizardByte' +bot_name = f'{org_name}-Bot' +bot_url = 'https://app.lizardbyte.dev' # avatar avatar = get_bot_avatar(gravatar=os.environ['GRAVATAR_EMAIL']) +response = requests.get(url=avatar) +avatar_img = BytesIO(response.content).read() + # context reference # https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.ext.commands.Context @@ -134,15 +131,6 @@ def post_json(url: str, headers: dict) -> dict: except FileNotFoundError: guild_ids = [] -# command : wiki-file -command_file = 'commands.json' -try: - with open(file=command_file, mode='r') as f: - command_dict = json.load(fp=f) -except FileNotFoundError: - print(f'Error: {command_file} not found') - sys.exit(__status=1) - @bot.event # on ready async def on_ready(): @@ -157,15 +145,18 @@ async def on_ready(): print(f'Logged in as || name: {bot.user.name} || id: {bot.user.id}') print(f'Servers connected to: {bot.guilds}') - global guild_ids for guild in bot.guilds: print(guild.name) - guild_ids.append(guild.id) + if guild.id not in guild_ids: + guild_ids.append(guild.id) with open(file=guild_file, mode='w') as file: json.dump(obj=guild_ids, fp=file, indent=2) + # update the username and avatar + await bot.user.edit(username=bot_name, avatar=avatar_img) + await bot.change_presence( - activity=discord.Activity(type=discord.ActivityType.watching, name="the RetroArcher server") + activity=discord.Activity(type=discord.ActivityType.watching, name=f"the {org_name} server") ) try: @@ -180,7 +171,7 @@ async def on_ready(): @bot.slash_command(name="help", - description="Get help with RetroArcher Bot", + description=f"Get help with {bot_name}", guild_ids=guild_ids, ) async def help_command(ctx): @@ -189,28 +180,29 @@ async def help_command(ctx): :param ctx: request message context :return: embed """ - description = """\ + description = f"""\ `/help` - Print this message. - `/donate ` - See how to support RetroArcher. + `/docs ` - Return url to project docs. + `project` - The project to return docs for. Required. + `version` - The version of the docs to return. Optional. `user` - The user to mention in the response. Optional. - `/random ` - Return a random video game quote. + `/donate ` - See how to support {org_name}. `user` - The user to mention in the response. Optional. - `/wiki ` - Return page from the RetroArcher wiki. - `page` - The page to return. Required. + `/random ` - Return a random video game quote. `user` - The user to mention in the response. Optional. """ embed = discord.Embed(description=description, color=0xE5A00D) - embed.set_footer(text='RetroArcher Bot', icon_url=avatar) + embed.set_footer(text=bot_name, icon_url=avatar) await ctx.respond(embed=embed) @bot.slash_command(name="donate", - description="Support the development of RetroArcher", + description=f"Support the development of {org_name}", guild_ids=guild_ids, ) async def donate_command(ctx, user: Option(discord.Member, description='Select the user to mention') = None): @@ -225,7 +217,7 @@ async def donate_command(ctx, user: Option(discord.Member, description='Select t embeds.append(discord.Embed(color=0x333)) embeds[-1].set_author( name='Github Sponsors', - url='https://github.com/sponsors/ReenigneArcher', + url=f'https://github.com/sponsors/{org_name}', icon_url='https://github.com/fluidicon.png' ) @@ -239,7 +231,7 @@ async def donate_command(ctx, user: Option(discord.Member, description='Select t embeds.append(discord.Embed(description='Includes Discord benefits.', color=0xf96854)) embeds[-1].set_author( name='Patreon', - url='https://www.patreon.com/RetroArcher', + url=f'https://www.patreon.com/{org_name}', icon_url='https://c5.patreon.com/external/favicon/apple-touch-icon.png?v=jw6AR4Rg74' ) @@ -269,103 +261,112 @@ async def random_command(ctx, user: Option(discord.Member, description='Select t :param user: username to mention in response :return: embed """ - quote_list = [ - "@!#?@!", - "Ah shit, here we go again", - "All Your Base Are Belong To Us", - "Beyond the scope of Light, beyond the reach of Dark. What could possibly await us. And yet we seek it, insatiably.", - "Bury me with my money!", - "Did I ever tell you what the definition of insanity is?", - "Do a barrel roll!", - "Don't make a girl a promise... If you know you can't keep it.", - "Finish Him!", - "Get over here!", - "Hell, it's about time", - "Hello, my friend. Stay awhile and listen.", - "Hey! Listen!", - "I gotta believe!!", - "I need a weapon.", - "I used to be an adventurer like you. Then I took an arrow in the knee.", - "I want to restore this world, but I fear I can't do it alone. Perhaps you could give me a hand?", - "It is pitch black. You are likely to be eaten by a grue.", - "It’s dangerous to go alone, take this!", - "It’s easy to forget what a sin is in the middle of a battlefield.", - "It's time to kick ass and chew bubblegum... and I'm all outta gum.", - "No Gods or Kings, only Man", - "Nothing is true, everything is permitted.", - "Reticulating splines", - "Sir. Finishing this fight.", - "Stand by for Titanfall", - "The Cake Is a Lie", - "Thank you Mario! But our Princess is in another castle!", - "Thought I'd Try Shooting My Way Out—Mix Things Up A Little.", - "Wake me... when you need me.", - "War Never Changes", - "We all make choices, but in the end our choices make us.", - "We should start from the first floor, okay? And, Jill, here's a lock pick. It might be handy if you, the master of unlocking, take it with you.", - "Well, butter my biscuits!", - "Wololo", - "Would you kindly?", - "Yes sir, I need a weapon.", - "You've got a heart of gold. Don't let them take it from you.", - "You have died of dysentery.", - "You must construct additional pylons.", - ] - - quote = random.choice(seq=quote_list) - - embed = discord.Embed(description=quote, color=0xE5A00D) - embed.set_footer(text='RetroArcher Bot', icon_url=avatar) + quotes = requests.get(url='https://app.lizardbyte.dev/uno/random-quotes/games.json').json() + + quote_index = random.choice(seq=quotes) + quote = quote_index['quote'] + + game = quote_index['game'] + character = quote_index['character'] + + if game and character: + description = f'~{character} / {game}' + elif game: + description = f'{game}' + elif character: + description = f'~{character}' + else: + description = None + + embed = discord.Embed(title=quote, description=description, color=0x00ff00) + embed.set_footer(text=bot_name, icon_url=avatar) if user: await ctx.respond(user.mention, embed=embed) else: await ctx.respond(embed=embed) -# Combine all wiki pages into one slash command with options/choices -# Wiki Command -wiki_choices = [] -for key, value in command_dict.items(): - wiki_choices.append(OptionChoice(name=key, value=key)) +# get projects list from readthedocs +def get_readthedocs() -> list: + url_base = 'https://readthedocs.org' + url = f'{url_base}/api/v3/projects/' + headers = {'Authorization': f'token {READTHEDOCS_TOKEN}'} + + results = [] + + while True: + res = requests.get(url, headers=headers) + data = res.json() + + results.extend(data['results']) + + if data['next']: + url = f"{url_base}{data['next']}" + else: + break -@bot.slash_command(name="wiki", - description="Return any of the listed Wiki pages as a message.", + return results + + +def get_readthedocs_names() -> list: + names = [] + + projects = get_readthedocs() + for project in projects: + names.append(project['name']) + + return names + + +@bot.slash_command(name="docs", + description="Return docs for any project.", guild_ids=guild_ids, ) -async def wiki_command(ctx, - page: Option(str, - description='Select the wiki page', - choices=wiki_choices, - required=True - ), +async def docs_command(ctx, + project: Option(str, + description='Select the project', + choices=get_readthedocs_names(), + required=True + ), + version: Option(str, + description='Documentation for which version', + choices=['latest', 'nightly'], + required=False + ) = 'latest', user: Option(discord.Member, description='Select the user to mention' ) = None ): """ - Send an embed with a wiki text to the server and channel where the command was issued. + Send an embed with a project documentation. :param ctx: request message context - :param page: wiki page to return in response + :param project: the project + :param version: the version of the documentation :param user: username to mention in response :return: embed """ - v = command_dict[page] + readthedocs = get_readthedocs() + project_url = None + for docs in readthedocs: + if project == docs['name']: + project_url = docs['urls']['documentation'] + break - git_user = 'RetroArcher' - git_repo = 'RetroArcher.bundle' - wiki_file = parse.quote(v) - title = v.replace('-', ' ').replace('_', ' ').strip() - color = 0xE5A00D + if project_url: + project_url = project_url.replace('/en/latest/', f'/en/{version}/') - url, embed_message, color = discord_message(git_user=git_user, git_repo=git_repo, wiki_file=wiki_file, color=color) - embed = discord.Embed(title=title, url=url, description=embed_message, color=color) - embed.set_footer(text='RetroArcher Bot', icon_url=avatar) + description = f"""\ + Here is the `{version}` documentation for `{project}`. + """ - if user: - await ctx.respond(user.mention, embed=embed) - else: - await ctx.respond(embed=embed) + embed = discord.Embed(title=project_url, url=project_url, description=description, color=0x00ff00) + embed.set_footer(text=bot_name, icon_url=avatar) + + if user: + await ctx.respond(user.mention, embed=embed) + else: + await ctx.respond(embed=embed) @tasks.loop(minutes=60.0) diff --git a/pyproject.toml b/pyproject.toml index 925a1be..4e4080f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,16 @@ [tool.poetry] -name = "retroarcherdiscord-bot" +name = "lizardbyte-discord-bot" version = "0.1.0" -description = "Bot for RetroArcher discord server" +description = "Bot for LizardByte discord server" authors = ["ReenigneArcher"] [tool.poetry.dependencies] python = "^3.8" -py-cord = "2.0.0b3" -Flask = "2.1.1" +py-cord = "2.0.0" +Flask = "2.1.3" igdb-api-v4 = "0.0.5" python-dotenv = "0.20.0" -requests = "2.27.1" +requests = "2.28.1" [tool.poetry.dev-dependencies] diff --git a/requirements.txt b/requirements.txt index a01165f..5e5eb75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -Flask==2.1.1 +Flask==2.1.3 igdb-api-v4==0.0.5 libgravatar==1.0.0 -py-cord==2.0.0b3 +py-cord==2.0.0 python-dotenv==0.20.0 -requests==2.27.1 +requests==2.28.1 diff --git a/sample.env b/sample.env index bf3122b..77064e3 100644 --- a/sample.env +++ b/sample.env @@ -9,3 +9,4 @@ BOT_TOKEN= GRAVATAR_EMAIL= IGDB_CLIENT_ID= IGDB_CLIENT_SECRET= +READTHEDOCS_TOKEN=