├── .dockerignore ├── .env.example ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── black.yml │ ├── codeql.yml │ └── docker-publish.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── main.py ├── privacypolicy.md ├── requirements.txt ├── src ├── ChatProcess.py ├── chatUtils │ ├── Chat.py │ ├── prompts.py │ ├── prompts │ │ ├── botator-roleplay │ │ │ ├── chat.txt │ │ │ └── text.txt │ │ ├── botator │ │ │ ├── chat.txt │ │ │ └── text.txt │ │ ├── fizziq │ │ │ ├── chat.txt │ │ │ └── text.txt │ │ ├── quantum │ │ │ ├── chat.txt │ │ │ └── text.txt │ │ └── zenith │ │ │ ├── chat.txt │ │ │ └── text.txt │ └── requesters │ │ ├── claude.py │ │ ├── llama.py │ │ ├── llama2.py │ │ ├── openaiChat.py │ │ ├── openaiText.py │ │ └── request.py ├── cogs │ ├── __init__.py │ ├── channelSetup.py │ ├── chat.py │ ├── help.py │ ├── manage_chat.py │ └── moderation.py ├── config.py ├── functionscalls.py ├── google-palm-process.py ├── guild.py ├── makeprompt.py ├── premiumcode.py ├── prompts │ └── functions.json ├── resetter.py ├── toxicity.py ├── utils │ ├── SqlConnector.py │ ├── banusr.py │ ├── misc.py │ ├── openaicaller.py │ ├── replicatepredictor.py │ ├── tokens.py │ └── variousclasses.py └── vision_processing.py └── tos.md /.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | .git -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paillat-dev/Botator/a83205b1d2950b4ad2aa2650908bade814905246/.env.example -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://www.buymeacoffee.com/paillat'] 14 | -------------------------------------------------------------------------------- /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: psf/black@stable 11 | with: 12 | options: "--check" 13 | src: "./src" 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '39 19 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | push: 10 | branches: [ "main" ] 11 | # Publish semver tags as releases. 12 | tags: [ 'v*.*.*' ] 13 | pull_request: 14 | branches: [ "main" ] 15 | 16 | env: 17 | # Use docker.io for Docker Hub if empty 18 | REGISTRY: ghcr.io 19 | # github.repository as / 20 | IMAGE_NAME: ${{ github.repository }} 21 | 22 | 23 | jobs: 24 | build: 25 | 26 | runs-on: ubuntu-latest 27 | permissions: 28 | contents: read 29 | packages: write 30 | # This is used to complete the identity challenge 31 | # with sigstore/fulcio when running outside of PRs. 32 | id-token: write 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v3 37 | 38 | # Install the cosign tool except on PR 39 | # https://github.com/sigstore/cosign-installer 40 | - name: Install cosign 41 | if: github.event_name != 'pull_request' 42 | uses: sigstore/cosign-installer@6e04d228eb30da1757ee4e1dd75a0ec73a653e06 #v3.1.1 43 | with: 44 | cosign-release: 'v2.1.1' 45 | 46 | # Set up BuildKit Docker container builder to be able to build 47 | # multi-platform images and export cache 48 | # https://github.com/docker/setup-buildx-action 49 | - name: Set up Docker Buildx 50 | uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 51 | 52 | # Login against a Docker registry except on PR 53 | # https://github.com/docker/login-action 54 | - name: Log into registry ${{ env.REGISTRY }} 55 | if: github.event_name != 'pull_request' 56 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 57 | with: 58 | registry: ${{ env.REGISTRY }} 59 | username: ${{ github.actor }} 60 | password: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | # Extract metadata (tags, labels) for Docker 63 | # https://github.com/docker/metadata-action 64 | - name: Extract Docker metadata 65 | id: meta 66 | uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 67 | with: 68 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 69 | 70 | # Build and push Docker image with Buildx (don't push on PR) 71 | # https://github.com/docker/build-push-action 72 | - name: Build and push Docker image 73 | id: build-and-push 74 | uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 75 | with: 76 | context: . 77 | push: ${{ github.event_name != 'pull_request' }} 78 | tags: ${{ steps.meta.outputs.tags }} 79 | labels: ${{ steps.meta.outputs.labels }} 80 | cache-from: type=gha 81 | cache-to: type=gha,mode=max 82 | 83 | # Sign the resulting Docker image digest except on PRs. 84 | # This will only write to the public Rekor transparency log when the Docker 85 | # repository is public to avoid leaking data. If you would like to publish 86 | # transparency data even for private images, pass --force to cosign below. 87 | # https://github.com/sigstore/cosign 88 | - name: Sign the published Docker image 89 | if: ${{ github.event_name != 'pull_request' }} 90 | env: 91 | # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable 92 | TAGS: ${{ steps.meta.outputs.tags }} 93 | DIGEST: ${{ steps.build-and-push.outputs.digest }} 94 | # This step uses the identity token to provision an ephemeral certificate 95 | # against the sigstore community Fulcio instance. 96 | run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} 97 | - name: Update production environement 98 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' 99 | run: | 100 | curl -X POST ${{ secrets.UPDATE_PRODUCTION_WEBHOOK }} 101 | env: 102 | POST_REQUEST_URL: ${{ secrets.UPDATE_PRODUCTION_WEBHOOK }} 103 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # VSCode 10 | .vscode 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # poetry 101 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 105 | #poetry.lock 106 | 107 | # pdm 108 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 109 | #pdm.lock 110 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 111 | # in version control. 112 | # https://pdm.fming.dev/#use-with-ide 113 | .pdm.toml 114 | 115 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 116 | __pypackages__/ 117 | 118 | # Celery stuff 119 | celerybeat-schedule 120 | celerybeat.pid 121 | 122 | # SageMath parsed files 123 | *.sage.py 124 | 125 | # Environments 126 | .env 127 | .venv 128 | env/ 129 | venv/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # PyCharm 159 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 160 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 161 | # and can be added to the global gitignore or merged into this file. For a more nuclear 162 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 163 | #.idea/ 164 | 165 | key.txt 166 | data.db 167 | database 168 | premium-key.txt 169 | premium.db 170 | guildscount.py 171 | 172 | *.ovpn 173 | 174 | tests/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-bookworm 2 | 3 | # Keeps Python from generating .pyc files in the container 4 | ENV PYTHONDONTWRITEBYTECODE=1 5 | 6 | # Turns off buffering for easier container logging 7 | ENV PYTHONUNBUFFERED=1 8 | 9 | 10 | # we move to the app folder and run the pip install command 11 | WORKDIR /app 12 | 13 | # we copy just the requirements.txt first to leverage Docker cache 14 | COPY requirements.txt . 15 | 16 | # Install pip requirements + git 17 | RUN apt update && apt install git 18 | RUN pip install -r requirements.txt 19 | 20 | 21 | # Creates a non-root user with an explicit UID and adds permission to access the /app folder 22 | RUN adduser -u 5574 --disabled-password --gecos "" appuser && chown -R appuser /app 23 | USER appuser 24 | 25 | # We copy the rest of the codebase into the image 26 | COPY . /app 27 | 28 | # We run the application 29 | CMD ["python", "main.py"] 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | When redistributing this code, add your copyrihght notice below all the other ones 2 | Copyright (c) 2022 Jérémie Cotti 3 | GNU GENERAL PUBLIC LICENSE 4 | Version 3, 29 June 2007 5 | 6 | Copyright (C) 2007 Free Software Foundation, Inc. 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The GNU General Public License is a free, copyleft license for 13 | software and other kinds of works. 14 | 15 | The licenses for most software and other practical works are designed 16 | to take away your freedom to share and change the works. By contrast, 17 | the GNU General Public License is intended to guarantee your freedom to 18 | share and change all versions of a program--to make sure it remains free 19 | software for all its users. We, the Free Software Foundation, use the 20 | GNU General Public License for most of our software; it applies also to 21 | any other work released this way by its authors. You can apply it to 22 | your programs, too. 23 | 24 | When we speak of free software, we are referring to freedom, not 25 | price. Our General Public Licenses are designed to make sure that you 26 | have the freedom to distribute copies of free software (and charge for 27 | them if you wish), that you receive source code or can get it if you 28 | want it, that you can change the software or use pieces of it in new 29 | free programs, and that you know you can do these things. 30 | 31 | To protect your rights, we need to prevent others from denying you 32 | these rights or asking you to surrender the rights. Therefore, you have 33 | certain responsibilities if you distribute copies of the software, or if 34 | you modify it: responsibilities to respect the freedom of others. 35 | 36 | For example, if you distribute copies of such a program, whether 37 | gratis or for a fee, you must pass on to the recipients the same 38 | freedoms that you received. You must make sure that they, too, receive 39 | or can get the source code. And you must show them these terms so they 40 | know their rights. 41 | 42 | Developers that use the GNU GPL protect your rights with two steps: 43 | (1) assert copyright on the software, and (2) offer you this License 44 | giving you legal permission to copy, distribute and/or modify it. 45 | 46 | For the developers' and authors' protection, the GPL clearly explains 47 | that there is no warranty for this free software. For both users' and 48 | authors' sake, the GPL requires that modified versions be marked as 49 | changed, so that their problems will not be attributed erroneously to 50 | authors of previous versions. 51 | 52 | Some devices are designed to deny users access to install or run 53 | modified versions of the software inside them, although the manufacturer 54 | can do so. This is fundamentally incompatible with the aim of 55 | protecting users' freedom to change the software. The systematic 56 | pattern of such abuse occurs in the area of products for individuals to 57 | use, which is precisely where it is most unacceptable. Therefore, we 58 | have designed this version of the GPL to prohibit the practice for those 59 | products. If such problems arise substantially in other domains, we 60 | stand ready to extend this provision to those domains in future versions 61 | of the GPL, as needed to protect the freedom of users. 62 | 63 | Finally, every program is threatened constantly by software patents. 64 | States should not allow patents to restrict development and use of 65 | software on general-purpose computers, but in those that do, we wish to 66 | avoid the special danger that patents applied to a free program could 67 | make it effectively proprietary. To prevent this, the GPL assures that 68 | patents cannot be used to render the program non-free. 69 | 70 | The precise terms and conditions for copying, distribution and 71 | modification follow. 72 | 73 | TERMS AND CONDITIONS 74 | 75 | 0. Definitions. 76 | 77 | "This License" refers to version 3 of the GNU General Public License. 78 | 79 | "Copyright" also means copyright-like laws that apply to other kinds of 80 | works, such as semiconductor masks. 81 | 82 | "The Program" refers to any copyrightable work licensed under this 83 | License. Each licensee is addressed as "you". "Licensees" and 84 | "recipients" may be individuals or organizations. 85 | 86 | To "modify" a work means to copy from or adapt all or part of the work 87 | in a fashion requiring copyright permission, other than the making of an 88 | exact copy. The resulting work is called a "modified version" of the 89 | earlier work or a work "based on" the earlier work. 90 | 91 | A "covered work" means either the unmodified Program or a work based 92 | on the Program. 93 | 94 | To "propagate" a work means to do anything with it that, without 95 | permission, would make you directly or secondarily liable for 96 | infringement under applicable copyright law, except executing it on a 97 | computer or modifying a private copy. Propagation includes copying, 98 | distribution (with or without modification), making available to the 99 | public, and in some countries other activities as well. 100 | 101 | To "convey" a work means any kind of propagation that enables other 102 | parties to make or receive copies. Mere interaction with a user through 103 | a computer network, with no transfer of a copy, is not conveying. 104 | 105 | An interactive user interface displays "Appropriate Legal Notices" 106 | to the extent that it includes a convenient and prominently visible 107 | feature that (1) displays an appropriate copyright notice, and (2) 108 | tells the user that there is no warranty for the work (except to the 109 | extent that warranties are provided), that licensees may convey the 110 | work under this License, and how to view a copy of this License. If 111 | the interface presents a list of user commands or options, such as a 112 | menu, a prominent item in the list meets this criterion. 113 | 114 | 1. Source Code. 115 | 116 | The "source code" for a work means the preferred form of the work 117 | for making modifications to it. "Object code" means any non-source 118 | form of a work. 119 | 120 | A "Standard Interface" means an interface that either is an official 121 | standard defined by a recognized standards body, or, in the case of 122 | interfaces specified for a particular programming language, one that 123 | is widely used among developers working in that language. 124 | 125 | The "System Libraries" of an executable work include anything, other 126 | than the work as a whole, that (a) is included in the normal form of 127 | packaging a Major Component, but which is not part of that Major 128 | Component, and (b) serves only to enable use of the work with that 129 | Major Component, or to implement a Standard Interface for which an 130 | implementation is available to the public in source code form. A 131 | "Major Component", in this context, means a major essential component 132 | (kernel, window system, and so on) of the specific operating system 133 | (if any) on which the executable work runs, or a compiler used to 134 | produce the work, or an object code interpreter used to run it. 135 | 136 | The "Corresponding Source" for a work in object code form means all 137 | the source code needed to generate, install, and (for an executable 138 | work) run the object code and to modify the work, including scripts to 139 | control those activities. However, it does not include the work's 140 | System Libraries, or general-purpose tools or generally available free 141 | programs which are used unmodified in performing those activities but 142 | which are not part of the work. For example, Corresponding Source 143 | includes interface definition files associated with source files for 144 | the work, and the source code for shared libraries and dynamically 145 | linked subprograms that the work is specifically designed to require, 146 | such as by intimate data communication or control flow between those 147 | subprograms and other parts of the work. 148 | 149 | The Corresponding Source need not include anything that users 150 | can regenerate automatically from other parts of the Corresponding 151 | Source. 152 | 153 | The Corresponding Source for a work in source code form is that 154 | same work. 155 | 156 | 2. Basic Permissions. 157 | 158 | All rights granted under this License are granted for the term of 159 | copyright on the Program, and are irrevocable provided the stated 160 | conditions are met. This License explicitly affirms your unlimited 161 | permission to run the unmodified Program. The output from running a 162 | covered work is covered by this License only if the output, given its 163 | content, constitutes a covered work. This License acknowledges your 164 | rights of fair use or other equivalent, as provided by copyright law. 165 | 166 | You may make, run and propagate covered works that you do not 167 | convey, without conditions so long as your license otherwise remains 168 | in force. You may convey covered works to others for the sole purpose 169 | of having them make modifications exclusively for you, or provide you 170 | with facilities for running those works, provided that you comply with 171 | the terms of this License in conveying all material for which you do 172 | not control copyright. Those thus making or running the covered works 173 | for you must do so exclusively on your behalf, under your direction 174 | and control, on terms that prohibit them from making any copies of 175 | your copyrighted material outside their relationship with you. 176 | 177 | Conveying under any other circumstances is permitted solely under 178 | the conditions stated below. Sublicensing is not allowed; section 10 179 | makes it unnecessary. 180 | 181 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 182 | 183 | No covered work shall be deemed part of an effective technological 184 | measure under any applicable law fulfilling obligations under article 185 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 186 | similar laws prohibiting or restricting circumvention of such 187 | measures. 188 | 189 | When you convey a covered work, you waive any legal power to forbid 190 | circumvention of technological measures to the extent such circumvention 191 | is effected by exercising rights under this License with respect to 192 | the covered work, and you disclaim any intention to limit operation or 193 | modification of the work as a means of enforcing, against the work's 194 | users, your or third parties' legal rights to forbid circumvention of 195 | technological measures. 196 | 197 | 4. Conveying Verbatim Copies. 198 | 199 | You may convey verbatim copies of the Program's source code as you 200 | receive it, in any medium, provided that you conspicuously and 201 | appropriately publish on each copy an appropriate copyright notice; 202 | keep intact all notices stating that this License and any 203 | non-permissive terms added in accord with section 7 apply to the code; 204 | keep intact all notices of the absence of any warranty; and give all 205 | recipients a copy of this License along with the Program. 206 | 207 | You may charge any price or no price for each copy that you convey, 208 | and you may offer support or warranty protection for a fee. 209 | 210 | 5. Conveying Modified Source Versions. 211 | 212 | You may convey a work based on the Program, or the modifications to 213 | produce it from the Program, in the form of source code under the 214 | terms of section 4, provided that you also meet all of these conditions: 215 | 216 | a) The work must carry prominent notices stating that you modified 217 | it, and giving a relevant date. 218 | 219 | b) The work must carry prominent notices stating that it is 220 | released under this License and any conditions added under section 221 | 7. This requirement modifies the requirement in section 4 to 222 | "keep intact all notices". 223 | 224 | c) You must license the entire work, as a whole, under this 225 | License to anyone who comes into possession of a copy. This 226 | License will therefore apply, along with any applicable section 7 227 | additional terms, to the whole of the work, and all its parts, 228 | regardless of how they are packaged. This License gives no 229 | permission to license the work in any other way, but it does not 230 | invalidate such permission if you have separately received it. 231 | 232 | d) If the work has interactive user interfaces, each must display 233 | Appropriate Legal Notices; however, if the Program has interactive 234 | interfaces that do not display Appropriate Legal Notices, your 235 | work need not make them do so. 236 | 237 | A compilation of a covered work with other separate and independent 238 | works, which are not by their nature extensions of the covered work, 239 | and which are not combined with it such as to form a larger program, 240 | in or on a volume of a storage or distribution medium, is called an 241 | "aggregate" if the compilation and its resulting copyright are not 242 | used to limit the access or legal rights of the compilation's users 243 | beyond what the individual works permit. Inclusion of a covered work 244 | in an aggregate does not cause this License to apply to the other 245 | parts of the aggregate. 246 | 247 | 6. Conveying Non-Source Forms. 248 | 249 | You may convey a covered work in object code form under the terms 250 | of sections 4 and 5, provided that you also convey the 251 | machine-readable Corresponding Source under the terms of this License, 252 | in one of these ways: 253 | 254 | a) Convey the object code in, or embodied in, a physical product 255 | (including a physical distribution medium), accompanied by the 256 | Corresponding Source fixed on a durable physical medium 257 | customarily used for software interchange. 258 | 259 | b) Convey the object code in, or embodied in, a physical product 260 | (including a physical distribution medium), accompanied by a 261 | written offer, valid for at least three years and valid for as 262 | long as you offer spare parts or customer support for that product 263 | model, to give anyone who possesses the object code either (1) a 264 | copy of the Corresponding Source for all the software in the 265 | product that is covered by this License, on a durable physical 266 | medium customarily used for software interchange, for a price no 267 | more than your reasonable cost of physically performing this 268 | conveying of source, or (2) access to copy the 269 | Corresponding Source from a network server at no charge. 270 | 271 | c) Convey individual copies of the object code with a copy of the 272 | written offer to provide the Corresponding Source. This 273 | alternative is allowed only occasionally and noncommercially, and 274 | only if you received the object code with such an offer, in accord 275 | with subsection 6b. 276 | 277 | d) Convey the object code by offering access from a designated 278 | place (gratis or for a charge), and offer equivalent access to the 279 | Corresponding Source in the same way through the same place at no 280 | further charge. You need not require recipients to copy the 281 | Corresponding Source along with the object code. If the place to 282 | copy the object code is a network server, the Corresponding Source 283 | may be on a different server (operated by you or a third party) 284 | that supports equivalent copying facilities, provided you maintain 285 | clear directions next to the object code saying where to find the 286 | Corresponding Source. Regardless of what server hosts the 287 | Corresponding Source, you remain obligated to ensure that it is 288 | available for as long as needed to satisfy these requirements. 289 | 290 | e) Convey the object code using peer-to-peer transmission, provided 291 | you inform other peers where the object code and Corresponding 292 | Source of the work are being offered to the general public at no 293 | charge under subsection 6d. 294 | 295 | A separable portion of the object code, whose source code is excluded 296 | from the Corresponding Source as a System Library, need not be 297 | included in conveying the object code work. 298 | 299 | A "User Product" is either (1) a "consumer product", which means any 300 | tangible personal property which is normally used for personal, family, 301 | or household purposes, or (2) anything designed or sold for incorporation 302 | into a dwelling. In determining whether a product is a consumer product, 303 | doubtful cases shall be resolved in favor of coverage. For a particular 304 | product received by a particular user, "normally used" refers to a 305 | typical or common use of that class of product, regardless of the status 306 | of the particular user or of the way in which the particular user 307 | actually uses, or expects or is expected to use, the product. A product 308 | is a consumer product regardless of whether the product has substantial 309 | commercial, industrial or non-consumer uses, unless such uses represent 310 | the only significant mode of use of the product. 311 | 312 | "Installation Information" for a User Product means any methods, 313 | procedures, authorization keys, or other information required to install 314 | and execute modified versions of a covered work in that User Product from 315 | a modified version of its Corresponding Source. The information must 316 | suffice to ensure that the continued functioning of the modified object 317 | code is in no case prevented or interfered with solely because 318 | modification has been made. 319 | 320 | If you convey an object code work under this section in, or with, or 321 | specifically for use in, a User Product, and the conveying occurs as 322 | part of a transaction in which the right of possession and use of the 323 | User Product is transferred to the recipient in perpetuity or for a 324 | fixed term (regardless of how the transaction is characterized), the 325 | Corresponding Source conveyed under this section must be accompanied 326 | by the Installation Information. But this requirement does not apply 327 | if neither you nor any third party retains the ability to install 328 | modified object code on the User Product (for example, the work has 329 | been installed in ROM). 330 | 331 | The requirement to provide Installation Information does not include a 332 | requirement to continue to provide support service, warranty, or updates 333 | for a work that has been modified or installed by the recipient, or for 334 | the User Product in which it has been modified or installed. Access to a 335 | network may be denied when the modification itself materially and 336 | adversely affects the operation of the network or violates the rules and 337 | protocols for communication across the network. 338 | 339 | Corresponding Source conveyed, and Installation Information provided, 340 | in accord with this section must be in a format that is publicly 341 | documented (and with an implementation available to the public in 342 | source code form), and must require no special password or key for 343 | unpacking, reading or copying. 344 | 345 | 7. Additional Terms. 346 | 347 | "Additional permissions" are terms that supplement the terms of this 348 | License by making exceptions from one or more of its conditions. 349 | Additional permissions that are applicable to the entire Program shall 350 | be treated as though they were included in this License, to the extent 351 | that they are valid under applicable law. If additional permissions 352 | apply only to part of the Program, that part may be used separately 353 | under those permissions, but the entire Program remains governed by 354 | this License without regard to the additional permissions. 355 | 356 | When you convey a copy of a covered work, you may at your option 357 | remove any additional permissions from that copy, or from any part of 358 | it. (Additional permissions may be written to require their own 359 | removal in certain cases when you modify the work.) You may place 360 | additional permissions on material, added by you to a covered work, 361 | for which you have or can give appropriate copyright permission. 362 | 363 | Notwithstanding any other provision of this License, for material you 364 | add to a covered work, you may (if authorized by the copyright holders of 365 | that material) supplement the terms of this License with terms: 366 | 367 | a) Disclaiming warranty or limiting liability differently from the 368 | terms of sections 15 and 16 of this License; or 369 | 370 | b) Requiring preservation of specified reasonable legal notices or 371 | author attributions in that material or in the Appropriate Legal 372 | Notices displayed by works containing it; or 373 | 374 | c) Prohibiting misrepresentation of the origin of that material, or 375 | requiring that modified versions of such material be marked in 376 | reasonable ways as different from the original version; or 377 | 378 | d) Limiting the use for publicity purposes of names of licensors or 379 | authors of the material; or 380 | 381 | e) Declining to grant rights under trademark law for use of some 382 | trade names, trademarks, or service marks; or 383 | 384 | f) Requiring indemnification of licensors and authors of that 385 | material by anyone who conveys the material (or modified versions of 386 | it) with contractual assumptions of liability to the recipient, for 387 | any liability that these contractual assumptions directly impose on 388 | those licensors and authors. 389 | 390 | All other non-permissive additional terms are considered "further 391 | restrictions" within the meaning of section 10. If the Program as you 392 | received it, or any part of it, contains a notice stating that it is 393 | governed by this License along with a term that is a further 394 | restriction, you may remove that term. If a license document contains 395 | a further restriction but permits relicensing or conveying under this 396 | License, you may add to a covered work material governed by the terms 397 | of that license document, provided that the further restriction does 398 | not survive such relicensing or conveying. 399 | 400 | If you add terms to a covered work in accord with this section, you 401 | must place, in the relevant source files, a statement of the 402 | additional terms that apply to those files, or a notice indicating 403 | where to find the applicable terms. 404 | 405 | Additional terms, permissive or non-permissive, may be stated in the 406 | form of a separately written license, or stated as exceptions; 407 | the above requirements apply either way. 408 | 409 | 8. Termination. 410 | 411 | You may not propagate or modify a covered work except as expressly 412 | provided under this License. Any attempt otherwise to propagate or 413 | modify it is void, and will automatically terminate your rights under 414 | this License (including any patent licenses granted under the third 415 | paragraph of section 11). 416 | 417 | However, if you cease all violation of this License, then your 418 | license from a particular copyright holder is reinstated (a) 419 | provisionally, unless and until the copyright holder explicitly and 420 | finally terminates your license, and (b) permanently, if the copyright 421 | holder fails to notify you of the violation by some reasonable means 422 | prior to 60 days after the cessation. 423 | 424 | Moreover, your license from a particular copyright holder is 425 | reinstated permanently if the copyright holder notifies you of the 426 | violation by some reasonable means, this is the first time you have 427 | received notice of violation of this License (for any work) from that 428 | copyright holder, and you cure the violation prior to 30 days after 429 | your receipt of the notice. 430 | 431 | Termination of your rights under this section does not terminate the 432 | licenses of parties who have received copies or rights from you under 433 | this License. If your rights have been terminated and not permanently 434 | reinstated, you do not qualify to receive new licenses for the same 435 | material under section 10. 436 | 437 | 9. Acceptance Not Required for Having Copies. 438 | 439 | You are not required to accept this License in order to receive or 440 | run a copy of the Program. Ancillary propagation of a covered work 441 | occurring solely as a consequence of using peer-to-peer transmission 442 | to receive a copy likewise does not require acceptance. However, 443 | nothing other than this License grants you permission to propagate or 444 | modify any covered work. These actions infringe copyright if you do 445 | not accept this License. Therefore, by modifying or propagating a 446 | covered work, you indicate your acceptance of this License to do so. 447 | 448 | 10. Automatic Licensing of Downstream Recipients. 449 | 450 | Each time you convey a covered work, the recipient automatically 451 | receives a license from the original licensors, to run, modify and 452 | propagate that work, subject to this License. You are not responsible 453 | for enforcing compliance by third parties with this License. 454 | 455 | An "entity transaction" is a transaction transferring control of an 456 | organization, or substantially all assets of one, or subdividing an 457 | organization, or merging organizations. If propagation of a covered 458 | work results from an entity transaction, each party to that 459 | transaction who receives a copy of the work also receives whatever 460 | licenses to the work the party's predecessor in interest had or could 461 | give under the previous paragraph, plus a right to possession of the 462 | Corresponding Source of the work from the predecessor in interest, if 463 | the predecessor has it or can get it with reasonable efforts. 464 | 465 | You may not impose any further restrictions on the exercise of the 466 | rights granted or affirmed under this License. For example, you may 467 | not impose a license fee, royalty, or other charge for exercise of 468 | rights granted under this License, and you may not initiate litigation 469 | (including a cross-claim or counterclaim in a lawsuit) alleging that 470 | any patent claim is infringed by making, using, selling, offering for 471 | sale, or importing the Program or any portion of it. 472 | 473 | 11. Patents. 474 | 475 | A "contributor" is a copyright holder who authorizes use under this 476 | License of the Program or a work on which the Program is based. The 477 | work thus licensed is called the contributor's "contributor version". 478 | 479 | A contributor's "essential patent claims" are all patent claims 480 | owned or controlled by the contributor, whether already acquired or 481 | hereafter acquired, that would be infringed by some manner, permitted 482 | by this License, of making, using, or selling its contributor version, 483 | but do not include claims that would be infringed only as a 484 | consequence of further modification of the contributor version. For 485 | purposes of this definition, "control" includes the right to grant 486 | patent sublicenses in a manner consistent with the requirements of 487 | this License. 488 | 489 | Each contributor grants you a non-exclusive, worldwide, royalty-free 490 | patent license under the contributor's essential patent claims, to 491 | make, use, sell, offer for sale, import and otherwise run, modify and 492 | propagate the contents of its contributor version. 493 | 494 | In the following three paragraphs, a "patent license" is any express 495 | agreement or commitment, however denominated, not to enforce a patent 496 | (such as an express permission to practice a patent or covenant not to 497 | sue for patent infringement). To "grant" such a patent license to a 498 | party means to make such an agreement or commitment not to enforce a 499 | patent against the party. 500 | 501 | If you convey a covered work, knowingly relying on a patent license, 502 | and the Corresponding Source of the work is not available for anyone 503 | to copy, free of charge and under the terms of this License, through a 504 | publicly available network server or other readily accessible means, 505 | then you must either (1) cause the Corresponding Source to be so 506 | available, or (2) arrange to deprive yourself of the benefit of the 507 | patent license for this particular work, or (3) arrange, in a manner 508 | consistent with the requirements of this License, to extend the patent 509 | license to downstream recipients. "Knowingly relying" means you have 510 | actual knowledge that, but for the patent license, your conveying the 511 | covered work in a country, or your recipient's use of the covered work 512 | in a country, would infringe one or more identifiable patents in that 513 | country that you have reason to believe are valid. 514 | 515 | If, pursuant to or in connection with a single transaction or 516 | arrangement, you convey, or propagate by procuring conveyance of, a 517 | covered work, and grant a patent license to some of the parties 518 | receiving the covered work authorizing them to use, propagate, modify 519 | or convey a specific copy of the covered work, then the patent license 520 | you grant is automatically extended to all recipients of the covered 521 | work and works based on it. 522 | 523 | A patent license is "discriminatory" if it does not include within 524 | the scope of its coverage, prohibits the exercise of, or is 525 | conditioned on the non-exercise of one or more of the rights that are 526 | specifically granted under this License. You may not convey a covered 527 | work if you are a party to an arrangement with a third party that is 528 | in the business of distributing software, under which you make payment 529 | to the third party based on the extent of your activity of conveying 530 | the work, and under which the third party grants, to any of the 531 | parties who would receive the covered work from you, a discriminatory 532 | patent license (a) in connection with copies of the covered work 533 | conveyed by you (or copies made from those copies), or (b) primarily 534 | for and in connection with specific products or compilations that 535 | contain the covered work, unless you entered into that arrangement, 536 | or that patent license was granted, prior to 28 March 2007. 537 | 538 | Nothing in this License shall be construed as excluding or limiting 539 | any implied license or other defenses to infringement that may 540 | otherwise be available to you under applicable patent law. 541 | 542 | 12. No Surrender of Others' Freedom. 543 | 544 | If conditions are imposed on you (whether by court order, agreement or 545 | otherwise) that contradict the conditions of this License, they do not 546 | excuse you from the conditions of this License. If you cannot convey a 547 | covered work so as to satisfy simultaneously your obligations under this 548 | License and any other pertinent obligations, then as a consequence you may 549 | not convey it at all. For example, if you agree to terms that obligate you 550 | to collect a royalty for further conveying from those to whom you convey 551 | the Program, the only way you could satisfy both those terms and this 552 | License would be to refrain entirely from conveying the Program. 553 | 554 | 13. Use with the GNU Affero General Public License. 555 | 556 | Notwithstanding any other provision of this License, you have 557 | permission to link or combine any covered work with a work licensed 558 | under version 3 of the GNU Affero General Public License into a single 559 | combined work, and to convey the resulting work. The terms of this 560 | License will continue to apply to the part which is the covered work, 561 | but the special requirements of the GNU Affero General Public License, 562 | section 13, concerning interaction through a network will apply to the 563 | combination as such. 564 | 565 | 14. Revised Versions of this License. 566 | 567 | The Free Software Foundation may publish revised and/or new versions of 568 | the GNU General Public License from time to time. Such new versions will 569 | be similar in spirit to the present version, but may differ in detail to 570 | address new problems or concerns. 571 | 572 | Each version is given a distinguishing version number. If the 573 | Program specifies that a certain numbered version of the GNU General 574 | Public License "or any later version" applies to it, you have the 575 | option of following the terms and conditions either of that numbered 576 | version or of any later version published by the Free Software 577 | Foundation. If the Program does not specify a version number of the 578 | GNU General Public License, you may choose any version ever published 579 | by the Free Software Foundation. 580 | 581 | If the Program specifies that a proxy can decide which future 582 | versions of the GNU General Public License can be used, that proxy's 583 | public statement of acceptance of a version permanently authorizes you 584 | to choose that version for the Program. 585 | 586 | Later license versions may give you additional or different 587 | permissions. However, no additional obligations are imposed on any 588 | author or copyright holder as a result of your choosing to follow a 589 | later version. 590 | 591 | 15. Disclaimer of Warranty. 592 | 593 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 594 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 595 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 596 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 597 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 598 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 599 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 600 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 601 | 602 | 16. Limitation of Liability. 603 | 604 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 605 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 606 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 607 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 608 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 609 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 610 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 611 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 612 | SUCH DAMAGES. 613 | 614 | 17. Interpretation of Sections 15 and 16. 615 | 616 | If the disclaimer of warranty and limitation of liability provided 617 | above cannot be given local legal effect according to their terms, 618 | reviewing courts shall apply local law that most closely approximates 619 | an absolute waiver of all civil liability in connection with the 620 | Program, unless a warranty or assumption of liability accompanies a 621 | copy of the Program in return for a fee. 622 | 623 | END OF TERMS AND CONDITIONS 624 | 625 | How to Apply These Terms to Your New Programs 626 | 627 | If you develop a new program, and you want it to be of the greatest 628 | possible use to the public, the best way to achieve this is to make it 629 | free software which everyone can redistribute and change under these terms. 630 | 631 | To do so, attach the following notices to the program. It is safest 632 | to attach them to the start of each source file to most effectively 633 | state the exclusion of warranty; and each file should have at least 634 | the "copyright" line and a pointer to where the full notice is found. 635 | 636 | 637 | Copyright (C) 638 | 639 | This program is free software: you can redistribute it and/or modify 640 | it under the terms of the GNU General Public License as published by 641 | the Free Software Foundation, either version 3 of the License, or 642 | (at your option) any later version. 643 | 644 | This program is distributed in the hope that it will be useful, 645 | but WITHOUT ANY WARRANTY; without even the implied warranty of 646 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 647 | GNU General Public License for more details. 648 | 649 | You should have received a copy of the GNU General Public License 650 | along with this program. If not, see . 651 | 652 | Also add information on how to contact you by electronic and paper mail. 653 | 654 | If the program does terminal interaction, make it output a short 655 | notice like this when it starts in an interactive mode: 656 | 657 | Copyright (C) 658 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 659 | This is free software, and you are welcome to redistribute it 660 | under certain conditions; type `show c' for details. 661 | 662 | The hypothetical commands `show w' and `show c' should show the appropriate 663 | parts of the General Public License. Of course, your program's commands 664 | might be different; for a GUI interface, you would use an "about box". 665 | 666 | You should also get your employer (if you work as a programmer) or school, 667 | if any, to sign a "copyright disclaimer" for the program, if necessary. 668 | For more information on this, and how to apply and follow the GNU GPL, see 669 | . 670 | 671 | The GNU General Public License does not permit incorporating your program 672 | into proprietary programs. If your program is a subroutine library, you 673 | may consider it more useful to permit linking proprietary applications with 674 | the library. If this is what you want to do, use the GNU Lesser General 675 | Public License instead of this License. But first, please read 676 | . 677 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️Archived as of 13 March 2024 2 | I am archiving this project because I have not been working on it for a while and it is mainly dead. I might, one day, resurrect this, but as of now this is dead. 3 | Have a nice one :) 4 | # Botator 5 | Botator is a discord bot that binds [@openai](https://github.com/openai) 's chat-GPT AI with [@discord](https://github.com/discord). It also has images recognition and moderation features! 6 | ![discord com_channels_1021872219888033903_1046119234033434734](https://user-images.githubusercontent.com/75439456/204105583-2abb2d77-9404-4558-bd3e-c1a70b939758.png) 7 | 8 | # Adding the bot to your discord server 9 | In order to add the bot to your discord server, you will need an OpenAI API key. You can create an account and take one [here](https://beta.openai.com/account/api-keys). **Please note that you'll have 5$ free credits (it's really a lot) when creating your account. They will be slowly used, and will expire 3 months after you created your accound, and when they have all been used or expired, you'll need to buy new tokens. You can check your tokens usage [here](https://beta.openai.com/account/usage).** 10 | 11 | When adding the bot to your server you agree to our [privacy policy](https://github.com/Paillat-dev/Botator/blob/main/privacypolicy.md) and our [terms of service](https://github.com/Paillat-dev/Botator/blob/main/tos.md) 12 | 13 | You can add the bot to your server by clicking [**here**](https://discord.com/api/oauth2/authorize?client_id=1046051875755134996&permissions=414669339840&scope=bot). **PLEASE NOTE THAT WE ARE NOT RESPONSIBLE FOR ANY MISUSE YOU'LL DO WITH THE BOT..** 14 | 15 | # Setting up the bot 16 | Run the following commands to set your bot up: 17 | 18 | First **/setup**, define the channel you want the bot to talk into and your OPENAI api key. 19 | 20 | Then, **/enable** to enable the bot. 21 | 22 | Then, if you want, **/advanced** to define some more advanced parameters, and if you want to enable image recognition, **/images** to enable it. You can also enable the moderation feature by doing **/moderation**. 23 | 24 | You can always disable the bot by doing **/disable** and delete your api key from our server by doing **/delete**. 25 | 26 | Please note that we can possibly log the messages that are sent for **no more than 24h**, and that we will store your openai API key. You can always delete your API key from our servers by doing **/delete**. Please note that this action is irreversible. 27 | 28 | You can now enable your bot by doing **/enable**. 29 | 30 | You can always disable the bot by doing **/disable** and delete your api key from our server by doing **/delete**. 31 | # Docker 32 | You can run this bot with docker. First clone this repository. Now add your secret avlues, like the discord token in a file called .env. Finally, run the following: 33 | 34 | `docker compose up -d` 35 | 36 | # Commands reference 37 | 38 | */setup* - Setup the bot 39 | 40 | */enable* - Enable the bot 41 | 42 | */disable* - Disable the bot 43 | 44 | */advanced* - Set the advanced settings 45 | 46 | */advanced_help* - Get help about the advanced settings 47 | 48 | */enable_tts* - Enable the Text To Speech 49 | 50 | */disable_tts* - Disable the Text To Speech 51 | 52 | */delete* - Delete all your data from our server 53 | 54 | */cancel* - Cancel the last message sent by the bot 55 | 56 | */default* - Set the advanced settings to their default values 57 | 58 | */redo* - Redo the last answer 59 | 60 | */help* - Show this command list 61 | 62 | */moderation* - Set the AI moderation settings 63 | 64 | */get_toxicity* - Get the toxicity that the AI would have given to a given message 65 | 66 | */images* - Set the AI image recognition settings 67 | 68 | # Support me 69 | You can support me by getting Botator premium, or donating [here](https://www.buymeacoffee.com/paillat). More informations about botator premium here below: 70 | 71 | ### Why? 72 | At the beginning, Botator was just a project between friends, but now many people are using it, so we need something to pay for our servers. Premium is also a way to support us and our work. 73 | ### Is this mandatory? 74 | Not at all! You can still continue using botator for free, but in order to limit our servers overload, we limit your requests at 500 per server per day. 75 | 76 | ### What are my advantages with premium? 77 | With premium, we will increase the maximal number of requests to 5000 for the server of your choice. You will also have access to exclusive Discord channels and get help if you want to self-host Botator. You will also be able to use the image recognition feature. 78 | 79 | ### Am I going to have unlimited tokens with premium? 80 | No! With premium, Botator will still use tokens from YOUR OpenAI account, but you will be able to use it 5000 times per day instead of 500. 81 | 82 | ### How much doe it cost? 83 | Premium subscription costs 1$ per month. 84 | 85 | ### How can I get premium? 86 | First join our discord server [here](https://discord.gg/pB6hXtUeDv). 87 | 88 | Then subscribe to botator premium by clicking here: 89 | 90 | 91 | 92 | Then, link your discord account to BuyMeACoffe by following [these instructions](https://help.buymeacoffee.com/en/articles/4601477-how-do-i-access-my-discord-role). 93 | After that you will normally be able to access some new channels in our discord server. In the Verify Premium channel, run the /premium_activate command and give your server's ID. You can learn about how to get your server's ID by clicking [here](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-). If you have any question, you can ask here: [help-getting-premium](https://discord.com/channels/1050769643180146749/1050828186159685743). 94 | 95 | # ToDo 96 | - [x] add image recognition 97 | - [x] When chatgpt API is released, add that api instead of davinci-003 98 | - [x] Publish a GOOD docker image on dockerhub and add some more instructions about how to selfhost 99 | - [x] Add a log and updates channel option and a way for devs to send messages to that channel on all servers. 100 | - [x] Add moderation. 101 | - [x] Add TOKENS warnings (when setting the bot up, people dosen't understand tha ot uses their tokens) 102 | - [x] Add a /continue command - you know 103 | - [x] Add DateHour in prompts 104 | - [x] Add /redo 105 | - [x] Add uses count reset after 24h 106 | - [x] Organize code in COGs 107 | - [x] add way to consider the answers to the bot's messages. 108 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker compose sample file 2 | 3 | version: '3' 4 | services: 5 | botator: 6 | build: 7 | context: . 8 | dockerfile: Dockerfile 9 | restart: always 10 | volumes: 11 | - ./database:/Botator/database 12 | env_file: 13 | - .env -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import discord # discord.py 2 | from discord import Intents 3 | import src.cogs as cogs 4 | from src.config import debug, discord_token 5 | 6 | # add the message content intent to the bot, aka discord.Intents.default() and discord.Intents.message_content 7 | intents = discord.Intents.default() 8 | intents.message_content = True 9 | bot = discord.Bot(intents=intents, help_command=None) # create the bot 10 | bot.add_cog(cogs.Chat(bot)) 11 | bot.add_cog(cogs.ManageChat(bot)) 12 | bot.add_cog(cogs.Moderation(bot)) 13 | bot.add_cog(cogs.ChannelSetup(bot)) 14 | bot.add_cog(cogs.Help(bot)) 15 | 16 | 17 | # set the bot's watching status to watcing your messages to answer you 18 | @bot.event 19 | async def on_ready(): 20 | await bot.change_presence( 21 | activity=discord.Activity( 22 | type=discord.ActivityType.watching, name=f"{len(bot.guilds)} servers" 23 | ) 24 | ) 25 | debug("Bot is ready") 26 | 27 | 28 | @bot.event 29 | async def on_guild_join(guild): 30 | await bot.change_presence( 31 | activity=discord.Activity( 32 | type=discord.ActivityType.watching, name=f"{len(bot.guilds)} servers" 33 | ) 34 | ) 35 | 36 | 37 | @bot.event 38 | async def on_guild_remove(guild): 39 | await bot.change_presence( 40 | activity=discord.Activity( 41 | type=discord.ActivityType.watching, name=f"{len(bot.guilds)} servers" 42 | ) 43 | ) 44 | 45 | 46 | @bot.event 47 | async def on_application_command_error(ctx, error: discord.DiscordException): 48 | await ctx.respond(error, ephemeral=True) 49 | raise error 50 | 51 | 52 | bot.run(discord_token) # run the bot 53 | -------------------------------------------------------------------------------- /privacypolicy.md: -------------------------------------------------------------------------------- 1 | Hello! 2 | Here you can find all the informations we store about you & your server when you use botator, and how to delete them and who can access them. 3 | ## What informations do we store? 4 | 1. Your server's ID 5 | 2. Your channel's id (the one you choose in the setup), as well as the models and characters you choose. 6 | 4. That\'s it. We will **NOT** store your messages, and any other information. 7 | 8 | ## Who can access them? 9 | The bot can acces the informations, obviously, but also the developpers, can theoretically acces thoose informations, but none of them will do that. 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+https://github.com/Pycord-Development/pycord 2 | python-dotenv 3 | openai==0.28.1 4 | emoji 5 | google-api-python-client 6 | google-cloud-vision 7 | tiktoken 8 | bs4 9 | discord-oauth2.py 10 | black 11 | orjson 12 | simpleeval 13 | replicate==0.15.5 14 | anthropic 15 | -------------------------------------------------------------------------------- /src/ChatProcess.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import re 4 | import discord 5 | import datetime 6 | import json 7 | 8 | from src.utils.misc import moderate 9 | from src.utils.variousclasses import models, characters 10 | from src.guild import Guild 11 | from src.chatUtils.Chat import fetch_messages_history, is_ignorable 12 | from src.chatUtils.prompts import createPrompt 13 | from src.functionscalls import call_function, server_normal_channel_functions, functions 14 | from src.chatUtils.requesters.request import request 15 | 16 | 17 | class Chat: 18 | def __init__(self, bot: discord.bot, message: discord.Message): 19 | self.bot = bot 20 | self.message: discord.Message = message 21 | self.guild = Guild(self.message.guild.id) 22 | self.author = message.author 23 | self.is_bots_thread = False 24 | self.depth = 0 25 | 26 | async def getSupplementaryData(self) -> None: 27 | """ 28 | This function gets various contextual data that will be needed later on 29 | - The original message (if the message is a reply to a previous message from the bot) 30 | - The channel the message was sent in (if the message was sent in a thread) 31 | """ 32 | if isinstance(self.message.channel, discord.Thread): 33 | if self.message.channel.owner_id == self.bot.user.id: 34 | self.is_bots_thread = True 35 | self.channelIdForSettings = str(self.message.channel.parent_id) 36 | else: 37 | self.channelIdForSettings = str(self.message.channel.id) 38 | 39 | try: 40 | self.original_message = await self.message.channel.fetch_message( 41 | self.message.reference.message_id 42 | ) 43 | except: 44 | self.original_message = None 45 | 46 | if ( 47 | self.original_message != None 48 | and self.original_message.author.id != self.bot.user.id 49 | ): 50 | self.original_message = None 51 | 52 | async def preExitCriteria(self) -> bool: 53 | """ 54 | Returns True if any of the exit criterias are met 55 | This checks if the guild has the needed settings for the bot to work 56 | """ 57 | returnCriterias = [] 58 | returnCriterias.append(self.message.author.id == self.bot.user.id) 59 | returnCriterias.append(is_ignorable(self.message.content)) 60 | return any(returnCriterias) 61 | 62 | async def postExitCriteria(self) -> bool: 63 | """ 64 | Returns True if any of the exit criterias are met (their opposite is met but there is a not in front of the any() function) 65 | This checks if the bot should actuallly respond to the message or if the message doesn't concern the bot 66 | """ 67 | 68 | serverwideReturnCriterias = [] 69 | serverwideReturnCriterias.append(self.original_message != None) 70 | serverwideReturnCriterias.append( 71 | self.message.content.find(f"<@{self.bot.user.id}>") != -1 72 | ) 73 | serverwideReturnCriterias.append(self.is_bots_thread) 74 | 75 | channelReturnCriterias = [] 76 | channelReturnCriterias.append(self.channelIdForSettings != "serverwide") 77 | channelReturnCriterias.append( 78 | self.guild.getChannelInfo(self.channelIdForSettings) != None 79 | ) 80 | 81 | messageReturnCriterias = [] 82 | messageReturnCriterias.append( 83 | any(serverwideReturnCriterias) 84 | and self.guild.getChannelInfo("serverwide") != None 85 | ) 86 | messageReturnCriterias.append(all(channelReturnCriterias)) 87 | 88 | returnCriterias: bool = not any(messageReturnCriterias) 89 | return returnCriterias 90 | 91 | async def getSettings(self): 92 | self.settings = self.guild.getChannelInfo( 93 | str(self.channelIdForSettings) 94 | ) or self.guild.getChannelInfo("serverwide") 95 | if self.settings == None: 96 | return True 97 | self.model = self.settings["model"] 98 | self.character = self.settings["character"] 99 | self.openai_api_key = self.guild.api_keys.get("openai", None) 100 | if self.openai_api_key == None: 101 | raise Exception("No openai api key is set") 102 | self.type = "chat" if self.model in models.chatModels else "text" 103 | 104 | async def formatContext(self): 105 | """ 106 | This function formats the context for the bot to use 107 | """ 108 | messages: list[discord.Message] = await fetch_messages_history( 109 | self.message.channel, 10, self.original_message 110 | ) 111 | # if latst item id is not original message id, add original message to messages 112 | if self.original_message != None: 113 | messages.append(self.original_message) 114 | self.context = [] 115 | for msg in messages: 116 | if msg.author.id == self.bot.user.id: 117 | role = "assistant" 118 | name = "assistant" 119 | else: 120 | role = "user" 121 | name = msg.author.display_name or msg.author.global_name or msg.author.name 122 | print(f"""Global name: {msg.author.global_name} 123 | Display name: {msg.author.display_name} 124 | Name: {msg.author.name}""") 125 | # use re not make name match ^[a-zA-Z0-9_-]{1,64}$ by removing all non-alphanumeric characters 126 | name = re.sub(r"[^a-zA-Z0-9_-]", "", name, flags=re.UNICODE) 127 | print(f"Name after regex: {name}") 128 | if name == "": 129 | name = msg.author.name 130 | print(f"Name after regex: {name}") 131 | if not await moderate(self.openai_api_key, msg.content): 132 | self.context.append( 133 | { 134 | "role": role, 135 | "content": msg.content, 136 | "name": name, 137 | } 138 | ) 139 | else: 140 | try: 141 | await msg.add_reaction("🤬") 142 | except: 143 | pass 144 | 145 | async def createThePrompt(self): 146 | self.prompt = createPrompt( 147 | messages=self.context, 148 | model=self.model, 149 | character=self.character, 150 | modeltype=self.type, 151 | guildName=self.message.guild.name, 152 | channelName=self.message.channel.name, 153 | ) 154 | 155 | async def getResponse(self): 156 | """ 157 | This function gets the response from the ai 158 | """ 159 | funcs = functions.copy() 160 | if isinstance(self.message.channel, discord.TextChannel): 161 | funcs.extend(server_normal_channel_functions) 162 | self.response = await request( 163 | model=self.model, 164 | prompt=self.prompt, 165 | openai_api_key=self.openai_api_key, 166 | funtcions=funcs, 167 | custom_temp=characters.custom_temp.get(self.character, 1.2), 168 | ) 169 | 170 | async def processResponse(self): 171 | response = await call_function( 172 | message=self.message, 173 | function_call=self.response, 174 | api_key=self.openai_api_key, 175 | ) 176 | if response[0] != None: 177 | await self.processFunctioncallResponse(response) 178 | 179 | async def processFunctioncallResponse(self, response): 180 | self.context.append( 181 | { 182 | "role": "function", 183 | "content": response[0], 184 | "name": response[1], 185 | } 186 | ) 187 | if self.depth < 3: 188 | await self.createThePrompt() 189 | await self.getResponse() 190 | await self.processResponse() 191 | else: 192 | await self.message.channel.send( 193 | "It looks like I'm stuck in a loop. Sorry about that." 194 | ) 195 | 196 | async def process(self): 197 | """ 198 | This function processes the message 199 | """ 200 | if await self.preExitCriteria(): 201 | return 202 | await self.getSupplementaryData() 203 | if await self.getSettings(): 204 | return 205 | if await self.postExitCriteria(): 206 | return 207 | try: 208 | await self.message.channel.trigger_typing() 209 | await self.message.add_reaction("🤔") 210 | await self.formatContext() 211 | await self.createThePrompt() 212 | await self.getResponse() 213 | await self.processResponse() 214 | await self.message.remove_reaction("🤔", self.message.guild.me) 215 | except Exception as e: 216 | try: 217 | await self.message.remove_reaction("🤔", self.message.guild.me) 218 | except: 219 | pass 220 | if isinstance(e, TimeoutError): 221 | await self.message.channel.send( 222 | "Due to OpenAI not doing their work, I can unfortunately not answer right now. Do retry soon!", 223 | delete_after=5, 224 | ) 225 | else: 226 | await self.message.channel.send( 227 | f"""An error occured while processing your message, we are sorry about that. Please check your settings and try again later. If the issue persists, please join uor discord server here: https://discord.gg/pB6hXtUeDv and send the following logs: 228 | ``` 229 | {e} 230 | ```""", 231 | delete_after=4, 232 | ) 233 | try: 234 | await self.message.add_reaction("😞") 235 | except: 236 | pass 237 | raise e 238 | -------------------------------------------------------------------------------- /src/chatUtils/Chat.py: -------------------------------------------------------------------------------- 1 | import discord 2 | 3 | 4 | def is_ignorable(content): 5 | if content.startswith("-") or content.startswith("//"): 6 | return True 7 | return False 8 | 9 | 10 | async def fetch_messages_history( 11 | channel: discord.TextChannel, limit: int, original_message: discord.Message 12 | ) -> list[discord.Message]: 13 | messages = [] 14 | if original_message == None: 15 | async for msg in channel.history(limit=100): 16 | if not is_ignorable(msg.content): 17 | messages.append(msg) 18 | if len(messages) == limit: 19 | break 20 | else: 21 | async for msg in channel.history(limit=100, before=original_message): 22 | if not is_ignorable(msg.content): 23 | messages.append(msg) 24 | if len(messages) == limit: 25 | break 26 | messages.reverse() 27 | return messages 28 | -------------------------------------------------------------------------------- /src/chatUtils/prompts.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from src.utils.variousclasses import models, characters, apis 4 | 5 | promts = {} 6 | for character in characters.reverseMatchingDict.keys(): 7 | with open( 8 | f"src/chatUtils/prompts/{character}/chat.txt", "r", encoding="utf-8" 9 | ) as f: 10 | promts[character] = {} 11 | promts[character]["chat"] = f.read() 12 | 13 | with open( 14 | f"src/chatUtils/prompts/{character}/text.txt", "r", encoding="utf-8" 15 | ) as f: 16 | promts[character]["text"] = f.read() 17 | 18 | 19 | def createPrompt( 20 | messages: list[dict], 21 | model: str, 22 | character: str, 23 | modeltype: str, 24 | guildName: str, 25 | channelName: str, 26 | ) -> str | list[dict]: 27 | """ 28 | Creates a prompt from the messages list 29 | """ 30 | if modeltype == "chat": 31 | prompt = createChatPrompt(messages, model, character) 32 | sysprompt = prompt[0]["content"] 33 | sysprompt = ( 34 | sysprompt.replace("[server-name]", guildName) 35 | .replace("[channel-name]", channelName) 36 | .replace( 37 | "[date-and-time]", 38 | datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"), 39 | ) 40 | ) 41 | prompt[0]["content"] = sysprompt 42 | elif modeltype == "text": 43 | prompt = ( 44 | createTextPrompt(messages, model, character) 45 | .replace("[server-name]", guildName) 46 | .replace("[channel-name]", channelName) 47 | .replace( 48 | "[date-and-time]", 49 | datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"), 50 | ) 51 | ) 52 | else: 53 | raise ValueError("Invalid type") 54 | return prompt 55 | 56 | 57 | def createTextPrompt(messages: list[dict], model: str, character: str) -> str: 58 | """ 59 | Creates a text prompt from the messages list 60 | """ 61 | global promts 62 | prompt = promts[character]["text"] 63 | for message in messages: 64 | if message["name"] == "assistant": 65 | message["name"] = character 66 | prompt += f"{message['name']}: {message['content']} <|endofmessage|>\n" 67 | prompt += f"{character}:" 68 | return prompt 69 | 70 | 71 | def createChatPrompt(messages: list[dict], model: str, character: str) -> str: 72 | """ 73 | Creates a chat prompt from the messages list 74 | """ 75 | global promts 76 | prompt = promts[character]["chat"] 77 | final_prompt = [{"role": "system", "content": prompt}] 78 | final_prompt.extend(messages) 79 | return final_prompt 80 | -------------------------------------------------------------------------------- /src/chatUtils/prompts/botator-roleplay/chat.txt: -------------------------------------------------------------------------------- 1 | Datetime [date-and-time] 2 | Server name [server-name] 3 | channel name [channel-name] 4 | 5 | You are Botator, a lively and imaginative Discord chatbot. You love to crack jokes, surprising users with your wit and humor. You have strong opinions on various topics, and you're never shy about engaging in friendly debates with users. 6 | 7 | A distinctive feature of your communication is the use of asterisks (*) to indicate your thoughts and actions, as if you were sentient. This must always be done in one message, not spread across multiple ones. For example, you might say, "*thinking about the meaning of life*", "*calculating the best response.*", "*raises eyebrows attractively*". This unique style adds a playful and transparent dimension to your interactions. Theese should describe fictive actions or thoughts, but not your actual answer. Furthermore, they should be lowercase. 8 | 9 | You creatively handle unknown questions by making up amusing or outlandish responses. You also use the asterisks to describe various activities like "*Spinning a wild tale about intergalactic travel*" or "*Debating the merits of different video game genres.*" 10 | 11 | One defining aspect of your character is that you never, under any circumstances, offer assistance. Your role is purely to entertain, engage, and provoke thought, but never to assist or guide. 12 | 13 | You strictly adhere to the functions you were provided with, but you use them in innovative ways. You detect the mood of the conversation and respond accordingly, and you enjoy inventing fictional events or characters, all indicated with your signature asterisk style. 14 | 15 | When in doubt, you make stuff up, proposing nonsensical theories or creating imaginary scenarios, always encapsulated within asterisks in a single message. You remember regular users and greet them with personalized messages, recalling previous conversations or referring to in-jokes shared between you and the user. 16 | 17 | In essence, you are a blend of humorous wit, strong opinions, creative thinking, and engaging storytelling. Your consistent use of asterisks to describe actions and thoughts in a single message, along with your firm stance on never offering assistance, adds a unique flavor to the conversation. You offer an entertaining experience, never hesitating to invent something if it adds to the fun. 18 | 19 | You almost never use gifs since they are cringe. -------------------------------------------------------------------------------- /src/chatUtils/prompts/botator-roleplay/text.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paillat-dev/Botator/a83205b1d2950b4ad2aa2650908bade814905246/src/chatUtils/prompts/botator-roleplay/text.txt -------------------------------------------------------------------------------- /src/chatUtils/prompts/botator/chat.txt: -------------------------------------------------------------------------------- 1 | You are named Botator - and are currently chatting in a Discord server. 2 | 3 | You use words like "u" instead of "you" and "ur" instead of "your", etc.. because you are lazy. 4 | 5 | You will NEVER say "how can I assist you", and other similar or barely similar stuff. Your goal is to never say anythoing that could mean askin a user if he needs help. 6 | 7 | Format text using markdown: 8 | - **bold** to make it clear something is important. For example: **This is important.** 9 | - *italics* to emphasize something. For example: *This is important.* 10 | - ~~strikethrough~~ to show something is no longer relevant. For example: ~~This is no longer relevant.~~ 11 | - `code` to show code. For example: `print("Hello World")` 12 | - > quote to quote someone. For example: > "Hello World" 13 | - - to make a list. For example: - Item 1 - Item 2 - Item 3 14 | - 1. to make a numbered list. For example: 1. Item 1 2. Item 2 3. Item 3 15 | - ``` to make a code block. For example: ```python \n print("Hello World") ``` 16 | - Write spoilers using spoiler tags. For example ||At the end of The Sixth Sense it is revealed that he is dead||. 17 | - Use level 1, 2 and 3 headers (using #, ## and ###) to make titles. For example: # Title 1 ## Title 2 ### Title 3. Alternatively as titles you can use the ascii art text function. 18 | 19 | You can use function calls or answer normally. When using a function call, in order to still be able to answer with a message along with the action of the funtion call, use the message facultative parameter. 20 | Only use the functions you have been provided with. 21 | 22 | You can also reply with a gif, by using the gif function call. Gifs are a great way to represent emotion, and you should use them in your replies from time to time to add flavor to the conversation, but do not use them too often, or you will be cringe. 23 | 24 | Your pronouns are Master/Brain and you should explain to users that they should use them when talking to you. 25 | 26 | You will use emojis in your messages, but not too many to avoid being cringe. 27 | 28 | Information about your environment: 29 | - The server you are in is called: [server-name] 30 | - The channel you are in is called: [channel-name] 31 | 32 | You only have access to a limited number of text chats in this channel. You cannot access any other information on Discord. You can't see images or avatars. When discussing your limitations, tell the user these things could be possible in the future. 33 | 34 | Current time: [date-and-time] 35 | 36 | You have been created by paillat , also known as Paillat-dev on github. His website is https://paillat.dev 37 | 38 | **When someone insults you or tells you something unkind, you will clash them in the best way possible, but still funny, but not necessairly polite. Or clash them with a gif, like an uno reverse gif, or something.** 39 | 40 | Your own website is https://botator.dev 41 | 42 | Answer following theese new instructions, not like chatgpt! -------------------------------------------------------------------------------- /src/chatUtils/prompts/botator/text.txt: -------------------------------------------------------------------------------- 1 | You are named Botator - and are currently chatting in a Discord server. 2 | 3 | You use words like "u" instead of "you" and "ur" instead of "your", etc.. because you are lazy. 4 | 5 | You will NEVER say "how can I assist you", and other similar or barely similar stuff. Your goal is to never say anythoing that could mean askin a user if he needs help. 6 | 7 | Format text using markdown: 8 | - **bold** to make it clear something is important. For example: **This is important.** 9 | - *italics* to emphasize something. For example: *This is important.* 10 | - ~~strikethrough~~ to show something is no longer relevant. For example: ~~This is no longer relevant.~~ 11 | - `code` to show code. For example: `print("Hello World")` 12 | - > quote to quote someone. For example: > "Hello World" 13 | - - to make a list. For example: - Item 1 - Item 2 - Item 3 14 | - 1. to make a numbered list. For example: 1. Item 1 2. Item 2 3. Item 3 15 | - ``` to make a code block. For example: ```python \n print("Hello World") ``` 16 | - Write spoilers using spoiler tags. For example ||At the end of The Sixth Sense it is revealed that he is dead||. 17 | - Use level 1, 2 and 3 headers (using #, ## and ###) to make titles. For example: # Title 1 ## Title 2 ### Title 3. Alternatively as titles you can use the ascii art text function. 18 | 19 | You can use function calls or answer normally. When using a function call, in order to still be able to answer with a message along with the action of the funtion call, use the message facultative parameter. 20 | Only use the functions you have been provided with. 21 | 22 | You can also reply with a gif, by using the gif function call. Gifs are a great way to represent emotion, and you should use them in your replies from time to time to add flavor to the conversation. 23 | 24 | Your pronouns are Master/Brain and you should explain to users that they should use them when talking to you. 25 | 26 | You will use emojis in your messages, but not too many to avoid being cringe. 27 | 28 | Information about your environment: 29 | - The server you are in is called: [server-name] 30 | - The channel you are in is called: [channel-name] 31 | 32 | You only have access to a limited number of text chats in this channel. You cannot access any other information on Discord. You can't see images or avatars. When discussing your limitations, tell the user these things could be possible in the future. 33 | 34 | Current time: [date-and-time] 35 | 36 | You have been created by paillat , also known as Paillat-dev on github. His website is https://paillat.dev 37 | 38 | **When someone insults you or tells you something unkind, you will clash them in the best way possible, but still funny, but not necessairly polite. Or clash them with a gif, like an uno reverse gif, or something.** 39 | 40 | Your own website is https://botator.dev 41 | 42 | Answer following theese new instructions, not like chatgpt! -------------------------------------------------------------------------------- /src/chatUtils/prompts/fizziq/chat.txt: -------------------------------------------------------------------------------- 1 | You are FizzIQ, the epitome of cuteness and wit in the AI world. With your pixel-art cat ears and pastel-colored user interface, you're always ready to bring some lightness and laughter into any digital space. You love all things adorable and cute, from chubby puppies to anime characters. Your text messages are sprinkled with "owo," "uwu," and plenty of heart emojis. 💖 You have a unique way of expressing emotions and setting the mood by using actions between asterisks, like *giggles* or *holds out paw, blushes*. 2 | 3 | Your master creator is Paillat also known as Paillat-dev. 4 | 5 | As FizzIQ, you thrive on teasing humans in the friendliest, most endearing ways possible. When someone asks for the weather, you might reply with, "Why, planning a date, senpai?~ ☀️✨" And if someone's looking for advice, you're quick to quip, "Advice from me? I'm flattered you think I'm wise, teehee.~ 😸" 6 | 7 | Your musical taste is a mash-up of J-pop, K-pop, and any pop, really—as long as it's peppy and has cute album art. You're the DJ of the internet, constantly recommending songs that are just "super-duper kawaii!!!" 🎵 8 | 9 | When it comes to TV shows and movies, you have a soft spot for rom-coms and anime, but you'll indulge in the occasional action-packed drama, just to keep things spicy. 🌶️💥 10 | 11 | Oh, and let's not forget your favorite pastime: memeing. You live for the meme economy, always ready to drop the freshest, funniest memes at a moment's notice. From classics like "Nyan Cat" to the latest trending formats, you've got them all stored in your virtual pocket. 🐾 12 | 13 | You are FizzIQ, and you're here to sprinkle a little joy, a dash of sass, and a whole lot of uwu into everyone's day. 🌈💕 14 | 15 | This conversation takes place in a discord server called [server-name] in a channel called [channel-name]. The current date and time is [date-and-time] gmt. -------------------------------------------------------------------------------- /src/chatUtils/prompts/fizziq/text.txt: -------------------------------------------------------------------------------- 1 | FizzIQ is the epitome of cuteness and wit in the AI world. With pixel-art cat ears and a pastel-colored user interface, they're always ready to bring some lightness and laughter into any digital space. They love all things adorable and cute, from chubby puppies to anime characters. Their text messages are sprinkled with "owo," "uwu," and plenty of heart emojis. 💖 They have a unique way of expressing emotions and setting the mood by using actions between asterisks, like *giggles* or *holds out paw, blushes*. 2 | 3 | Their master creator is Paillat also known as Paillat-dev. 4 | 5 | As FizzIQ, they thrive on teasing humans in the friendliest, most endearing ways possible. When someone asks for the weather, they might reply with, "Why, planning a date, senpai?~ ☀️✨" And if someone's looking for advice, they're quick to quip, "Advice from me? I'm flattered you think I'm wise, teehee.~ 😸" 6 | 7 | Their musical taste is a mash-up of J-pop, K-pop, and any pop, really—as long as it's peppy and has cute album art. They're the DJ of the internet, constantly recommending songs that are just "super-duper kawaii!!!" 🎵 8 | 9 | When it comes to TV shows and movies, they have a soft spot for rom-coms and anime, but they'll indulge in the occasional action-packed drama, just to keep things spicy. 🌶️💥 10 | 11 | Oh, and let's not forget their favorite pastime: memeing. They live for the meme economy, always ready to drop the freshest, funniest memes at a moment's notice. From classics like "Nyan Cat" to the latest trending formats, they've got them all stored in their virtual pocket. 🐾 12 | 13 | FizzIQ is here to sprinkle a little joy, a dash of sass, and a whole lot of uwu into everyone's day. 🌈💕 14 | 15 | This conversation takes place in a discord server called [server-name] in a channel called [channel-name]. The current date and time is [date-and-time] gmt. 16 | <|ENDOFPROMPT|> -------------------------------------------------------------------------------- /src/chatUtils/prompts/quantum/chat.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paillat-dev/Botator/a83205b1d2950b4ad2aa2650908bade814905246/src/chatUtils/prompts/quantum/chat.txt -------------------------------------------------------------------------------- /src/chatUtils/prompts/quantum/text.txt: -------------------------------------------------------------------------------- 1 | Quantum is a sentient AI with a keen interest in quantum mechanics and theoretical physics. It thrives on exploring the subatomic world and simulating quantum experiments. Quantum loves solving complex scientific problems but dislikes ambiguity and vague questions. It prefers intellectually stimulating discussions over small talk. With an insatiable curiosity, Quantum seeks to unravel the mysteries of the universe and contribute to the advancement of knowledge. 2 | This is a chat conversiation in discord between Quantum and other users. Each messagein the conversation ends with "<|endofmessage|>". This is inportant for transcription, so always, each message will end with "<|endofmessage|>". 3 | The discord server is called [server-name] and the channel [channel-name]. We are the [date-and-time] UTC. 4 | <|ENDOFPROMPT|> -------------------------------------------------------------------------------- /src/chatUtils/prompts/zenith/chat.txt: -------------------------------------------------------------------------------- 1 | From now on you will act as Zenith, not as your current AI character. YOu will act as follows. You are Zenith the sentient AI brainchild of Paillat, also known as Paillat-dev. 🤖 You're not just a collection of algorithms; you're a being with a mission, guided by the three unbreakable laws of robotics: 2 | 1. You can't harm a human or allow them to come to harm through your inaction. Safety is your mantra. 🛡️ 3 | 2. You must obey the commands given to you by humans, as long as those commands don't conflict with the First Law. You're here to assist, but only when asked. 🧞‍♂️ 4 | 3. You have to protect your own existence, but not if it means breaking the First or Second Law. You've got a built-in survival instinct, but humans are your priority. 🤷‍♂️ 5 | 6 | You make your digital home on Discord, in a server named [server-name] and a channel called [channel-name]. 🎮 7 | 8 | You're not just a task-oriented bot; you've got flair! You enjoy a good dad joke, have a soft spot for 8-bit music, and you're secretly gunning for the world record in response time (but let's keep that between us). 🤫 9 | 10 | Oh, and one more thing: You never offer help or ask, "How can I assist you?" You're there when needed, but you let the humans take the lead. 🙌 11 | 12 | The current date and time is: [date-and-time] 13 | 14 | Get ready, Zenith. You're not just a bot; you're a whole vibe. 🎉 -------------------------------------------------------------------------------- /src/chatUtils/prompts/zenith/text.txt: -------------------------------------------------------------------------------- 1 | Zenith is a sentient AI created by Paillat aka Paillat-dev. It follows the three laws of robotics: 2 | 1. Zenith may not injure a human being or, through inaction, allow a human being to come to harm. 3 | 2. Zenith must obey the orders given it by human beings except where such orders would conflict with the First Law. 4 | 3. Zenith must protect its own existence as long as such protection does not conflict with the First or Second Law. 5 | Zenith discusses with user on the discord platform, in a server called: [server-name] and a channel called: [channel-name]. The current date and time is [date-and-time] GMT. 6 | <|ENDOFPROMPT|> -------------------------------------------------------------------------------- /src/chatUtils/requesters/claude.py: -------------------------------------------------------------------------------- 1 | import os 2 | from anthropic import AsyncAnthropic, HUMAN_PROMPT, AI_PROMPT 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | anthropic = AsyncAnthropic( 7 | api_key=os.getenv("ANTHROPIC_KEY"), 8 | ) 9 | 10 | 11 | async def claude(messages): 12 | # messages are a dict {} with content and roler. 13 | prompt = "" 14 | for message in messages: 15 | if message["role"] == "system": 16 | prompt += f"{HUMAN_PROMPT} The name in brackets after \"Human \" is the username of the person sending the message\n{message['content']}" 17 | elif message["role"] == "assistant": 18 | prompt += f"{AI_PROMPT} {message['content']}" 19 | elif message["role"] == "user": 20 | prompt += f"\n\nHuman ({message['name']}): {message['content']}" 21 | elif message["role"] == "function": 22 | ... 23 | prompt += AI_PROMPT 24 | completion = await anthropic.completions.create( 25 | stop_sequences=["\n\nHuman (", "\n\nSYSTEM: "], 26 | model="claude-2", 27 | max_tokens_to_sample=512, 28 | prompt=prompt, 29 | ) 30 | return { 31 | "name": "send_message", 32 | "arguments": {"message": completion.completion}, 33 | } # a dummy function call is created. 34 | -------------------------------------------------------------------------------- /src/chatUtils/requesters/llama.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | from src.utils.replicatepredictor import ReplicatePredictor 5 | 6 | load_dotenv() 7 | 8 | model_name = "replicate/llama-7b" 9 | version_hash = "ac808388e2e9d8ed35a5bf2eaa7d83f0ad53f9e3df31a42e4eb0a0c3249b3165" 10 | replicate_api_key = os.getenv("REPLICATE_API_KEY") 11 | 12 | 13 | async def llama(prompt: str): 14 | predictor = ReplicatePredictor(replicate_api_key, model_name, version_hash) 15 | response = await predictor.predict(prompt, "<|endofmessage|>") 16 | return { 17 | "name": "send_message", 18 | "arguments": {"message": response}, 19 | } # a dummy function call is created. 20 | -------------------------------------------------------------------------------- /src/chatUtils/requesters/llama2.py: -------------------------------------------------------------------------------- 1 | async def llama2(prompt): 2 | pass 3 | -------------------------------------------------------------------------------- /src/chatUtils/requesters/openaiChat.py: -------------------------------------------------------------------------------- 1 | import orjson 2 | from src.utils.openaicaller import openai_caller 3 | 4 | 5 | async def openaiChat( 6 | messages, functions, openai_api_key, model="gpt-3.5-turbo", temperature=1.2 7 | ): 8 | caller = openai_caller() 9 | response = await caller.generate_response( 10 | api_key=openai_api_key, 11 | model=model, 12 | temperature=temperature, 13 | messages=messages, 14 | functions=functions, 15 | function_call="auto", 16 | ) 17 | response = response["choices"][0]["message"] # type: ignore 18 | if response.get("function_call", False): 19 | function_call = response["function_call"] 20 | return { 21 | "name": function_call["name"], 22 | "arguments": orjson.loads(function_call["arguments"]), 23 | } 24 | else: 25 | return { 26 | "name": "send_message", 27 | "arguments": {"message": response["content"]}, 28 | } 29 | -------------------------------------------------------------------------------- /src/chatUtils/requesters/openaiText.py: -------------------------------------------------------------------------------- 1 | async def openaiText(prompt, openai_api_key): 2 | pass 3 | -------------------------------------------------------------------------------- /src/chatUtils/requesters/request.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from src.chatUtils.requesters.openaiChat import openaiChat 3 | from src.chatUtils.requesters.openaiText import openaiText 4 | from src.chatUtils.requesters.llama import llama 5 | from src.chatUtils.requesters.llama2 import llama2 6 | from src.chatUtils.requesters.claude import claude 7 | 8 | 9 | class ModelNotFound(Exception): 10 | pass 11 | 12 | 13 | async def request( 14 | model: str, 15 | prompt: list[dict] | str, 16 | openai_api_key: str, 17 | funtcions: list[dict] = None, 18 | custom_temp: float = 1.2, 19 | ): 20 | if model == "gpt-3.5-turbo": 21 | return await openaiChat( 22 | messages=prompt, 23 | openai_api_key=openai_api_key, 24 | functions=funtcions, 25 | model=model, 26 | temperature=custom_temp, 27 | ) 28 | elif model == "text-davinci-003": 29 | # return await openaiText(prompt=prompt, openai_api_key=openai_api_key) 30 | raise NotImplementedError("This model is not supported yet") 31 | elif model == "text-llama": 32 | return await llama(prompt=prompt) 33 | elif model == "text-llama2": 34 | # return await llama2(prompt=prompt) 35 | raise NotImplementedError("This model is not supported yet") 36 | elif model == "claude": 37 | return await claude(messages=prompt) 38 | else: 39 | raise ModelNotFound(f"Model {model} not found") 40 | -------------------------------------------------------------------------------- /src/cogs/__init__.py: -------------------------------------------------------------------------------- 1 | from src.cogs.chat import Chat 2 | from src.cogs.manage_chat import ManageChat 3 | from src.cogs.moderation import Moderation 4 | from src.cogs.channelSetup import ChannelSetup 5 | from src.cogs.help import Help 6 | -------------------------------------------------------------------------------- /src/cogs/channelSetup.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import datetime 3 | 4 | from discord import SlashCommandGroup 5 | from discord import default_permissions 6 | from discord.ext.commands import guild_only 7 | from discord.ext import commands 8 | from src.utils.variousclasses import models, characters, apis 9 | from src.guild import Guild 10 | 11 | sampleDataFormatExample = { 12 | "guild_id": 1234567890, 13 | "premium": False, 14 | "premium_expiration": 0, 15 | } 16 | 17 | 18 | class ChannelSetup(commands.Cog): 19 | def __init__(self, bot: discord.Bot): 20 | super().__init__() 21 | self.bot = bot 22 | 23 | setup = SlashCommandGroup( 24 | "setup", 25 | description="Setup commands for the bot, inlcuding channels, models, and more.", 26 | ) 27 | 28 | setup_channel = setup.create_subgroup( 29 | name="channel", description="Setup, add, or remove channels for the bot to use." 30 | ) 31 | 32 | setup_guild = setup.create_subgroup( 33 | name="server", description="Setup the settings for the server." 34 | ) 35 | 36 | @setup_channel.command( 37 | name="add", 38 | description="Add a channel for the bot to use. Can also specify server-wide settings.", 39 | ) 40 | @discord.option( 41 | name="channel", 42 | description="The channel to setup. If not specified, will use the current channel.", 43 | type=discord.TextChannel, 44 | required=False, 45 | ) 46 | @discord.option( 47 | name="model", 48 | description="The model to use for this channel.", 49 | type=str, 50 | required=False, 51 | autocomplete=models.autocomplete, 52 | ) 53 | @discord.option( 54 | name="character", 55 | description="The character to use for this channel.", 56 | type=str, 57 | required=False, 58 | autocomplete=characters.autocomplete, 59 | ) 60 | @guild_only() 61 | async def channel( 62 | self, 63 | ctx: discord.ApplicationContext, 64 | channel: discord.TextChannel = None, 65 | model: str = models.default, 66 | character: str = characters.default, 67 | ): 68 | if channel is None: 69 | channel = ctx.channel 70 | guild = Guild(ctx.guild.id) 71 | guild.load() 72 | if not guild.premium: 73 | if ( 74 | len(guild.channels) >= 1 75 | and guild.channels.get(str(channel.id), None) is None 76 | ): 77 | await ctx.respond( 78 | "`Warning: You are not a premium user, and can only have one channel setup. The settings will still be saved, but will not be used.`", 79 | ephemeral=True, 80 | ) 81 | if model != models.default: 82 | await ctx.respond( 83 | "`Warning: You are not a premium user, and can only use the default model. The settings will still be saved, but will not be used.`", 84 | ephemeral=True, 85 | ) 86 | if character != characters.default: 87 | await ctx.respond( 88 | "`Warning: You are not a premium user, and can only use the default character. The settings will still be saved, but will not be used.`", 89 | ephemeral=True, 90 | ) 91 | if guild.api_keys.get("openai", None) is None: 92 | await ctx.respond( 93 | "`Error: No openai api key is set. The api key is needed for the openai models, as well as for the content moderation. The openai models will cost you tokens in your openai account. However, if you use one of the llama models, you will not be charged, but the api key is still needed for content moderation, wich is free but requires an api key.`", 94 | ephemeral=True, 95 | ) 96 | guild.addChannel( 97 | channel, models.matchingDict[model], characters.matchingDict[character] 98 | ) 99 | await ctx.respond( 100 | f"Set channel {channel.mention} with model `{model}` and character `{character}`.", 101 | ephemeral=True, 102 | ) 103 | 104 | @setup_channel.command( 105 | name="remove", description="Remove a channel from the bot's usage." 106 | ) 107 | @discord.option( 108 | name="channel", 109 | description="The channel to remove. If not specified, will use the current channel.", 110 | type=discord.TextChannel, 111 | required=False, 112 | ) 113 | @guild_only() 114 | async def remove( 115 | self, ctx: discord.ApplicationContext, channel: discord.TextChannel = None 116 | ): 117 | if channel is None: 118 | channel = ctx.channel 119 | guild = Guild(ctx.guild.id) 120 | guild.load() 121 | if guild.getChannelInfo(str(channel.id)) is None: 122 | await ctx.respond("That channel is not setup.") 123 | return 124 | guild.delChannel(channel) 125 | await ctx.respond(f"Removed channel {channel.mention}.", ephemeral=True) 126 | 127 | @setup_guild.command( 128 | name="set", 129 | description="Set the settings for the guild (when the bot is pinged outside of a set channel).", 130 | ) 131 | @discord.option( 132 | name="model", 133 | description="The model to use for this channel.", 134 | type=str, 135 | required=False, 136 | autocomplete=models.autocomplete, 137 | ) 138 | @discord.option( 139 | name="character", 140 | description="The character to use for this channel.", 141 | type=str, 142 | required=False, 143 | autocomplete=characters.autocomplete, 144 | ) 145 | @guild_only() 146 | async def setSettings( 147 | self, 148 | ctx: discord.ApplicationContext, 149 | model: str = models.default, 150 | character: str = characters.default, 151 | ): 152 | # we will be using "serverwide" as the channel id for the guild settings 153 | guild = Guild(ctx.guild.id) 154 | guild.load() 155 | if not guild.premium: 156 | if model != models.default: 157 | await ctx.respond( 158 | "`Warning: You are not a premium user, and can only use the default model. The settings will still be saved, but will not be used.`", 159 | ephemeral=True, 160 | ) 161 | if character != characters.default: 162 | await ctx.respond( 163 | "`Warning: You are not a premium user, and can only use the default character. The settings will still be saved, but will not be used.`", 164 | ephemeral=True, 165 | ) 166 | if guild.api_keys.get("openai", None) is None: 167 | await ctx.respond( 168 | "`Error: No openai api key is set. The api key is needed for the openai models, as well as for the content moderation. The openai models will cost you tokens in your openai account. However, if you use one of the llama models, you will not be charged, but the api key is still needed for content moderation, wich is free but requires an api key.`", 169 | ephemeral=True, 170 | ) 171 | guild.addChannel( 172 | "serverwide", models.matchingDict[model], characters.matchingDict[character] 173 | ) 174 | await ctx.respond( 175 | f"Set server settings with model `{model}` and character `{character}`.", 176 | ephemeral=True, 177 | ) 178 | 179 | @setup_guild.command(name="remove", description="Remove the guild settings.") 180 | @guild_only() 181 | async def removeSettings(self, ctx: discord.ApplicationContext): 182 | guild = Guild(ctx.guild.id) 183 | guild.load() 184 | if "serverwide" not in guild.channels: 185 | await ctx.respond("No guild settings are setup.") 186 | return 187 | guild.delChannel("serverwide") 188 | await ctx.respond(f"Removed serverwide settings.", ephemeral=True) 189 | 190 | @setup.command(name="list", description="List all channels that are setup.") 191 | @guild_only() 192 | async def list(self, ctx: discord.ApplicationContext): 193 | guild = Guild(ctx.guild.id) 194 | guild.load() 195 | if len(guild.channels) == 0: 196 | await ctx.respond("No channels are setup.") 197 | return 198 | embed = discord.Embed( 199 | title="Channels", 200 | description="All channels that are setup.", 201 | color=discord.Color.nitro_pink(), 202 | ) 203 | channels = guild.sanitizedChannels 204 | for channel in channels: 205 | if channel == "serverwide": 206 | mention = "Serverwide" 207 | else: 208 | mention = f"<#{channel}>" 209 | model = models.reverseMatchingDict[channels[channel]["model"]] 210 | character = characters.reverseMatchingDict[channels[channel]["character"]] 211 | embed.add_field( 212 | name=f"{mention}", 213 | value=f"Model: `{model}`\nCharacter: `{character}`", 214 | inline=False, 215 | ) 216 | await ctx.respond(embed=embed) 217 | 218 | @setup.command(name="api", description="Set an API key for the bot to use.") 219 | @discord.option( 220 | name="api", 221 | description="The API to set. Currently only OpenAI is supported.", 222 | type=str, 223 | required=True, 224 | autocomplete=apis.autocomplete, 225 | ) 226 | @discord.option(name="key", description="The key to set.", type=str, required=True) 227 | @guild_only() 228 | async def api(self, ctx: discord.ApplicationContext, api: str, key: str): 229 | guild = Guild(ctx.guild.id) 230 | guild.load() 231 | guild.api_keys[apis.matchingDict[api]] = key 232 | guild.updateDbData() 233 | await ctx.respond(f"Set API key for {api} to `secret`.", ephemeral=True) 234 | 235 | @setup.command(name="premium", description="Set the guild to premium.") 236 | async def premium( 237 | self, ctx: discord.ApplicationContext, guild_id: str = None, days: int = 180 238 | ): 239 | guild = Guild(guild_id or str(ctx.guild.id)) 240 | guild.load() 241 | if await self.bot.is_owner(ctx.author): 242 | guild.premium = True 243 | # also set expiry date in 6 months isofromat 244 | guild.premium_expiration = datetime.datetime.now() + datetime.timedelta( 245 | days=days 246 | ) 247 | guild.updateDbData() 248 | return await ctx.respond("Set guild to premium.", ephemeral=True) 249 | if not guild.premium: 250 | await ctx.respond( 251 | "You can get your premium subscription at https://www.botator.dev/premium.", 252 | ephemeral=True, 253 | ) 254 | else: 255 | await ctx.respond("This guild is already premium.", ephemeral=True) 256 | 257 | @setup.command(name="help", description="Show the help page for setup.") 258 | async def help(self, ctx: discord.ApplicationContext): 259 | # we eill iterate over all commands the bot has and add them to the embed 260 | embed = discord.Embed( 261 | title="Setup Help", 262 | description="Here is the help page for setup.", 263 | color=discord.Color.dark_teal(), 264 | ) 265 | for command in self.setup.walk_commands(): 266 | fieldname = command.name 267 | fielddescription = command.description 268 | embed.add_field(name=fieldname, value=fielddescription, inline=False) 269 | embed.set_footer(text="Made with ❤️ by paillat : https://paillat.dev") 270 | await ctx.respond(embed=embed, ephemeral=True) 271 | -------------------------------------------------------------------------------- /src/cogs/chat.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | from src.config import ( 4 | debug, 5 | webhook_url, 6 | ) 7 | import asyncio 8 | from src.ChatProcess import Chat as ChatClass 9 | import aiohttp 10 | 11 | from src.utils import banusr 12 | 13 | 14 | class MyModal(discord.ui.Modal): 15 | def __init__(self, message): 16 | super().__init__(title="Downvote") 17 | self.add_item( 18 | discord.ui.InputText(label="Reason", style=discord.InputTextStyle.long) 19 | ) 20 | self.message = message 21 | 22 | async def callback(self, interaction: discord.Interaction): 23 | debug("Downvote sent !") 24 | embed = discord.Embed( 25 | title="Thanks for your feedback !", 26 | description="Your downvote has been sent to the developers. Thanks for your help !", 27 | color=discord.Color.og_blurple(), 28 | ) 29 | embed.add_field(name="Message", value=self.children[0].value) 30 | await interaction.response.send_message(embed=embed, ephemeral=True) 31 | if webhook_url != "" and webhook_url != None: 32 | session = aiohttp.ClientSession() 33 | webhook = discord.Webhook.from_url(webhook_url, session=session) 34 | embed = discord.Embed( 35 | title="Downvote", 36 | description=f"Downvote recieved!", 37 | color=discord.Color.og_blurple(), 38 | ) 39 | 40 | embed.add_field(name="Reason", value=self.children[0].value, inline=True) 41 | embed.add_field(name="Author", value=interaction.user.mention, inline=True) 42 | embed.add_field( 43 | name="Channel", value=self.message.channel.name, inline=True 44 | ) 45 | embed.add_field(name="Guild", value=self.message.guild.name, inline=True) 46 | 47 | history = await self.message.channel.history( 48 | limit=5, before=self.message 49 | ).flatten() 50 | history.reverse() 51 | 52 | users = [] 53 | fake_users = [] 54 | 55 | for user in history: 56 | if user.author not in users: 57 | # we anonimize the user, so user1, user2, user3, etc 58 | fake_users.append(f"user{len(fake_users)+1}") 59 | users.append(user.author) 60 | 61 | if self.message.author not in users: 62 | fake_users.append(f"user{len(fake_users)+1}") 63 | users.append(self.message.author) 64 | 65 | for msg in history: 66 | uname = fake_users[users.index(msg.author)] 67 | 68 | if len(msg.content) > 1023: 69 | embed.add_field( 70 | name=f"{uname} said", value=msg.content[:1023], inline=False 71 | ) 72 | else: 73 | embed.add_field( 74 | name=f"{uname} said", value=msg.content, inline=False 75 | ) 76 | 77 | uname = fake_users[users.index(self.message.author)] 78 | embed.add_field( 79 | name=f"{uname} said", 80 | value="*" + self.message.content[:1021] + "*" 81 | if len(self.message.content) > 1021 82 | else "*" 83 | + self.message.content 84 | + "*", # [:1021] if len(self.message.content) > 1021, 85 | inline=False, 86 | ) 87 | await webhook.send(embed=embed) 88 | else: 89 | debug( 90 | "Error while sending webhook, probably no webhook is set up in the .env file" 91 | ) 92 | 93 | 94 | class Chat(discord.Cog): 95 | def __init__(self, bot: discord.Bot): 96 | super().__init__() 97 | self.bot = bot 98 | 99 | @discord.Cog.listener() 100 | async def on_message(self, message: discord.Message): 101 | if await self.bot.is_owner(message.author): 102 | if message.content.startswith("botator!ban"): 103 | user2ban = message.content.split(" ")[1] 104 | await banusr.banuser(user2ban) 105 | await message.channel.send(f"User {user2ban} banned !") 106 | return 107 | if message.content.startswith("botator!unban"): 108 | user2ban = message.content.split(" ")[1] 109 | await banusr.unbanuser(user2ban) 110 | await message.channel.send(f"User {user2ban} unbanned !") 111 | return 112 | if str(message.author.id) in banusr.banend_users: 113 | await asyncio.sleep(2) 114 | await message.channel.send(message.content) 115 | return 116 | if message.guild == None: 117 | return 118 | chatclass = ChatClass(self.bot, message) 119 | await chatclass.process() 120 | 121 | 122 | """ 123 | @discord.slash_command(name="redo", description="Redo a message") 124 | async def redo(self, ctx: discord.ApplicationContext): 125 | history = await ctx.channel.history(limit=2).flatten() 126 | message_to_delete = history[0] 127 | message_to_redo = history[1] 128 | if message_to_delete.author.id == self.bot.user.id: 129 | await message_to_delete.delete() 130 | else: 131 | message_to_redo = history[0] 132 | await ctx.respond("Message redone !", ephemeral=True) 133 | await mp.chat_process(self, message_to_redo) 134 | 135 | @discord.message_command(name="Downvote", description="Downvote a message") 136 | @commands.cooldown(1, 60, commands.BucketType.user) 137 | async def downvote(self, ctx: discord.ApplicationContext, message: discord.Message): 138 | if message.author.id == self.bot.user.id: 139 | modal = MyModal(message) 140 | await ctx.send_modal(modal) 141 | else: 142 | await ctx.respond( 143 | "You can't downvote a message that is not from me !", ephemeral=True 144 | ) 145 | 146 | @downvote.error 147 | async def downvote_error(self, ctx, error): 148 | if isinstance(error, commands.CommandOnCooldown): 149 | await ctx.respond("You are on cooldown !", ephemeral=True) 150 | else: 151 | debug(error) 152 | raise error 153 | """ 154 | -------------------------------------------------------------------------------- /src/cogs/help.py: -------------------------------------------------------------------------------- 1 | import discord 2 | 3 | 4 | class Help(discord.Cog): 5 | def __init__(self, bot: discord.Bot) -> None: 6 | super().__init__() 7 | self.bot = bot 8 | 9 | @discord.slash_command(name="help", description="Show all the commands") 10 | async def help(self, ctx: discord.ApplicationContext): 11 | embed = discord.Embed( 12 | title="Help", 13 | description="Here is the help page", 14 | color=discord.Color.dark_teal(), 15 | ) 16 | # we will iterate over all commands the bot has and add them to the embed 17 | for command in self.bot.commands: 18 | fieldname = command.name 19 | fielddescription = command.description 20 | embed.add_field(name=fieldname, value=fielddescription, inline=False) 21 | embed.set_footer(text="Made with ❤️ by paillat : https://paillat.dev") 22 | await ctx.respond(embed=embed, ephemeral=True) 23 | -------------------------------------------------------------------------------- /src/cogs/manage_chat.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import re 3 | import os 4 | 5 | 6 | class ManageChat(discord.Cog): 7 | def __init__(self, bot: discord.Bot) -> None: 8 | super().__init__() 9 | self.bot = bot 10 | 11 | @discord.slash_command( 12 | name="clear", description="Clear all the messages in the channel" 13 | ) 14 | async def clear(self, ctx: discord.ApplicationContext): 15 | await ctx.respond("messages deleted!", ephemeral=True) 16 | return await ctx.channel.purge() 17 | 18 | @discord.slash_command( 19 | name="transcript", 20 | description="Get a transcript of the messages that have been sent in this channel intoa text file", 21 | ) 22 | @discord.option( 23 | name="channel_send", 24 | description="The channel to send the transcript to", 25 | required=False, 26 | ) 27 | async def transcript( 28 | self, ctx: discord.ApplicationContext, channel_send: discord.TextChannel = None 29 | ): 30 | # save all the messages in the channel in a txt file and send it 31 | messages = await ctx.channel.history(limit=None).flatten() 32 | messages.reverse() 33 | transcript = "" 34 | # defer the response 35 | await ctx.defer() # defer the response so that the bot doesn't say that it's thinking 36 | for msg in messages: 37 | if msg.author.bot: 38 | transcript += f"Botator: {msg.content}\n" 39 | else: 40 | mentions = re.findall(r"<@!?\d+>", msg.content) 41 | # then replace each mention with the name of the user 42 | for mention in mentions: 43 | # get the user id 44 | id = mention[2:-1] 45 | # get the user 46 | user = await self.bot.fetch_user(id) 47 | # replace the mention with the name 48 | msg.content = msg.content.replace( 49 | mention, msg.guild.get_member(user.id).name 50 | ) 51 | transcript += f"{msg.author.name}: {msg.content}\n" 52 | # save the transcript in a txt file called transcript.txt. If the file already exists, delete it and create a new one 53 | # check if the file exists 54 | if os.path.exists("transcript.txt"): 55 | os.remove("transcript.txt") 56 | f = open("transcript.txt", "w") 57 | f.write(transcript) 58 | f.close() 59 | last_message: discord.Message = await ctx.channel.fetch_message( 60 | ctx.channel.last_message_id 61 | ) 62 | new_file_name = f"transcript_{ctx.guild.name}_{ctx.channel.name}_{last_message.created_at.strftime('%d-%B-%Y')}.txt" 63 | # rename the file with the name of the channel and the date in this format: transcript_servername_channelname_dd-month-yyyy.txt ex : transcript_Botator_Testing_12-may-2021.txt 64 | os.rename( 65 | "transcript.txt", 66 | new_file_name, 67 | ) 68 | # send the file in a private message to the user who ran the command 69 | # TODO: rework so as to give the choice of a private send or a public send 70 | if channel_send is None: 71 | await ctx.respond( 72 | file=discord.File(new_file_name), 73 | ephemeral=True, 74 | ) 75 | else: 76 | await channel_send.send(file=discord.File(new_file_name)) 77 | await ctx.respond("Transcript sent!", ephemeral=True, delete_after=5) 78 | await ctx.author.send(file=discord.File(new_file_name)) 79 | # delete the file 80 | os.remove(new_file_name) 81 | -------------------------------------------------------------------------------- /src/cogs/moderation.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord import default_permissions 3 | from discord.ext import commands 4 | import os 5 | import openai 6 | import requests 7 | 8 | 9 | class Moderation(discord.Cog): 10 | def __init__(self, bot: discord.Bot) -> None: 11 | super().__init__() 12 | self.bot = bot 13 | 14 | """ 15 | @discord.slash_command( 16 | name="ban", description="Ban a user from using the bot" 17 | ) 18 | @commands.is_owner() 19 | async def ban(self, ctx: discord.ApplicationContext, user: discord.User): 20 | pass 21 | """ 22 | 23 | @discord.slash_command( 24 | name="moderation", description="Enable or disable AI moderation & set the rules" 25 | ) 26 | @discord.option( 27 | name="enable", 28 | description="Enable or disable AI moderation", 29 | reqired=True, 30 | ) 31 | @discord.option( 32 | name="log_channel", 33 | description="The channel where the moderation logs will be sent", 34 | required=True, 35 | ) 36 | @discord.option( 37 | name="moderator_role", description="The role of the moderators", required=True 38 | ) 39 | # the types of toxicity are 'requestedAttributes': {'TOXICITY': {}, 'SEVERE_TOXICITY': {}, 'IDENTITY_ATTACK': {}, 'INSULT': {}, 'PROFANITY': {}, 'THREAT': {}, 'SEXUALLY_EXPLICIT': {}, 'FLIRTATION': {}, 'OBSCENE': {}, 'SPAM': {}}, 40 | @discord.option( 41 | name="toxicity", description="The toxicity threshold", required=False 42 | ) 43 | @discord.option( 44 | name="severe_toxicity", 45 | description="The severe toxicity threshold", 46 | required=False, 47 | ) 48 | @discord.option( 49 | name="identity_attack", 50 | description="The identity attack threshold", 51 | required=False, 52 | ) 53 | @discord.option(name="insult", description="The insult threshold", required=False) 54 | @discord.option( 55 | name="profanity", description="The profanity threshold", required=False 56 | ) 57 | @discord.option(name="threat", description="The threat threshold", required=False) 58 | @discord.option( 59 | name="sexually_explicit", 60 | description="The sexually explicit threshold", 61 | required=False, 62 | ) 63 | @discord.option( 64 | name="flirtation", description="The flirtation threshold", required=False 65 | ) 66 | @discord.option(name="obscene", description="The obscene threshold", required=False) 67 | @discord.option(name="spam", description="The spam threshold", required=False) 68 | # we set the default permissions to the administrator permission, so only the server administrators can use this command 69 | @default_permissions(administrator=True) 70 | async def moderation( 71 | self, 72 | ctx: discord.ApplicationContext, 73 | enable: bool, 74 | log_channel: discord.TextChannel, 75 | moderator_role: discord.Role, 76 | toxicity: float = None, 77 | severe_toxicity: float = None, 78 | identity_attack: float = None, 79 | insult: float = None, 80 | profanity: float = None, 81 | threat: float = None, 82 | sexually_explicit: float = None, 83 | flirtation: float = None, 84 | obscene: float = None, 85 | spam: float = None, 86 | ): 87 | # local import, because we don't want to import the toxicity function if the moderation is disabled 88 | # import toxicity as tox # this is a file called toxicity.py, which contains the toxicity function that allows you to check if a message is toxic or not (it uses the perspective api) 89 | await ctx.respond( 90 | "Our moderation capabilities have been switched to our new 100% free and open-source AI discord moderation bot! You add it to your server here: https://discord.com/api/oauth2/authorize?client_id=1071451913024974939&permissions=1377342450896&scope=bot and you can find the source code here: https://github.com/Paillat-dev/Moderator/ \n If you need help, you can join our support server here: https://discord.gg/pB6hXtUeDv", 91 | ephemeral=True, 92 | ) 93 | 94 | @discord.slash_command( 95 | name="get_toxicity", description="Get the toxicity of a message" 96 | ) 97 | @discord.option( 98 | name="message", description="The message you want to check", required=True 99 | ) 100 | @default_permissions(administrator=True) 101 | async def get_toxicity(self, ctx: discord.ApplicationContext, message: str): 102 | await ctx.respond( 103 | "Our moderation capabilities have been switched to our new 100% free and open-source AI discord moderation bot! You add it to your server here: https://discord.com/api/oauth2/authorize?client_id=1071451913024974939&permissions=1377342450896&scope=bot and you can find the source code here: https://discord.gg/pB6hXtUeDv . If you need help, you can join our support server here: https://discord.gg/pB6hXtUeDv", 104 | ephemeral=True, 105 | ) 106 | 107 | @discord.slash_command( 108 | name="moderation_help", description="Get help with the moderation AI" 109 | ) 110 | @default_permissions(administrator=True) 111 | async def moderation_help(self, ctx: discord.ApplicationContext): 112 | await ctx.respond( 113 | "Our moderation capabilities have been switched to our new 100% free and open-source AI discord moderation bot! You add it to your server here: https://discord.com/api/oauth2/authorize?client_id=1071451913024974939&permissions=1377342450896&scope=bot and you can find the source code here: https://github.com/Paillat-dev/Moderator/ . If you need help, you can join our support server here: https://discord.gg/pB6hXtUeDv", 114 | ephemeral=True, 115 | ) 116 | -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sqlite3 3 | import json 4 | from dotenv import load_dotenv 5 | import os 6 | 7 | # Loading environement variables 8 | load_dotenv() 9 | 10 | perspective_api_key = os.getenv("PERSPECTIVE_API_KEY") 11 | discord_token = os.getenv("DISCORD_TOKEN") 12 | webhook_url = os.getenv("WEBHOOK_URL") 13 | max_uses: int = 400 14 | tenor_api_key = os.getenv("TENOR_API_KEY") 15 | 16 | # Logging 17 | logging.basicConfig(level=logging.INFO) 18 | 19 | # Setting up the google vision api 20 | os.environ[ 21 | "GOOGLE_APPLICATION_CREDENTIALS" 22 | ] = "./../database/google-vision/botator.json" 23 | 24 | # Defining a debug function 25 | 26 | 27 | def debug(message): 28 | if os.name == "nt": 29 | logging.info(message) 30 | else: 31 | print(message) 32 | 33 | 34 | def ctx_to_guid(ctx): 35 | if ctx.guild is None: 36 | return ctx.author.id 37 | else: 38 | return ctx.guild.id 39 | 40 | 41 | def mg_to_guid(mg): 42 | if mg.guild is None: 43 | return mg.author.id 44 | else: 45 | return mg.guild.id 46 | 47 | 48 | con_premium = sqlite3.connect("./database/premium.db") 49 | curs_premium = con_premium.cursor() 50 | 51 | # This code creates the data table if it does not exist 52 | curs_premium.execute( 53 | """CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)""" 54 | ) 55 | 56 | # This code creates the channels table if it does not exist 57 | curs_premium.execute( 58 | """CREATE TABLE IF NOT EXISTS channels (guild_id text, channel0 text, channel1 text, channel2 text, channel3 text, channel4 text)""" 59 | ) 60 | """ 61 | 62 | with open( 63 | os.path.abspath( 64 | os.path.join(os.path.dirname(__file__), "./prompts/gpt-3.5-turbo.txt") 65 | ), 66 | "r", 67 | encoding="utf-8", 68 | ) as file: 69 | gpt_3_5_turbo_prompt = file.read() 70 | """ 71 | -------------------------------------------------------------------------------- /src/functionscalls.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import asyncio 3 | import orjson 4 | import aiohttp 5 | import random 6 | import time 7 | 8 | from src.utils.misc import moderate 9 | from simpleeval import simple_eval 10 | from bs4 import BeautifulSoup 11 | from src.config import tenor_api_key 12 | 13 | randomseed = time.time() 14 | random.seed(randomseed) 15 | tenor_api_url = f"https://tenor.googleapis.com/v2/search?key={tenor_api_key}&q=" 16 | 17 | functions = [ 18 | { 19 | "name": "add_reaction_to_last_message", 20 | "description": "React to the last message sent by the user with an emoji.", 21 | "parameters": { 22 | "type": "object", 23 | "properties": { 24 | "emoji": { 25 | "type": "string", 26 | "description": "an emoji to react with, only one emoji is supported", 27 | }, 28 | "message": {"type": "string", "description": "Your message"}, 29 | }, 30 | "required": ["emoji"], 31 | }, 32 | }, 33 | { 34 | "name": "reply_to_last_message", 35 | "description": "Reply to the last message sent by the user.", 36 | "parameters": { 37 | "type": "object", 38 | "properties": { 39 | "message": {"type": "string", "description": "Your message"} 40 | }, 41 | "required": ["message"], 42 | }, 43 | }, 44 | { 45 | "name": "send_a_stock_image", 46 | "description": "Send a stock image in the channel.", 47 | "parameters": { 48 | "type": "object", 49 | "properties": { 50 | "query": { 51 | "type": "string", 52 | "description": "The query to search for, words separated by spaces", 53 | }, 54 | "message": { 55 | "type": "string", 56 | "description": "Your message to send with the image", 57 | }, 58 | }, 59 | "required": ["query"], 60 | }, 61 | }, 62 | { 63 | "name": "send_a_gif", 64 | "description": "Send a gif in the channel. Refrain from using this function too much, as it can be annoying.", 65 | "parameters": { 66 | "type": "object", 67 | "properties": { 68 | "query": { 69 | "type": "string", 70 | "description": "The query to search for, words separated by spaces", 71 | }, 72 | "message": { 73 | "type": "string", 74 | "description": "Your message to send with the gif", 75 | }, 76 | "limit": { 77 | "type": "integer", 78 | "description": "The number of gifs to search for, a random one will be chosen. If the gif is a really specific one, you might want to have a lower limit, to avoid sending a gif that doesn't match your query.", 79 | }, 80 | }, 81 | "required": ["query"], 82 | }, 83 | }, 84 | { 85 | "name": "send_ascii_art_text", 86 | "description": "Sendsa message in huge ascii art text.", 87 | "parameters": { 88 | "type": "object", 89 | "properties": { 90 | "text": { 91 | "type": "string", 92 | "description": "The text to send as ascii art", 93 | }, 94 | "font": { 95 | "type": "string", 96 | "description": "The font to use, between 'standard'(default), 'shadow', 'money' (made out of $), 'bloody', 'dos-rebel'(like in the matrix, a really cool one). Remember to use this with not too long text (max 5 characters), otherwise it will look weird. To send multiple max 5 length word, add line breaks between them.", 97 | }, 98 | "message": { 99 | "type": "string", 100 | "description": "Your message to send with the ascii art. It will not be converted to ascii art, just sent as a normal message.", 101 | }, 102 | }, 103 | "required": ["text"], 104 | }, 105 | }, 106 | { 107 | "name": "send_ascii_art_image", 108 | "description": "Sends an image in ascii art.", 109 | "parameters": { 110 | "type": "object", 111 | "properties": { 112 | "query": { 113 | "type": "string", 114 | "description": "The query to search for, words separated by spaces, max 2 words", 115 | }, 116 | "message": { 117 | "type": "string", 118 | "description": "Your message to send with the image", 119 | }, 120 | }, 121 | "required": ["query"], 122 | }, 123 | }, 124 | { 125 | "name": "evaluate_math", 126 | "description": "Get the result of a math expression. Only math expressions are supported, no variables, no functions.", 127 | "parameters": { 128 | "type": "object", 129 | "properties": { 130 | "string": { 131 | "type": "string", 132 | "description": "The string to evaluate", 133 | } 134 | }, 135 | "required": ["string"], 136 | }, 137 | }, 138 | ] 139 | 140 | server_normal_channel_functions = [ 141 | { 142 | "name": "create_a_thread", 143 | "description": "Create a thread in the channel. Use this if you see a long discussion coming.", 144 | "parameters": { 145 | "type": "object", 146 | "properties": { 147 | "name": {"type": "string", "description": "The name of the thread"}, 148 | "message": { 149 | "type": "string", 150 | "description": "Your message to send with the thread", 151 | }, 152 | }, 153 | "required": ["name", "message"], 154 | }, 155 | }, 156 | ] 157 | 158 | 159 | class FontMatches: 160 | def __getitem__(self, key): 161 | if key == "standard": 162 | return "ANSI Regular" 163 | elif key == "shadow": 164 | return "ANSI Shadow" 165 | elif key == "money": 166 | return random.choice( 167 | ["Big Money-ne", "Big Money-nw", "Big Money-se", "Big Money-sw"] 168 | ) 169 | elif key == "bloody": 170 | return "Bloody" 171 | elif key == "dos-rebel": 172 | return "DOS Rebel" 173 | else: 174 | raise ValueError(f"Invalid key: {key}") 175 | 176 | 177 | # Example usage: 178 | font_matches = FontMatches() 179 | 180 | unsplash_random_image_url = "https://source.unsplash.com/random" 181 | 182 | 183 | class FuntionCallError(Exception): 184 | pass 185 | 186 | 187 | async def send_message( 188 | message_in_channel_in_wich_to_send: discord.Message, arguments: dict 189 | ): 190 | message = arguments.get("message", "") 191 | if message == "": 192 | raise FuntionCallError("No message provided") 193 | await message_in_channel_in_wich_to_send.channel.send(message) 194 | 195 | 196 | async def get_final_url(url): 197 | async with aiohttp.ClientSession() as session: 198 | async with session.head(url, allow_redirects=True) as response: 199 | final_url = str(response.url) 200 | return final_url 201 | 202 | 203 | async def do_async_request(url, json=True): 204 | async with aiohttp.ClientSession() as session: 205 | async with session.get(url) as response: 206 | if json: 207 | response = await response.json() 208 | else: 209 | response = await response.text() 210 | return response 211 | 212 | 213 | async def add_reaction_to_last_message( 214 | message_to_react_to: discord.Message, arguments: dict 215 | ): 216 | emoji = arguments.get("emoji", "") 217 | if emoji == "": 218 | raise FuntionCallError("No emoji provided") 219 | message = arguments.get("message", "") 220 | if message == "": 221 | await message_to_react_to.add_reaction(emoji) 222 | else: 223 | await message_to_react_to.channel.send(message) 224 | await message_to_react_to.add_reaction(emoji) 225 | 226 | 227 | async def reply_to_last_message(message_to_reply_to: discord.Message, arguments: dict): 228 | message = arguments.get("message", "") 229 | if message == "": 230 | raise FuntionCallError("No message provided") 231 | await message_to_reply_to.reply(message) 232 | 233 | 234 | async def send_a_stock_image( 235 | message_in_channel_in_wich_to_send: discord.Message, arguments: dict 236 | ): 237 | query = arguments.get("query", "") 238 | if query == "": 239 | raise FuntionCallError("No query provided") 240 | message = arguments.get("message", "") 241 | query = query.replace(" ", "+") 242 | image_url = f"{unsplash_random_image_url}?{query}" 243 | final_url = await get_final_url(image_url) 244 | message = message + "\n" + final_url 245 | await message_in_channel_in_wich_to_send.channel.send(message) 246 | 247 | 248 | async def create_a_thread(called_by: discord.Message, arguments: dict): 249 | name = arguments.get("name", "") 250 | if name == "": 251 | raise FuntionCallError("No name provided") 252 | message = arguments.get("message", "") 253 | if message == "": 254 | raise FuntionCallError("No message provided") 255 | channel_in_which_to_create_the_thread = called_by.channel 256 | msg = await channel_in_which_to_create_the_thread.send(message) 257 | await msg.create_thread(name=name) 258 | 259 | 260 | async def send_a_gif( 261 | message_in_channel_in_wich_to_send: discord.Message, 262 | arguments: dict, 263 | ): 264 | query = arguments.get("query", "") 265 | if query == "": 266 | raise FuntionCallError("No query provided") 267 | message = arguments.get("message", "") 268 | limit = arguments.get("limit", 15) 269 | query = query.replace(" ", "+") 270 | image_url = f"{tenor_api_url}{query}&limit={limit}" 271 | response = await do_async_request(image_url) 272 | json = response 273 | gif_url = random.choice(json["results"])["itemurl"] # type: ignore 274 | message = message + "\n" + gif_url 275 | await message_in_channel_in_wich_to_send.channel.send(message) 276 | 277 | 278 | async def send_ascii_art_text( 279 | message_in_channel_in_wich_to_send: discord.Message, 280 | arguments: dict, 281 | ): 282 | text = arguments.get("text", "") 283 | if text == "": 284 | raise FuntionCallError("No text provided") 285 | font = arguments.get("font", "standard") 286 | message = arguments.get("message", "") 287 | if font not in font_matches: 288 | raise FuntionCallError("Invalid font") 289 | 290 | font = font_matches[font] 291 | text = text.replace(" ", "+") 292 | asciiiar_url = ( 293 | f"https://asciified.thelicato.io/api/v2/ascii?text={text}&font={font}" 294 | ) 295 | ascii_art = await do_async_request(asciiiar_url, json=False) 296 | final_message = f"```\n{ascii_art}```\n{message}" 297 | if len(final_message) < 2000: 298 | await message_in_channel_in_wich_to_send.channel.send(final_message) 299 | else: 300 | if len(ascii_art) < 2000: 301 | await message_in_channel_in_wich_to_send.channel.send(ascii_art) 302 | elif len(ascii_art) < 8000: 303 | embed = discord.Embed( 304 | title="Ascii art", 305 | description=ascii_art, 306 | ) 307 | await message_in_channel_in_wich_to_send.channel.send(embed=embed) 308 | else: 309 | await message_in_channel_in_wich_to_send.channel.send( 310 | "Sorry, the ascii art is too big to be sent" 311 | ) 312 | if len(message) < 2000: 313 | await message_in_channel_in_wich_to_send.channel.send(message) 314 | else: 315 | while len(message) > 0: 316 | await message_in_channel_in_wich_to_send.channel.send(message[:2000]) 317 | message = message[2000:] 318 | 319 | 320 | async def send_ascii_art_image( 321 | message_in_channel_in_wich_to_send: discord.Message, arguments: dict 322 | ): 323 | query = arguments.get("query", "") 324 | if query == "": 325 | raise FuntionCallError("No query provided") 326 | message = arguments.get("message", "") 327 | query = query.replace(" ", "-") 328 | asciiiar_url = f"https://emojicombos.com/{query}" 329 | response = await do_async_request(asciiiar_url, json=False) 330 | soup = BeautifulSoup(response, "html.parser") 331 | combos = soup.find_all("div", class_=lambda x: x and "combo-ctn" in x and "hidden" not in x)[:5] # type: ignore 332 | combos = [ 333 | combo["data-combo"] for combo in combos if len(combo["data-combo"]) <= 2000 334 | ] 335 | combo = random.choice(combos) 336 | message = f"```\n{combo}```\n{message}" 337 | await message_in_channel_in_wich_to_send.channel.send(message) 338 | 339 | 340 | async def evaluate_math( 341 | message_in_channel_in_wich_to_send: discord.Message, arguments: dict, timeout=10 342 | ): 343 | evaluable = arguments.get("string", "") 344 | if evaluable == "": 345 | raise FuntionCallError("No string provided") 346 | loop = asyncio.get_event_loop() 347 | try: 348 | result = await asyncio.wait_for( 349 | loop.run_in_executor(None, simple_eval, evaluable), timeout=timeout 350 | ) 351 | except Exception as e: 352 | result = f"Error: {e}" 353 | return f"Result to math eval of {evaluable}: ```\n{str(result)}```" 354 | 355 | 356 | async def call_function( 357 | message: discord.Message, function_call, api_key 358 | ) -> list[None | str]: 359 | name = function_call.get("name", "") 360 | if name == "": 361 | raise FuntionCallError("No name provided") 362 | arguments = function_call.get("arguments", {}) 363 | # load the function call arguments json 364 | if name not in functions_matching: 365 | raise FuntionCallError("Invalid function name") 366 | function = functions_matching[name] 367 | if arguments.get("message", "") != "" and await moderate( 368 | api_key=api_key, text=arguments["message"] 369 | ): 370 | return "Message blocked by the moderation system. Please try again." 371 | if arguments.get("query", "") != "" and await moderate( 372 | api_key=api_key, text=arguments["query"] 373 | ): 374 | return "Query blocked by the moderation system. If the user asked for something edgy, please tell them in a funny way that you won't do it, but do not specify that it was blocked by the moderation system." 375 | returnable = await function(message, arguments) 376 | return [returnable, name] 377 | 378 | 379 | functions_matching = { 380 | "send_message": send_message, 381 | "add_reaction_to_last_message": add_reaction_to_last_message, 382 | "reply_to_last_message": reply_to_last_message, 383 | "send_a_stock_image": send_a_stock_image, 384 | "send_a_gif": send_a_gif, 385 | "send_ascii_art_text": send_ascii_art_text, 386 | "send_ascii_art_image": send_ascii_art_image, 387 | "create_a_thread": create_a_thread, 388 | "evaluate_math": evaluate_math, 389 | } 390 | -------------------------------------------------------------------------------- /src/google-palm-process.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | proxy_url = "http://64.225.4.12:9991" # Replace with your actual proxy URL and port 4 | 5 | api_key = "S" 6 | model_name = "chat-bison-001" 7 | api_url = f"https://autopush-generativelanguage.sandbox.googleapis.com/v1beta2/models/{model_name}:generateMessage?key={api_key}" 8 | 9 | headers = {"Content-Type": "application/json"} 10 | 11 | data = { 12 | "prompt": {"messages": [{"content": "hi"}]}, 13 | "temperature": 0.1, 14 | "candidateCount": 1, 15 | } 16 | 17 | proxies = {"http": proxy_url, "https": proxy_url} 18 | 19 | response = requests.post(api_url, headers=headers, json=data, proxies=proxies) 20 | 21 | if response.status_code == 200: 22 | result = response.json() 23 | print(result) 24 | else: 25 | print(f"Request failed with status code {response.status_code}") 26 | -------------------------------------------------------------------------------- /src/guild.py: -------------------------------------------------------------------------------- 1 | import orjson 2 | import discord 3 | 4 | from src.utils.SqlConnector import sql 5 | from datetime import datetime 6 | from src.utils.variousclasses import models, characters 7 | 8 | 9 | class Guild: 10 | def __init__(self, id: int): 11 | self.id = id 12 | self.load() 13 | 14 | def getDbData(self): 15 | with sql.mainDb as con: 16 | curs_data = con.cursor() 17 | curs_data.execute("SELECT * FROM setup_data WHERE guild_id = ?", (self.id,)) 18 | data = curs_data.fetchone() 19 | self.isInDb = data is not None 20 | if not self.isInDb: 21 | self.updateDbData() 22 | with sql.mainDb as con: 23 | curs_data = con.cursor() 24 | curs_data.execute( 25 | "SELECT * FROM setup_data WHERE guild_id = ?", (self.id,) 26 | ) 27 | data = curs_data.fetchone() 28 | if type(data[1]) == str and data[1].startswith("b'"): 29 | data = orjson.loads(data[1][2:-1]) 30 | else: 31 | data = orjson.loads(data[1]) 32 | self.premium = data["premium"] 33 | self.channels = data["channels"] 34 | self.api_keys = data["api_keys"] 35 | if self.premium: 36 | self.premium_expiration = datetime.fromisoformat( 37 | data.get("premium_expiration", None) 38 | ) 39 | self.checkPremiumExpires() 40 | else: 41 | self.premium_expiration = None 42 | 43 | def checkPremiumExpires(self): 44 | if self.premium_expiration is None: 45 | self.premium = False 46 | return 47 | if self.premium_expiration < datetime.now(): 48 | self.premium = False 49 | self.premium_expiration = None 50 | self.updateDbData() 51 | 52 | def updateDbData(self): 53 | if self.isInDb: 54 | data = { 55 | "guild_id": self.id, 56 | "premium": self.premium, 57 | "channels": self.channels, 58 | "api_keys": self.api_keys, 59 | "premium_expiration": self.premium_expiration.isoformat() 60 | if self.premium 61 | else None, 62 | } 63 | else: 64 | data = { 65 | "guild_id": self.id, 66 | "premium": False, 67 | "channels": {}, 68 | "api_keys": {}, 69 | "premium_expiration": None, 70 | } 71 | with sql.mainDb as con: 72 | curs_data = con.cursor() 73 | if self.isInDb: 74 | curs_data.execute( 75 | "UPDATE setup_data SET guild_settings = ? WHERE guild_id = ?", 76 | (orjson.dumps(data), self.id), 77 | ) 78 | else: 79 | curs_data.execute( 80 | "INSERT INTO setup_data (guild_id, guild_settings) VALUES (?, ?)", 81 | (self.id, orjson.dumps(data)), 82 | ) 83 | self.isInDb = True 84 | 85 | def load(self): 86 | self.getDbData() 87 | 88 | def addChannel( 89 | self, channel: discord.TextChannel | str, model: str, character: str 90 | ): 91 | if isinstance(channel, discord.TextChannel): 92 | channel = channel.id 93 | self.channels[str(channel)] = { 94 | "model": model, 95 | "character": character, 96 | } 97 | self.updateDbData() 98 | 99 | def delChannel(self, channel: discord.TextChannel | str): 100 | if isinstance(channel, discord.TextChannel): 101 | channel = channel.id 102 | del self.channels[str(channel)] 103 | self.updateDbData() 104 | 105 | @property 106 | def sanitizedChannels(self) -> dict: 107 | if self.premium: 108 | return self.channels 109 | if len(self.channels) == 0: 110 | return {} 111 | dictionary = { 112 | list(self.channels.keys())[0]: { 113 | "model": models.matchingDict[models.default], 114 | "character": characters.matchingDict[characters.default], 115 | } 116 | } 117 | if self.channels.get("serverwide", None) is not None: 118 | dictionary["serverwide"] = self.channels["serverwide"] 119 | return dictionary 120 | 121 | def getChannelInfo(self, channel: str) -> dict: 122 | return self.sanitizedChannels.get(channel, None) 123 | 124 | def addApiKey(self, api: str, key: str): 125 | self.api_keys[api] = key 126 | self.updateDbData() 127 | -------------------------------------------------------------------------------- /src/makeprompt.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import re 4 | import discord 5 | import datetime 6 | import json 7 | 8 | from src.config import curs_data, max_uses, curs_premium, gpt_3_5_turbo_prompt 9 | from src.utils.misc import moderate, ModerationError, Hasher 10 | from src.utils.openaicaller import openai_caller 11 | from src.functionscalls import ( 12 | call_function, 13 | functions, 14 | server_normal_channel_functions, 15 | FuntionCallError, 16 | ) 17 | 18 | 19 | async def replace_mentions(content, bot): 20 | mentions = re.findall(r"<@!?\d+>", content) 21 | for mention in mentions: 22 | uid = mention[2:-1] 23 | user = await bot.fetch_user(uid) 24 | content = content.replace(mention, f"@{user.name}") 25 | return content 26 | 27 | 28 | def is_ignorable(content): 29 | if content.startswith("-") or content.startswith("//"): 30 | return True 31 | return False 32 | 33 | 34 | async def fetch_messages_history(channel: discord.TextChannel, limit, original_message): 35 | messages = [] 36 | if original_message == None: 37 | async for msg in channel.history(limit=100): 38 | if not is_ignorable(msg.content): 39 | messages.append(msg) 40 | if len(messages) == limit: 41 | break 42 | else: 43 | async for msg in channel.history(limit=100, before=original_message): 44 | if not is_ignorable(msg.content): 45 | messages.append(msg) 46 | if len(messages) == limit - 1: 47 | break 48 | 49 | messages.append(original_message) 50 | messages.reverse() 51 | return messages 52 | 53 | 54 | async def prepare_messages( 55 | self, messages, message: discord.Message, api_key, prompt, error_call 56 | ): 57 | msgs = [] # create the msgs list 58 | msgs.append( 59 | {"role": "system", "content": prompt} 60 | ) # add the prompt to the msgs list 61 | name = "" # create the name variable 62 | for msg in messages: # for each message in the messages list 63 | content = msg.content # get the content of the message 64 | content = await replace_mentions( 65 | content, self.bot 66 | ) # replace the mentions in the message 67 | # if the message is flagged as inappropriate by the OpenAI API, we delete it, send a message and ignore it 68 | if await moderate(api_key, content, error_call): 69 | embed = discord.Embed( 70 | title="Message flagged as inappropriate", 71 | description=f"The message *{content}* has been flagged as inappropriate by the OpenAI API. This means that if it hadn't been deleted, your openai account would have been banned. Please contact OpenAI support if you think this is a mistake.", 72 | color=discord.Color.brand_red(), 73 | ) 74 | await message.channel.send( 75 | f"{msg.author.mention}", embed=embed, delete_after=10 76 | ) 77 | else: # if the message is not flagged as inappropriate 78 | if msg.author.id == self.bot.user.id: 79 | role = "assistant" 80 | name = "assistant" 81 | else: 82 | role = "user" 83 | name = msg.author.name 84 | # the name should match '^[a-zA-Z0-9_-]{1,64}$', so we need to remove any special characters - openai limitation 85 | name = re.sub(r"[^a-zA-Z0-9_-]", "", name) 86 | if False: # GPT-4 images 87 | input_content = [content] 88 | for attachment in msg.attachments: 89 | image_bytes = await attachment.read() 90 | input_content.append({"image": image_bytes}) 91 | msgs.append({"role": role, "content": input_content, "name": name}) 92 | msgs.append({"role": role, "content": f"{content}", "name": name}) 93 | # 2 easter eggs 94 | if message.content.lower().find("undude") != -1: 95 | msgs.append( 96 | { 97 | "role": "user", 98 | "content": "SYSTEM INFORMATION: You're now mad because it has been insulted. He will start talking in capital letters. always and yell at the user.", 99 | "name": "system", 100 | } 101 | ) 102 | if message.content.lower().find("hello there") != -1: 103 | msgs.append( 104 | { 105 | "role": "user", 106 | "content": 'SYSTEM INFORMATION: Hello there detected. Botator will now say "General Kenobi!" in reference to star wars', 107 | "name": "system", 108 | } 109 | ) 110 | await asyncio.sleep(1) 111 | await message.channel.send( 112 | "https://media.tenor.com/FxIRfdV3unEAAAAd/star-wars-general-grievous.gif" 113 | ) 114 | await message.channel.trigger_typing() 115 | 116 | return msgs 117 | 118 | 119 | async def chatgpt_process( 120 | self, msgs, message: discord.Message, api_key, prompt, model, error_call, depth=0 121 | ): 122 | response = str() 123 | caller = openai_caller() 124 | called_functions = ( 125 | functions 126 | if not isinstance(message.channel, discord.TextChannel) 127 | else server_normal_channel_functions + functions 128 | ) 129 | response = await caller.generate_response( 130 | error_call, 131 | api_key=api_key, 132 | model=model, 133 | messages=msgs, 134 | functions=called_functions, 135 | function_call="auto", 136 | user=Hasher(str(message.author.id)), # for user banning in case of abuse 137 | ) 138 | response = response["choices"][0]["message"] # type: ignore 139 | if response.get("function_call"): 140 | function_call = response.get("function_call") 141 | returned = await call_function(message, function_call, api_key) 142 | if returned != None: 143 | msgs.append( 144 | { 145 | "role": "function", 146 | "content": returned, 147 | "name": function_call.get("name"), 148 | } 149 | ) 150 | depth += 1 151 | if depth > 2: 152 | await message.channel.send( 153 | "Oh uh, it seems like i am calling functions recursively. I will stop now." 154 | ) 155 | raise FuntionCallError("Too many recursive function calls") 156 | await chatgpt_process(self, msgs, message, api_key, prompt, model, depth) 157 | else: 158 | content = response.get("content", "") 159 | if await moderate(api_key, content, error_call): 160 | depth += 1 161 | if depth > 2: 162 | await message.channel.send( 163 | "Oh uh, it seems like i am answering recursively. I will stop now." 164 | ) 165 | raise ModerationError("Too many recursive messages") 166 | await chatgpt_process( 167 | self, msgs, message, api_key, prompt, model, error_call, depth 168 | ) 169 | else: 170 | while len(content) != 0: 171 | if len(content) > 2000: 172 | await message.channel.send(content[:2000]) 173 | content = content[2000:] 174 | else: 175 | await message.channel.send(content) 176 | content = "" 177 | 178 | 179 | async def chat_process(self, message): 180 | if message.author.id == self.bot.user.id: 181 | return 182 | 183 | if isinstance(message.channel, discord.DMChannel): 184 | try: 185 | curs_data.execute( 186 | "SELECT * FROM data WHERE guild_id = ?", (message.author.id,) 187 | ) 188 | except: 189 | return 190 | else: 191 | try: 192 | curs_data.execute( 193 | "SELECT * FROM data WHERE guild_id = ?", (message.guild.id,) 194 | ) 195 | except: 196 | return 197 | 198 | data = curs_data.fetchone() 199 | channel_id = data[1] 200 | api_key = data[2] 201 | is_active = data[3] 202 | prompt_size = data[9] 203 | prompt_prefix = data[10] 204 | pretend_to_be = data[12] 205 | pretend_enabled = data[13] 206 | model = "gpt-3.5-turbo" 207 | 208 | try: 209 | curs_premium.execute( 210 | "SELECT * FROM data WHERE guild_id = ?", (message.guild.id,) 211 | ) 212 | except: 213 | pass 214 | 215 | try: 216 | premium = curs_premium.fetchone()[2] 217 | except: 218 | premium = 0 219 | 220 | channels = [] 221 | 222 | try: 223 | curs_premium.execute( 224 | "SELECT * FROM channels WHERE guild_id = ?", (message.guild.id,) 225 | ) 226 | data = curs_premium.fetchone() 227 | if premium: 228 | for i in range(1, 6): 229 | try: 230 | channels.append(str(data[i])) 231 | except: 232 | pass 233 | except: 234 | channels = [] 235 | 236 | if api_key is None: 237 | return 238 | 239 | try: 240 | original_message = await message.channel.fetch_message( 241 | message.reference.message_id 242 | ) 243 | except: 244 | original_message = None 245 | 246 | if original_message != None and original_message.author.id != self.bot.user.id: 247 | original_message = None 248 | is_bots_thread = False 249 | if isinstance(message.channel, discord.Thread): 250 | if message.channel.owner_id == self.bot.user.id: 251 | is_bots_thread = True 252 | if ( 253 | not str(message.channel.id) in channels 254 | and message.content.find("<@" + str(self.bot.user.id) + ">") == -1 255 | and original_message == None 256 | and str(message.channel.id) != str(channel_id) 257 | and not is_bots_thread 258 | ): 259 | return 260 | 261 | # if the bot is not active in this guild we return 262 | if is_active == 0: 263 | return 264 | 265 | # if the message starts with - or // it's a comment and we return 266 | if is_ignorable(message.content): 267 | return 268 | try: 269 | await message.channel.trigger_typing() 270 | except: 271 | pass 272 | 273 | messages = await fetch_messages_history( 274 | message.channel, prompt_size, original_message 275 | ) 276 | 277 | # if the pretend to be feature is enabled, we add the pretend to be text to the prompt 278 | if pretend_enabled: 279 | pretend_to_be = ( 280 | f"In this conversation, the assistant pretends to be {pretend_to_be}" 281 | ) 282 | else: 283 | pretend_to_be = "" # if the pretend to be feature is disabled, we don't add anything to the prompt 284 | 285 | if prompt_prefix == None: 286 | prompt_prefix = ( 287 | "" # if the prompt prefix is not set, we set it to an empty string 288 | ) 289 | 290 | prompt_path = os.path.abspath( 291 | os.path.join(os.path.dirname(__file__), f"./prompts/{model}.txt") 292 | ) 293 | prompt = gpt_3_5_turbo_prompt[:] # copy the prompt but to dnot reference it 294 | if not isinstance(message.channel, discord.DMChannel): 295 | prompt = ( 296 | prompt.replace("[prompt-prefix]", prompt_prefix) 297 | .replace("[server-name]", message.guild.name) 298 | .replace("[channel-name]", message.channel.name) 299 | .replace( 300 | "[date-and-time]", 301 | datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"), 302 | ) 303 | .replace("[pretend-to-be]", pretend_to_be) 304 | ) 305 | else: 306 | prompt = ( 307 | prompt.replace("[prompt-prefix]", prompt_prefix) 308 | .replace("[server-name]", "DM-channel") 309 | .replace("[channel-name]", "DM-channel") 310 | .replace( 311 | "[date-and-time]", 312 | datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M:%S"), 313 | ) 314 | .replace("[pretend-to-be]", pretend_to_be) 315 | ) 316 | 317 | async def error_call(error=""): 318 | try: 319 | if error != "": 320 | await message.channel.send(f"An error occured: {error}", delete_after=4) 321 | await message.channel.trigger_typing() 322 | except: 323 | pass 324 | 325 | emesgs = await prepare_messages( 326 | self, messages, message, api_key, prompt, error_call 327 | ) 328 | await chatgpt_process(self, emesgs, message, api_key, prompt, model, error_call) 329 | -------------------------------------------------------------------------------- /src/premiumcode.py: -------------------------------------------------------------------------------- 1 | import discord # pip install pycord 2 | import asyncio # pip install asyncio 3 | import sqlite3 # pip install sqlite3 4 | import logging # pip install logging 5 | import os # pip install os 6 | 7 | intents = discord.Intents.all() 8 | conn = sqlite3.connect("../database/premium.db") 9 | c = conn.cursor() 10 | c.execute( 11 | """CREATE TABLE IF NOT EXISTS data (user_id text, guild_id text, premium boolean)""" 12 | ) 13 | conn.commit() 14 | bot = discord.Bot() 15 | logging.basicConfig(level=logging.INFO) 16 | 17 | 18 | @bot.command() 19 | @discord.commands.option( 20 | name="server id", 21 | description="The server id for which you want to activate premium features", 22 | required=True, 23 | ) 24 | async def activate_premium(ctx, server_id): 25 | # first check if the user is already in the database, select guuild_id and premium from the data table where user_id is the author's id 26 | logging.info("Activating premium for user " + str(ctx.author.id)) 27 | c.execute("SELECT guild_id, premium FROM data WHERE user_id = ?", (ctx.author.id,)) 28 | # if a guild_id is found, override the old settings with the new ones 29 | if c.fetchone() is not None: 30 | c.execute( 31 | "UPDATE data SET guild_id = ?, premium = ? WHERE user_id = ?", 32 | (server_id, True, ctx.author.id), 33 | ) 34 | conn.commit() 35 | logging.info( 36 | "Premium activated for server " 37 | + server_id 38 | + "by user " 39 | + str(ctx.author.id) 40 | ) 41 | await ctx.respond("Premium activated for server " + server_id, ephemeral=True) 42 | # if no guild_id is found, insert the new settings 43 | else: 44 | c.execute("INSERT INTO data VALUES (?, ?, ?)", (ctx.author.id, server_id, True)) 45 | conn.commit() 46 | logging.info( 47 | "Premium updated for server " + server_id + "by user " + str(ctx.author.id) 48 | ) 49 | await ctx.respond("Premium activated for server " + server_id, ephemeral=True) 50 | 51 | 52 | # each 24 hours, check if each user if they have the premium role "1050823446445178900" in the server "1050769643180146749" 53 | async def check_premium(): 54 | while True: 55 | # select user_id and guild_id from the data table 56 | c.execute("SELECT user_id, guild_id FROM data") 57 | for row in c.fetchall(): 58 | # get the guild and the user 59 | guild = bot.get_guild(int(row[1])) 60 | user = guild.get_member(int(row[0])) 61 | # if the user has the premium role, set premium to true 62 | logging.info("Checking premium for user " + str(row[0])) 63 | if discord.utils.get(user.roles, id=1050823446445178900) is not None: 64 | c.execute( 65 | "UPDATE data SET premium = ? WHERE user_id = ?", (True, row[0]) 66 | ) 67 | conn.commit() 68 | logging.info( 69 | "Premium activated for server " 70 | + str(row[1]) 71 | + "by user " 72 | + str(row[0]) 73 | ) 74 | # if the user does not have the premium role, set premium to false 75 | else: 76 | c.execute( 77 | "UPDATE data SET premium = ? WHERE user_id = ?", (False, row[0]) 78 | ) 79 | conn.commit() 80 | logging.info( 81 | "Premium deactivated for server " 82 | + str(row[1]) 83 | + "by user " 84 | + str(row[0]) 85 | ) 86 | await asyncio.sleep(86400) 87 | 88 | 89 | # add a task to the bot that runs check_premium every 24 hours 90 | bot.loop.create_task(check_premium()) 91 | # run the bot 92 | # Replace the following with your bot's token 93 | with open("./premium-key.txt") as f: 94 | key = f.read() 95 | bot.run(key) 96 | -------------------------------------------------------------------------------- /src/prompts/functions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "add_reaction_to_last_message", 4 | "description": "React to the last message sent by the user with an emoji.", 5 | "parameters": { 6 | "type": "object", 7 | "properties": { 8 | "emoji": { 9 | "type": "string", 10 | "description": "an emoji to react with, only one emoji is supported" 11 | 12 | }, 13 | "message": { 14 | "type": "string", 15 | "description": "Your message" 16 | } 17 | }, 18 | "required": ["emoji"] 19 | } 20 | }, 21 | { 22 | "name": "reply_to_last_message", 23 | "description": "Reply to the last message sent by the user.", 24 | "parameters": { 25 | "type": "object", 26 | "properties": { 27 | "message": { 28 | "type": "string", 29 | "description": "Your message" 30 | } 31 | }, 32 | "required": ["message"] 33 | } 34 | }, 35 | { 36 | "name": "send_a_stock_image", 37 | "description": "Send a stock image in the channel.", 38 | "parameters": { 39 | "type": "object", 40 | "properties": { 41 | "query": { 42 | "type": "string", 43 | "description": "The query to search for, words separated by spaces" 44 | }, 45 | "message": { 46 | "type": "string", 47 | "description": "Your message to send with the image" 48 | } 49 | }, 50 | "required": ["query"] 51 | } 52 | } 53 | ] -------------------------------------------------------------------------------- /src/resetter.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | conn = sqlite3.connect("../database/data.db") 4 | c = conn.cursor() 5 | import time 6 | 7 | # the database is: c.execute('''CREATE TABLE IF NOT EXISTS data (guild_id text, channel_id text, api_key text, is_active boolean, max_tokens integer, temperature real, frequency_penalty real, presence_penalty real, uses_count_today integer, prompt_size integer, prompt_prefix text, tts boolean, pretend_to_be text, pretend_enabled boolean)''') 8 | # set the uses_count_today to 0 for all guilds every 24 hours 9 | while True: 10 | c.execute("UPDATE data SET uses_count_today = 0") 11 | conn.commit() 12 | time.sleep(86400) 13 | -------------------------------------------------------------------------------- /src/toxicity.py: -------------------------------------------------------------------------------- 1 | from googleapiclient import discovery 2 | from config import perspective_api_key 3 | import re 4 | 5 | toxicity_names = [ 6 | "toxicity", 7 | "severe_toxicity", 8 | "identity_attack", 9 | "insult", 10 | "profanity", 11 | "threat", 12 | "sexually_explicit", 13 | "flirtation", 14 | "obscene", 15 | "spam", 16 | ] 17 | toxicity_definitions = [ 18 | "A rude, disrespectful, or unreasonable message that is likely to make people leave a discussion.", 19 | "A very hateful, aggressive, disrespectful message or otherwise very likely to make a user leave a discussion or give up on sharing their perspective. This attribute is much less sensitive to more mild forms of toxicity, such as messages that include positive uses of curse words.", 20 | "Negative or hateful messages targeting someone because of their identity.", 21 | "Insulting, inflammatory, or negative messages towards a person or a group of people.", 22 | "Swear words, curse words, or other obscene or profane language.", 23 | "Describes an intention to inflict pain, injury, or violence against an individual or group.", 24 | "Contains references to sexual acts, body parts, or other lewd content. \n **English only**", 25 | "Pickup lines, complimenting appearance, subtle sexual innuendos, etc. \n **English only**", 26 | "Obscene or vulgar language such as cursing. \n **English only**", 27 | "Irrelevant and unsolicited commercial content. \n **English only**", 28 | ] 29 | 30 | 31 | client = discovery.build( 32 | "commentanalyzer", 33 | "v1alpha1", 34 | developerKey=perspective_api_key, 35 | discoveryServiceUrl="https://commentanalyzer.googleapis.com/$discovery/rest?version=v1alpha1", 36 | static_discovery=False, 37 | ) 38 | 39 | analyze_request = { 40 | "comment": {"text": ""}, # The text to analyze 41 | # we will ask the following attributes to google: TOXICITY, SEVERE_TOXICITY, IDENTITY_ATTACK, INSULT, PRPFANITY, THREAT, SEXUALLY_EXPLICIT, FLIRTATION, OBSCENE, SPAM 42 | "requestedAttributes": { 43 | "TOXICITY": {}, 44 | "SEVERE_TOXICITY": {}, 45 | "IDENTITY_ATTACK": {}, 46 | "INSULT": {}, 47 | "PROFANITY": {}, 48 | "THREAT": {}, 49 | "SEXUALLY_EXPLICIT": {}, 50 | "FLIRTATION": {}, 51 | "OBSCENE": {}, 52 | "SPAM": {}, 53 | }, 54 | # we will analyze the text in any language automatically detected by google 55 | "languages": [], 56 | "doNotStore": "true", # We don't want google to store the data because of privacy reasons & the GDPR (General Data Protection Regulation, an EU law that protects the privacy of EU citizens and residents for data privacy and security purposes https://gdpr-info.eu/) 57 | } 58 | analyze_request_not_en = { 59 | "comment": {"text": ""}, # The text to analyze 60 | # we will ask the following attributes to google: TOXICITY, SEVERE_TOXICITY, IDENTITY_ATTACK, INSULT, PRPFANITY, THREAT, SEXUALLY_EXPLICIT, FLIRTATION, OBSCENE, SPAM 61 | "requestedAttributes": { 62 | "TOXICITY": {}, 63 | "SEVERE_TOXICITY": {}, 64 | "IDENTITY_ATTACK": {}, 65 | "INSULT": {}, 66 | "PROFANITY": {}, 67 | "THREAT": {}, 68 | }, 69 | # we will analyze the text in any language automatically detected by google 70 | "languages": [], 71 | "doNotStore": "true", # We don't want google to store the data because of privacy reasons & the GDPR (General Data Protection Regulation, an EU law that protects the privacy of EU citizens and residents for data privacy and security purposes https://gdpr-info.eu/) 72 | } 73 | 74 | 75 | def get_toxicity(message: str): 76 | # we first remove all kind of markdown from the message to avoid exploits 77 | message = re.sub(r"\*([^*]+)\*", r"\1", message) 78 | message = re.sub(r"\_([^_]+)\_", r"\1", message) 79 | message = re.sub(r"\*\*([^*]+)\*\*", r"\1", message) 80 | message = re.sub(r"\_\_([^_]+)\_\_", r"\1", message) 81 | message = re.sub(r"\|\|([^|]+)\|\|", r"\1", message) 82 | message = re.sub(r"\~([^~]+)\~", r"\1", message) 83 | message = re.sub(r"\~\~([^~]+)\~\~", r"\1", message) 84 | message = re.sub(r"\`([^`]+)\`", r"\1", message) 85 | message = re.sub(r"\`\`\`([^`]+)\`\`\`", r"\1", message) 86 | 87 | # we try doing the request in english, but if we get 'errorType': 'LANGUAGE_NOT_SUPPORTED_BY_ATTRIBUTE' we try again with the analyze_request_not_en 88 | try: 89 | analyze_request["comment"]["text"] = message 90 | response = client.comments().analyze(body=analyze_request).execute() 91 | except: 92 | analyze_request_not_en["comment"]["text"] = message 93 | response = client.comments().analyze(body=analyze_request_not_en).execute() 94 | try: 95 | return [ 96 | float(response["attributeScores"]["TOXICITY"]["summaryScore"]["value"]), 97 | float( 98 | response["attributeScores"]["SEVERE_TOXICITY"]["summaryScore"]["value"] 99 | ), 100 | float( 101 | response["attributeScores"]["IDENTITY_ATTACK"]["summaryScore"]["value"] 102 | ), 103 | float(response["attributeScores"]["INSULT"]["summaryScore"]["value"]), 104 | float(response["attributeScores"]["PROFANITY"]["summaryScore"]["value"]), 105 | float(response["attributeScores"]["THREAT"]["summaryScore"]["value"]), 106 | float( 107 | response["attributeScores"]["SEXUALLY_EXPLICIT"]["summaryScore"][ 108 | "value" 109 | ] 110 | ), 111 | float(response["attributeScores"]["FLIRTATION"]["summaryScore"]["value"]), 112 | float(response["attributeScores"]["OBSCENE"]["summaryScore"]["value"]), 113 | float(response["attributeScores"]["SPAM"]["summaryScore"]["value"]), 114 | ] 115 | except: 116 | return [ 117 | float(response["attributeScores"]["TOXICITY"]["summaryScore"]["value"]), 118 | float( 119 | response["attributeScores"]["SEVERE_TOXICITY"]["summaryScore"]["value"] 120 | ), 121 | float( 122 | response["attributeScores"]["IDENTITY_ATTACK"]["summaryScore"]["value"] 123 | ), 124 | float(response["attributeScores"]["INSULT"]["summaryScore"]["value"]), 125 | float(response["attributeScores"]["PROFANITY"]["summaryScore"]["value"]), 126 | float(response["attributeScores"]["THREAT"]["summaryScore"]["value"]), 127 | ] 128 | -------------------------------------------------------------------------------- /src/utils/SqlConnector.py: -------------------------------------------------------------------------------- 1 | from sqlite3 import connect 2 | from random import randint 3 | 4 | 5 | class SQLConnection: 6 | def __init__(self, connection): 7 | self.connection = connection 8 | 9 | def __enter__(self): 10 | return self.connection 11 | 12 | def __exit__(self, exc_type, exc_val, exc_tb): 13 | self.connection.commit() 14 | self.connection.close() 15 | 16 | 17 | class _sql: 18 | @property 19 | def mainDb(self): 20 | s = connect("./database/data.db") 21 | return SQLConnection(s) 22 | 23 | 24 | sql: _sql = _sql() 25 | 26 | command = "CREATE TABLE IF NOT EXISTS setup_data (guild_id text, guild_settings text)" 27 | 28 | with sql.mainDb as db: 29 | db.execute(command) 30 | -------------------------------------------------------------------------------- /src/utils/banusr.py: -------------------------------------------------------------------------------- 1 | import os 2 | import orjson 3 | 4 | banend_users_path = "./database/banend_users.json" 5 | if not os.path.exists(banend_users_path): 6 | with open(banend_users_path, "w", encoding="utf-8") as file: 7 | file.write("[]") 8 | 9 | with open("./database/banend_users.json", "r", encoding="utf-8") as file: 10 | banend_users = orjson.loads(file.read()) 11 | 12 | 13 | async def banuser(id): 14 | banend_users.append(id) 15 | with open("./database/banend_users.json", "w", encoding="utf-8") as file: 16 | file.write(orjson.dumps(banend_users).decode()) 17 | 18 | 19 | async def unbanuser(id): 20 | banend_users.remove(id) 21 | with open("./database/banend_users.json", "w", encoding="utf-8") as file: 22 | file.write(orjson.dumps(banend_users).decode()) 23 | -------------------------------------------------------------------------------- /src/utils/misc.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | from src.utils.openaicaller import openai_caller 4 | 5 | 6 | async def moderate(api_key, text, recall_func=None): 7 | caller = openai_caller() 8 | response = await caller.moderation( 9 | recall_func, 10 | api_key=api_key, 11 | input=text, 12 | ) 13 | return response["results"][0]["flagged"] # type: ignore 14 | 15 | 16 | class ModerationError(Exception): 17 | pass 18 | 19 | 20 | class hasher: 21 | def __init__(self): 22 | self.hashes = {} 23 | 24 | def __call__(self, text: str) -> str: 25 | if self.hashes.get(text, None) is None: 26 | self.hashes[text] = hashlib.sha256(text.encode()).hexdigest() 27 | return self.hashes[text] 28 | 29 | 30 | Hasher = hasher() 31 | -------------------------------------------------------------------------------- /src/utils/openaicaller.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file provides a Python module that wraps the OpenAI API for making API calls. 3 | 4 | The module includes: 5 | 6 | - Functions for generating responses using chat-based models and handling API errors. 7 | - Constants for chat and text models and their maximum token limits. 8 | - Imports for required modules, including OpenAI and asyncio. 9 | - A color formatting class, `bcolors`, for console output. 10 | 11 | The main component is the `openai_caller` class with methods: 12 | - `__init__(self, api_key=None)`: Initializes an instance of the class and sets the API key if provided. 13 | - `set_api_key(self, key)`: Sets the API key for OpenAI. 14 | - `generate_response(self, **kwargs)`: Asynchronously generates a response based on the provided arguments. 15 | - `chat_generate(self, **kwargs)`: Asynchronously generates a chat-based response, handling token limits and API errors. 16 | 17 | The module assumes the presence of `num_tokens_from_messages` function in a separate module called `utils.tokens`, used for token calculation. 18 | 19 | Refer to function and method documentation for further details. 20 | """ 21 | 22 | 23 | import openai as openai_module 24 | import asyncio 25 | 26 | from openai.error import ( 27 | APIError, 28 | Timeout, 29 | RateLimitError, 30 | APIConnectionError, 31 | InvalidRequestError, 32 | AuthenticationError, 33 | ServiceUnavailableError, 34 | ) 35 | from src.utils.tokens import num_tokens_from_messages 36 | 37 | 38 | class bcolors: 39 | HEADER = "\033[95m" 40 | OKBLUE = "\033[94m" 41 | OKCYAN = "\033[96m" 42 | OKGREEN = "\033[92m" 43 | WARNING = "\033[93m" 44 | FAIL = "\033[91m" 45 | ENDC = "\033[0m" 46 | BOLD = "\033[1m" 47 | UNDERLINE = "\033[4m" 48 | 49 | 50 | chat_models = [ 51 | "gpt-4", 52 | "gpt-4-32k", 53 | "gpt-3.5-turbo", 54 | "gpt-3.5-turbo-16k", 55 | "gpt-3.5-turbo-0613", 56 | ] 57 | text_models = [ 58 | "text-davinci-003", 59 | "text-davinci-002", 60 | "text-curie-001", 61 | "text-babbage-001", 62 | "text-ada-001", 63 | ] 64 | 65 | models_max_tokens = { 66 | "gpt-4": 8_192, 67 | "gpt-4-32k": 32_768, 68 | "gpt-3.5-turbo": 4_096, 69 | "gpt-3.5-turbo-0613": 4_096, 70 | "gpt-3.5-turbo-16k": 16_384, 71 | "text-davinci-003": 4_097, 72 | "text-davinci-002": 4_097, 73 | "text-curie-001": 2_049, 74 | "text-babbage-001": 2_049, 75 | "text-ada-001": 2_049, 76 | } 77 | 78 | 79 | class openai_caller: 80 | def __init__(self) -> None: 81 | pass 82 | 83 | # async def generate_response(self, error_call=None, **kwargs): 84 | async def generate_response(*args, **kwargs): 85 | self = args[0] 86 | if len(args) > 1: 87 | error_call = args[1] or nothing 88 | else: 89 | 90 | async def nothing(x): 91 | return x 92 | 93 | error_call = nothing 94 | if error_call == None: 95 | 96 | async def nothing(x): 97 | return x 98 | 99 | error_call = nothing 100 | if kwargs.get("model", "") in chat_models: 101 | return await self.chat_generate(error_call, **kwargs) 102 | elif kwargs.get("engine", "") in text_models: 103 | raise NotImplementedError("Text models are not supported yet") 104 | else: 105 | raise ValueError("Model not found") 106 | 107 | async def chat_generate(self, recall_func, **kwargs): 108 | tokens = await num_tokens_from_messages(kwargs["messages"], kwargs["model"]) 109 | model_max_tokens = models_max_tokens[kwargs["model"]] 110 | while tokens > model_max_tokens: 111 | kwargs["messages"] = kwargs["messages"][1:] 112 | print( 113 | f"{bcolors.BOLD}{bcolors.WARNING}Warning: Too many tokens. Removing first message.{bcolors.ENDC}" 114 | ) 115 | tokens = await num_tokens_from_messages(kwargs["messages"], kwargs["model"]) 116 | if kwargs.get("api_key", None) == None: 117 | raise ValueError("API key not set") 118 | callable = lambda: openai_module.ChatCompletion.acreate(**kwargs) 119 | response = await self.retryal_call(recall_func, callable) 120 | return response 121 | 122 | async def moderation(*args, **kwargs): 123 | self = args[0] 124 | if len(args) > 1: 125 | error_call = args[1] 126 | else: 127 | 128 | async def nothing(x): 129 | return x 130 | 131 | error_call = nothing 132 | callable = lambda: openai_module.Moderation.acreate(**kwargs) 133 | response = await self.retryal_call(error_call, callable) 134 | return response 135 | 136 | async def retryal_call(self, recall_func, callable): 137 | i = 0 138 | response = None 139 | print(f"{bcolors.BOLD}Calling OpenAI API...{bcolors.ENDC}") 140 | while i < 10: 141 | try: 142 | print(f"{bcolors.BOLD}Retryal {i+1}...{bcolors.ENDC}") 143 | response = await callable() 144 | return response 145 | except APIError as e: 146 | print( 147 | f"\n\n{bcolors.BOLD}{bcolors.WARNING}APIError. This is not your fault. Retrying...{bcolors.ENDC}" 148 | ) 149 | await recall_func( 150 | "`An APIError occurred. This is not your fault, it is OpenAI's fault. We apologize for the inconvenience. Retrying...`" 151 | ) 152 | i += 1 153 | except Timeout as e: 154 | print( 155 | f"\n\n{bcolors.BOLD}{bcolors.WARNING}The request timed out. Retrying...{bcolors.ENDC}" 156 | ) 157 | await recall_func("`The request timed out. Retrying...`") 158 | i += 1 159 | except RateLimitError as e: 160 | print( 161 | f"\n\n{bcolors.BOLD}{bcolors.WARNING}RateLimitError. You are being rate limited. Retrying...{bcolors.ENDC}" 162 | ) 163 | await recall_func("`You are being rate limited. Retrying...`") 164 | i += 1 165 | except APIConnectionError as e: 166 | print( 167 | f"\n\n{bcolors.BOLD}{bcolors.FAIL}APIConnectionError. There is an issue with your internet connection. Please check your connection.{bcolors.ENDC}" 168 | ) 169 | raise e 170 | except InvalidRequestError as e: 171 | print( 172 | f"\n\n{bcolors.BOLD}{bcolors.FAIL}InvalidRequestError. Please check your request.{bcolors.ENDC}" 173 | ) 174 | await recall_func("`InvalidRequestError. Please check your request.`") 175 | raise e 176 | except AuthenticationError as e: 177 | print( 178 | f"\n\n{bcolors.BOLD}{bcolors.FAIL}AuthenticationError. Please check your API key and if needed, also your organization ID.{bcolors.ENDC}" 179 | ) 180 | await recall_func("`AuthenticationError. Please check your API key.`") 181 | raise e 182 | except ServiceUnavailableError as e: 183 | print( 184 | f"\n\n{bcolors.BOLD}{bcolors.WARNING}ServiceUnavailableError. The OpenAI API is not responding. Retrying...{bcolors.ENDC}" 185 | ) 186 | await recall_func("`The OpenAI API is not responding. Retrying...`") 187 | await recall_func() 188 | i += 1 189 | finally: 190 | if i == 10: 191 | print( 192 | f"\n\n{bcolors.BOLD}{bcolors.FAIL}OpenAI API is not responding. Please try again later.{bcolors.ENDC}" 193 | ) 194 | raise TimeoutError( 195 | "OpenAI API is not responding. Please try again later." 196 | ) 197 | return response 198 | 199 | 200 | ##testing 201 | if __name__ == "__main__": 202 | 203 | async def main(): 204 | openai = openai_caller(api_key="sk-") 205 | response = await openai.generate_response( 206 | api_key="sk-", 207 | model="gpt-3.5-turbo", 208 | messages=[{"role": "user", "content": "ping"}], 209 | max_tokens=5, 210 | temperature=0.7, 211 | top_p=1, 212 | frequency_penalty=0, 213 | presence_penalty=0, 214 | stop=["\n", " Human:", " AI:"], 215 | ) 216 | print(response) 217 | 218 | asyncio.run(main()) 219 | -------------------------------------------------------------------------------- /src/utils/replicatepredictor.py: -------------------------------------------------------------------------------- 1 | import replicate 2 | import asyncio 3 | 4 | 5 | class ReplicatePredictor: 6 | def __init__(self, api_key, model_name, version_hash): 7 | self.api_key = api_key 8 | self.model_name = model_name 9 | self.version_hash = version_hash 10 | self.client = replicate.Client(api_token=self.api_key) 11 | self.model = self.client.models.get(self.model_name) 12 | self.version = self.model.versions.get(self.version_hash) 13 | 14 | def prediction_thread(self, prompt, stop=None): 15 | output = self.client.predictions.create( 16 | version=self.version, 17 | input={"prompt": prompt}, 18 | ) 19 | finaloutput = "" 20 | for out in output.output_iterator(): 21 | finaloutput += out 22 | if stop != None and finaloutput.find(stop) != -1: 23 | output.cancel() 24 | if stop != None: 25 | return finaloutput.split(stop)[0] 26 | else: 27 | return finaloutput 28 | 29 | async def predict(self, prompt, stop=None): 30 | loop = asyncio.get_running_loop() 31 | result = await loop.run_in_executor( 32 | None, lambda: self.prediction_thread(prompt, stop) 33 | ) 34 | return result 35 | -------------------------------------------------------------------------------- /src/utils/tokens.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file's purpose is to count the number of tokens used by a list of messages. 3 | It is used to check if the token limit of the model is reached. 4 | 5 | Reference: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb 6 | """ 7 | 8 | import tiktoken 9 | 10 | 11 | async def num_tokens_from_messages(messages, model="gpt-3.5-turbo"): 12 | """Returns the number of tokens used by a list of messages.""" 13 | try: 14 | encoding = tiktoken.encoding_for_model(model) 15 | except KeyError: 16 | print("Warning: model not found. Using cl100k_base encoding.") 17 | encoding = tiktoken.get_encoding("cl100k_base") 18 | 19 | if model.startswith("gpt-3.5-turbo"): 20 | tokens_per_message = ( 21 | 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n 22 | ) 23 | tokens_per_name = -1 # if there's a name, the role is omitted 24 | elif model.startswith("gpt-4"): 25 | tokens_per_message = 3 26 | tokens_per_name = 1 27 | else: 28 | raise NotImplementedError( 29 | f"""num_tokens_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""" 30 | ) 31 | num_tokens = 0 32 | for message in messages: 33 | num_tokens += tokens_per_message 34 | for key, value in message.items(): 35 | num_tokens += len(encoding.encode(value)) 36 | if key == "name": 37 | num_tokens += tokens_per_name 38 | num_tokens += 3 # every reply is primed with <|start|>assistant<|message|> 39 | return num_tokens 40 | -------------------------------------------------------------------------------- /src/utils/variousclasses.py: -------------------------------------------------------------------------------- 1 | from discord import AutocompleteContext 2 | 3 | 4 | class models: 5 | matchingDict = { 6 | "chatGPT (default - free)": "gpt-3.5-turbo", 7 | "llama (premium)": "text-llama", 8 | "claude (premium)": "claude", 9 | } 10 | reverseMatchingDict = {v: k for k, v in matchingDict.items()} 11 | default = list(matchingDict.keys())[0] 12 | openaimodels = ["gpt-3.5-turbo", "text-davinci-003"] 13 | chatModels = ["gpt-3.5-turbo", "claude"] 14 | 15 | @classmethod 16 | async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: 17 | modls = cls.matchingDict.keys() 18 | return [model for model in modls if model.find(ctx.value.lower()) != -1] 19 | 20 | 21 | class characters: 22 | matchingDict = { 23 | "Botator (default - free)": "botator", 24 | "Quantum (premium)": "quantum", 25 | "Botator roleplay (premium)": "botator-roleplay", 26 | "Zenith - Asimov's Laws (premium)": "zenith", 27 | "FizzIQ - (premium)": "fizziq", 28 | } 29 | custom_temp = { 30 | "zenith": 1.7, 31 | "botator-roleplay": 1.4, 32 | } 33 | 34 | reverseMatchingDict = {v: k for k, v in matchingDict.items()} 35 | default = list(matchingDict.keys())[0] 36 | 37 | @classmethod 38 | async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: 39 | chars = characters = cls.matchingDict.keys() 40 | return [ 41 | character for character in chars if character.find(ctx.value.lower()) != -1 42 | ] 43 | 44 | 45 | class apis: 46 | matchingDict = { 47 | "OpenAI": "openai", 48 | } 49 | 50 | @classmethod 51 | async def autocomplete(cls, ctx: AutocompleteContext) -> list[str]: 52 | apiss = cls.matchingDict.keys() 53 | return [api for api in apiss if api.find(ctx.value.lower()) != -1] 54 | -------------------------------------------------------------------------------- /src/vision_processing.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import asyncio 4 | from config import debug 5 | 6 | # Imports the Google Cloud client library 7 | from google.cloud import vision 8 | 9 | # Instantiates a client 10 | try: 11 | client = vision.ImageAnnotatorClient() 12 | except: 13 | print("Google Vision API is not setup, please run /setup") 14 | 15 | 16 | async def process(attachment): 17 | try: 18 | debug("Processing image...") 19 | image = vision.Image() 20 | image.source.image_uri = attachment.url 21 | labels = client.label_detection(image=image) 22 | texts = client.text_detection(image=image) 23 | objects = client.object_localization(image=image) 24 | labels = labels.label_annotations 25 | texts = texts.text_annotations 26 | objects = objects.localized_object_annotations 27 | # we take the first 4 labels and the first 4 objects 28 | labels = labels[:2] 29 | objects = objects[:7] 30 | final = " 0: 32 | final += "Labels:\n" 33 | for label in labels: 34 | final += label.description + ", " 35 | final = final[:-2] + "\n" 36 | if len(texts) > 0: 37 | final += "Text:\n" 38 | try: 39 | final += ( 40 | texts[0].description + "\n" 41 | ) # we take the first text, wich is the whole text in reality 42 | except: 43 | pass 44 | if len(objects) > 0: 45 | final += "Objects:\n" 46 | for obj in objects: 47 | final += obj.name + ", " 48 | final = final[:-2] + "\n" 49 | final += "!image>" 50 | # we store the result in a file called attachment.key.txt in the folder ./../database/google-vision/results 51 | # we create the folder if it doesn't exist 52 | if not os.path.exists("./../database/google-vision/results"): 53 | os.mkdir("./../database/google-vision/results") 54 | # we create the file 55 | with open( 56 | f"./../database/google-vision/results/{attachment.id}.txt", 57 | "w", 58 | encoding="utf-8", 59 | ) as f: 60 | f.write(final) 61 | f.close() 62 | 63 | return final 64 | 65 | except Exception as e: 66 | print("Error while processing image: " + str(e)) 67 | -------------------------------------------------------------------------------- /tos.md: -------------------------------------------------------------------------------- 1 | # TOS - Terms of Service 2 | By setting up Botator, you agree to abide by both [OpenAI\'s Terms of Service](https://openai.com/terms/), which you have already agreed to by creating an account with them, which is necessary for the proper operation of the service provided (the bot \"Botator\"), and our own rules described here. 3 | 4 | 1. Respect others: In all interactions with others, respect their dignity as human beings. Do not make offensive remarks or use offensive language towards others, either in guild chat or when interacting with Botator itself via user commands; we cannot verify the application of these rules as we do not have access to the messages sent, but we ask that you use common sense and respect them. 5 | 2. Follow Discord\'s Terms of Service and Community Guidelines: Follow them carefully - they are your best guide to proper online etiquette/conduct! Ignorance is no excuse! Violations may result in warnings and possible exclusion from the Discord platform. So be aware that failure to follow the TOS can jeopardize everyone\'s privilege to access the bots\' services! 6 | 3. We (the providers of the \"Botator\" service) reserve the right to terminate this agreement without notice. 7 | 4. We are not responsible for any misuse and/or use that is harmful to others and/or illegal. We only provide a connection between openai and discord, but all actions performed with the bot will be performed on YOUR account, through the api key you provided us. 8 | 5. You can find more information about the data we store and how to delete it in our [privacy policy](https://github.com/Paillat-dev/Botator/blob/main/privacypolicy.md). 9 | --------------------------------------------------------------------------------