├── cogs ├── __init__.py ├── roleAssign.py ├── help.py ├── steam.py ├── gwent.py ├── mod.py ├── admin.py ├── forum.py ├── fun.py ├── anime.py └── utility.py ├── config ├── __init__.py ├── blacklist.py ├── cogs.py ├── config.example.py └── games.py ├── img ├── ava.png └── title.png ├── font ├── Ubuntu-R.ttf ├── copyright.txt ├── TRADEMARKS.txt ├── LICENCE.txt └── LICENCE-FAQ.txt ├── requirements.txt ├── examples ├── discord.service.example └── docker-compose.example.yml ├── tests └── test_send.py ├── tests.py ├── LICENSE ├── Dockerfile ├── .github ├── dependabot.yml └── workflows │ ├── docker-publish.yml │ └── codeql-analysis.yml ├── .dockerignore ├── .gitignore ├── loadconfig.py ├── main.py ├── README.md └── .pylintrc /cogs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/ava.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Der-Eddy/discord_bot/HEAD/img/ava.png -------------------------------------------------------------------------------- /img/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Der-Eddy/discord_bot/HEAD/img/title.png -------------------------------------------------------------------------------- /font/Ubuntu-R.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Der-Eddy/discord_bot/HEAD/font/Ubuntu-R.ttf -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.13.0 2 | discord.py==2.6.4 3 | Pillow==11.3.0 4 | pytz==2025.2 5 | -------------------------------------------------------------------------------- /config/blacklist.py: -------------------------------------------------------------------------------- 1 | __blacklist__ = [ 2 | 189742735513616384, 3 | 248205232012656651 4 | ] 5 | -------------------------------------------------------------------------------- /font/copyright.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010,2011 Canonical Ltd. 2 | 3 | This Font Software is licensed under the Ubuntu Font Licence, Version 4 | 1.0. https://launchpad.net/ubuntu-font-licence 5 | 6 | -------------------------------------------------------------------------------- /font/TRADEMARKS.txt: -------------------------------------------------------------------------------- 1 | Ubuntu and Canonical are registered trademarks of Canonical Ltd. 2 | 3 | The licence accompanying these works does not grant any rights 4 | under trademark law and all such rights are reserved. 5 | -------------------------------------------------------------------------------- /config/cogs.py: -------------------------------------------------------------------------------- 1 | #Cogs to enable on Startup 2 | __cogs__ = [ 3 | 'cogs.admin', 4 | 'cogs.mod', 5 | 'cogs.fun', 6 | 'cogs.anime', 7 | 'cogs.forum', 8 | 'cogs.utility', 9 | 'cogs.help' 10 | ] 11 | -------------------------------------------------------------------------------- /examples/discord.service.example: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Shinobu Discord Bot 3 | After=multi-user.target 4 | [Service] 5 | WorkingDirectory=/home/eddy/discord_bot 6 | Environment="PYTHONHASHSEED=0" 7 | User=eddy 8 | Group=eddy 9 | ExecStart=/usr/bin/python3 /home/eddy/discord_bot/main.py 10 | Type=idle 11 | Restart=on-failure 12 | RestartSec=15 13 | TimeoutStartSec=15 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /tests/test_send.py: -------------------------------------------------------------------------------- 1 | import discord 2 | import pytest 3 | 4 | bot_channel = 165174405222236161 5 | 6 | @pytest.mark.asyncio 7 | async def test_message(bot): 8 | channel = client.get_channel(bot_channel) 9 | 10 | await channel.send("Test Message") 11 | 12 | @pytest.mark.asyncio 13 | async def test_embed(bot): 14 | channel = client.get_channel(bot_channel) 15 | 16 | embed = discord.Embed(title="Test Embed") 17 | embed.add_field(name="Field 1", value="Lorem ipsum") 18 | 19 | await channel.send(embed=embed) 20 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import discord.ext.test as dpytest 2 | from main import ShinobuBot 3 | import pytest 4 | 5 | 6 | @pytest.mark.asyncio 7 | async def test_bot(): 8 | bot = ShinobuBot() 9 | dpytest.configure(bot) 10 | 11 | # Load any extensions/cogs you want to in here 12 | bot_channel = 165174405222236161 13 | channel = bot.get_channel(bot_channel) 14 | await channel.send("Test Message") 15 | dpytest.verify_message("Test Message") 16 | 17 | await dpytest.message(":about") 18 | dpytest.verify_message("[Expected help output]") -------------------------------------------------------------------------------- /cogs/roleAssign.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | import loadconfig 4 | 5 | class roleAssign(discord.Client): 6 | '''Fügt eine Rolle neuen Benutzern beim joinen des Server hinzu''' 7 | 8 | def __init__(self, bot): 9 | self.bot = bot 10 | 11 | async def on_member_join(self, member): 12 | if member.server.id == loadconfig.__botserverid__ or True: 13 | role = discord.utils.get(member.server.roles, name=loadconfig.__selfassignrole__) 14 | await self.bot.add_roles(member, role) 15 | 16 | 17 | async def setup(bot): 18 | await bot.add_cog(roleAssign(bot)) 19 | -------------------------------------------------------------------------------- /cogs/help.py: -------------------------------------------------------------------------------- 1 | import discord 2 | from discord.ext import commands 3 | 4 | class HelpCommand(commands.MinimalHelpCommand): 5 | def get_command_signature(self, command): 6 | return '{0.clean_prefix}{1.qualified_name} {1.signature}'.format(self, command) 7 | 8 | class Help(commands.Cog): 9 | def __init__(self, bot): 10 | self._original_help_command = bot.help_command 11 | bot.help_command = HelpCommand() 12 | bot.help_command.cog = self 13 | 14 | async def cog_unload(self): 15 | self.bot.help_command = self._original_help_command 16 | 17 | async def setup(bot): 18 | await bot.add_cog(Help(bot)) 19 | bot.get_command('help').hidden = True 20 | -------------------------------------------------------------------------------- /config/config.example.py: -------------------------------------------------------------------------------- 1 | __token__ = 'INSERT BOT TOKEN HERE' 2 | __prefix__ = ':' #OPTIONAL Prefix for all commands, defaults to colon 3 | __timezone__ = 'Europe/Berlin' #OPTIONAL 4 | __botserverid__ = 102817255661772800 #OPTIONAL Specifies the main serverid from which the server-/modlog should be taken + some other nito features 5 | __kawaiichannel__ = 207909155556687872 #OPTIONAL specified a channel where the :kawaii commands gets this pinned messages 6 | __greetmsg__ = '{emoji} Welcome {member} on my server!' #OPTIONAL sends a greet message to new user in the botserverid system channel 7 | __leavemsg__ = ':sad: {member} left the server' #OPTIONAL sends a leave message to the botserverid system channel 8 | __selfassignrole__ = 'NSFW' #OPTIONAL allows to selfassign a role like NSFW on the BOTSERVERID -------------------------------------------------------------------------------- /config/games.py: -------------------------------------------------------------------------------- 1 | import discord 2 | 3 | __games__ = [ 4 | (discord.ActivityType.playing, 'with Eddy-Senpai'), 5 | (discord.ActivityType.playing, 'with Cats'), 6 | (discord.ActivityType.playing, 'try :help'), 7 | (discord.ActivityType.playing, 'try :about'), 8 | (discord.ActivityType.playing, 'with VS Code'), 9 | (discord.ActivityType.playing, 'with Python'), 10 | (discord.ActivityType.playing, 'with async'), 11 | (discord.ActivityType.playing, 'with Karen-chan'), 12 | (discord.ActivityType.playing, 'with Hinata-chan'), 13 | (discord.ActivityType.playing, 'with Eduard Laser'), 14 | (discord.ActivityType.watching, 'over {guilds} Server'), 15 | (discord.ActivityType.watching, 'over {members} Members'), 16 | (discord.ActivityType.watching, 'Trash Animes'), 17 | (discord.ActivityType.watching, 'you right now'), 18 | (discord.ActivityType.watching, 'Hentai'), 19 | (discord.ActivityType.listening, 'Podcasts') 20 | ] 21 | __gamesTimer__ = 2 * 60 #2 minutes 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Eduard Nikoleisen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/docker-compose.example.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | discord_bot: 5 | container_name: discord_bot 6 | image: ghcr.io/der-eddy/shinobu_bot:latest 7 | restart: always 8 | volumes: 9 | - discord_bot_data:/discord_bot/config 10 | environment: 11 | DISCORD_TOKEN: 'INSERT BOT TOKEN HERE' 12 | DISCORD_PREFIX: ':' #OPTIONAL Prefix for all commands, defaults to colon 13 | DISCORD_TIMEZONE: 'Europe/Berlin' #OPTIONAL 14 | DISCORD_BOTSERVERID: '102817255661772800' #OPTIONAL Specifies the main serverid from which the server-/modlog should be taken + some other nito features 15 | DISCORD_KAWAIICHANNEL: '207909155556687872' #OPTIONAL specified a channel where the :kawaii commands gets this pinned messages 16 | DISCORD_GREETMSG: '{emoji} Welcome {member} on my server!' #OPTIONAL sends a greet message to new user in the botserverid system channel 17 | DISCORD_LEAVEMSG: ':sad: {member} left the server' #OPTIONAL sends a leave message to the botserverid system channel 18 | DISCORD_SELFASSIGNROLE: 'NSFW' #OPTIONAL allows to selfassign a role like NSFW on the BOTSERVERID 19 | 20 | volumes: 21 | discord_bot_data: -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim AS build 2 | #Update first 3 | RUN apt-get update && apt-get upgrade -y && apt-get install git -y 4 | ADD . /build 5 | WORKDIR /build 6 | RUN pip install --upgrade pip 7 | RUN pip install -r ./requirements.txt 8 | 9 | #Multistage build with distroless image 10 | FROM gcr.io/distroless/python3-debian11:nonroot 11 | COPY --from=build --chown=nonroot:nonroot /build /discord_bot 12 | COPY --from=build /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages 13 | WORKDIR /discord_bot 14 | ENV PYTHONPATH=/usr/local/lib/python3.9/site-packages 15 | 16 | #Don't generate .pyc files, enable tracebacks on segfaults and disable STDOUT / STDERR buffering 17 | ENV LANG C.UTF-8 18 | ENV LC_ALL C.UTF-8 19 | ENV PYTHONDONTWRITEBYTECODE 1 20 | ENV PYTHONFAULTHANDLER 1 21 | ENV PYTHONUNBUFFERED 1 22 | ENV PYTHONHASHSEED 0 23 | 24 | #For https://github.com/users/der-eddy/packages/container/package/shinobu_bot 25 | LABEL org.opencontainers.image.source https://github.com/der-eddy/discord_bot 26 | 27 | #add user, don't run as root 28 | #distroless creates automatically a nonroot user with uid 65532:65532 29 | USER nonroot 30 | 31 | CMD [ "main.py", "docker"] 32 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | day: "friday" 13 | time: "13:00" 14 | labels: 15 | - "pip dependencies" 16 | assignees: 17 | - "Der-Eddy" 18 | - package-ecosystem: "docker" 19 | directory: "/" 20 | schedule: 21 | interval: "weekly" 22 | day: "friday" 23 | time: "13:00" 24 | labels: 25 | - "docker dependencies" 26 | assignees: 27 | - "Der-Eddy" 28 | # I want the specific Python version for the latest Debian stable release 29 | ignore: 30 | - dependency-name: "python" 31 | - package-ecosystem: "github-actions" 32 | directory: "/" 33 | schedule: 34 | interval: "weekly" 35 | day: "friday" 36 | time: "13:00" 37 | labels: 38 | - "github workflow dependencies" 39 | assignees: 40 | - "Der-Eddy" -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyBuilder 66 | target/ 67 | 68 | # IPython Notebook 69 | .ipynb_checkpoints 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # celery beat schedule file 75 | celerybeat-schedule 76 | 77 | # dotenv 78 | .env 79 | 80 | # virtualenv 81 | venv/ 82 | ENV/ 83 | 84 | # Spyder project settings 85 | .spyderproject 86 | 87 | # Rope project settings 88 | .ropeproject 89 | 90 | *.log 91 | *.log.* 92 | config.py 93 | temp* 94 | tmp* 95 | tempBot.txt 96 | tempAva.* 97 | *.db 98 | debug.html 99 | .vscode/ 100 | .directory 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask instance folder 57 | instance/ 58 | 59 | # Scrapy stuff: 60 | .scrapy 61 | 62 | # Sphinx documentation 63 | docs/_build/ 64 | 65 | # PyBuilder 66 | target/ 67 | 68 | # IPython Notebook 69 | .ipynb_checkpoints 70 | 71 | # pyenv 72 | .python-version 73 | 74 | # celery beat schedule file 75 | celerybeat-schedule 76 | 77 | # dotenv 78 | .env 79 | 80 | # virtualenv 81 | venv/ 82 | ENV/ 83 | 84 | # Spyder project settings 85 | .spyderproject 86 | 87 | # Rope project settings 88 | .ropeproject 89 | 90 | *.log 91 | *.log.* 92 | config.py 93 | temp* 94 | tmp* 95 | tempBot.txt 96 | tempAva.* 97 | *.db 98 | debug.html 99 | .vscode/ 100 | docker-compose.yml 101 | .directory 102 | -------------------------------------------------------------------------------- /loadconfig.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | configFile = os.path.join('.', 'config', 'config.py') 4 | 5 | if os.path.isfile(configFile): 6 | try: 7 | from config.config import __token__ 8 | except ImportError: 9 | raise Exception('__token__ variable MUST be set ') 10 | try: 11 | from config.config import __prefix__ 12 | except ImportError: 13 | __prefix__ = ':' 14 | try: 15 | from config.config import __botserverid__ 16 | except ImportError: 17 | __botserverid__ = 0 18 | try: 19 | from config.config import __greetmsg__ 20 | except ImportError: 21 | __greetmsg__ = '' 22 | try: 23 | from config.config import __leavemsg__ 24 | except ImportError: 25 | __leavemsg__ = '' 26 | try: 27 | from config.config import __kawaiichannel__ 28 | except ImportError: 29 | __kawaiichannel__ = 0 30 | try: 31 | from config.config import __timezone__ 32 | except ImportError: 33 | __timezone__ = 0 34 | try: 35 | from config.config import __selfassignrole__ 36 | except ImportError: 37 | __selfassignrole__ = 'NSFW' 38 | else: 39 | #Fallback for Heroku or Docker environments 40 | __token__ = os.environ.get('DISCORD_TOKEN') 41 | if __token__ == '': 42 | raise Exception('DISCORD_TOKEN environment variable MUST be set ') 43 | __prefix__ = os.environ.get('DISCORD_PREFIX', ':') 44 | __botserverid__ = int(os.environ.get('DISCORD_BOTSERVERID', 0)) 45 | __kawaiichannel__ = int(os.environ.get('DISCORD_KAWAIICHANNEL', 0)) 46 | __greetmsg__ = os.environ.get('DISCORD_GREETMSG', '') 47 | __leavemsg__ = os.environ.get('DISCORD_LEAVEMSG', '') 48 | __timezone__ = os.environ.get('DISCORD_TIMEZONE', 'Europe/London') 49 | __selfassignrole__ = os.environ.get('DISCORD_SELFASSIGNROLE', 'NSFW') 50 | 51 | 52 | from config.games import __games__, __gamesTimer__ 53 | from config.cogs import __cogs__ 54 | from config.blacklist import __blacklist__ 55 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | # Publish `master` as Docker `latest` image. 6 | branches: 7 | - master 8 | 9 | # Publish `v1.2.3` tags as releases. 10 | tags: 11 | - '*' 12 | 13 | # Run tests for any PRs. 14 | pull_request: 15 | 16 | jobs: 17 | # Run tests. 18 | # See also https://docs.docker.com/docker-hub/builds/automated-testing/ 19 | test: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v5 24 | 25 | - name: Run tests 26 | run: | 27 | if [ -f docker-compose.test.yml ]; then 28 | docker-compose --file docker-compose.test.yml build 29 | docker-compose --file docker-compose.test.yml run sut 30 | else 31 | docker build . --file Dockerfile 32 | fi 33 | 34 | push: 35 | needs: test 36 | runs-on: ubuntu-latest 37 | if: github.event_name == 'push' 38 | steps: 39 | - 40 | name: Checkout 41 | uses: actions/checkout@v5 42 | - 43 | name: Docker meta 44 | id: docker_meta 45 | uses: crazy-max/ghaction-docker-meta@v5.8.0 46 | with: 47 | images: ghcr.io/der-eddy/shinobu_bot 48 | tag-semver: | 49 | {{version}} 50 | {{major}}.{{minor}} 51 | - 52 | name: Set up QEMU 53 | uses: docker/setup-qemu-action@v3 54 | - 55 | name: Set up Docker Buildx 56 | uses: docker/setup-buildx-action@v3 57 | - 58 | name: Cache Docker layers 59 | uses: actions/cache@v4.3.0 60 | with: 61 | path: /tmp/.buildx-cache 62 | key: ${{ runner.os }}-buildx-${{ github.sha }} 63 | restore-keys: | 64 | ${{ runner.os }}-buildx- 65 | - 66 | name: Login to GitHub Container Registry 67 | if: github.event_name != 'pull_request' 68 | uses: docker/login-action@v3 69 | with: 70 | registry: ghcr.io 71 | username: ${{ github.repository_owner }} 72 | password: ${{ secrets.CR_PAT }} 73 | - 74 | name: Build and push 75 | uses: docker/build-push-action@v6 76 | with: 77 | context: . 78 | file: ./Dockerfile 79 | platforms: linux/amd64,linux/arm64 80 | push: ${{ github.event_name != 'pull_request' }} 81 | tags: ${{ steps.docker_meta.outputs.tags }} 82 | labels: ${{ steps.docker_meta.outputs.labels }} 83 | cache-from: type=local,src=/tmp/.buildx-cache 84 | cache-to: type=local,dest=/tmp/.buildx-cache -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.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: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '34 6 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'python' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v5 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v4 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v4 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v4 68 | -------------------------------------------------------------------------------- /cogs/steam.py: -------------------------------------------------------------------------------- 1 | import re 2 | import asyncio 3 | import aiohttp 4 | import discord 5 | from discord.ext import commands 6 | 7 | class steam(commands.Cog): 8 | '''Steam spezifische Commands''' 9 | 10 | def __init__(self, bot): 11 | self.bot = bot 12 | self.steamSpyAPIURL = 'https://steamspy.com/api.php' 13 | self.steamCurrentPlayerAPIURL = 'http://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v0001/?appid=' 14 | self.steamMarketAPIURL = 'http://store.steampowered.com/api/appdetails/?appids=' 15 | 16 | @commands.command(pass_context=True) 17 | @commands.cooldown(1, 2, commands.cooldowns.BucketType.server) 18 | async def steam(self, ctx, appid: str): 19 | '''Gibt Informationen zu einem Spiel bei Steam aus 20 | 21 | Beispiel: 22 | ----------- 23 | 24 | :steam 570 25 | ''' 26 | await self.bot.send_typing(ctx.message.channel) 27 | steamSpyURL = f'{self.steamSpyAPIURL}?request=appdetails&appid={appid}' 28 | async with aiohttp.get(steamSpyURL) as r: 29 | spyJSON = await r.json() 30 | 31 | steamCurrentPlayerURL = f'{self.steamCurrentPlayerAPIURL}{appid}' 32 | async with aiohttp.get(steamCurrentPlayerURL) as r: 33 | currentPlayerJSON = await r.json() 34 | 35 | steamMarketURL = f'{self.steamMarketAPIURL}{appid}' 36 | async with aiohttp.get(steamMarketURL) as r: 37 | marketJSON = await r.json() 38 | 39 | owner = '{:,}'.format(spyJSON['owners']).replace(',', '.') 40 | owner += ' ± ' 41 | owner += '{:,}'.format(spyJSON['owners_variance']).replace(',', '.') 42 | 43 | try: 44 | price = str(int(marketJSON[appid]['data']['price_overview']['final']) / 100) + '€' 45 | if marketJSON[appid]['data']['price_overview']['discount_percent'] != 0: 46 | price += ' (-{}%)'.format(marketJSON[appid]['data']['price_overview']['discount_percent']) 47 | except KeyError: 48 | price = 'Free' 49 | 50 | embed = discord.Embed(color=0x2F668D, title=spyJSON['name'], url=f'http://store.steampowered.com/app/{appid}') 51 | embed.set_footer(text='AppID: {}'.format(spyJSON['appid'])) 52 | embed.set_thumbnail(url=f'http://cdn.akamai.steamstatic.com/steam/apps/{appid}/header.jpg') 53 | embed.add_field(name='Name', value=spyJSON['name'], inline=True) 54 | embed.add_field(name='Score Rank', value=spyJSON['score_rank'], inline=True) 55 | embed.add_field(name='Preis', value=price, inline=True) 56 | embed.add_field(name='Besitzer', value=owner, inline=True) 57 | embed.add_field(name='Derzeitige Spieler', value=currentPlayerJSON['response']['player_count'], inline=True) 58 | embed.add_field(name='Durchschnittliche Spieler gestern', value=spyJSON['ccu'], inline=True) 59 | embed.add_field(name='Entwickler', value=spyJSON['developer'], inline=True) 60 | embed.add_field(name='Publisher', value=spyJSON['publisher'], inline=True) 61 | if marketJSON[appid]['data']['short_description'] != '' and marketJSON[appid]['data']['short_description'] != None: 62 | discription = re.sub(re.compile('<.*?>'), '', marketJSON[appid]['data']['short_description']) 63 | embed.add_field(name='Beschreibung', value=discription, inline=False) 64 | embed.add_field(name='Link', value=f'http://store.steampowered.com/app/{appid}', inline=False) 65 | 66 | await self.bot.say(embed=embed) 67 | 68 | 69 | @steam.error 70 | async def steam_error(self, error, ctx): 71 | if isinstance(error, commands.errors.CommandOnCooldown): 72 | seconds = str(error)[34:] 73 | await self.bot.say(f':alarm_clock: Cooldown! Versuche es in {seconds} erneut') 74 | if isinstance(error, discord.ext.commands.errors.CommandInvokeError): 75 | await self.bot.say(':x: Konnte keine Verbindung zum Steam API aufbauen') 76 | 77 | async def setup(bot): 78 | await bot.add_cog(steam(bot)) 79 | -------------------------------------------------------------------------------- /cogs/gwent.py: -------------------------------------------------------------------------------- 1 | import os 2 | import asyncio 3 | import aiohttp 4 | import discord 5 | from discord.ext import commands 6 | 7 | class gwent(commands.Cog): 8 | '''Gwent: The Witcher 3 Card Game spezifische Commands''' 9 | 10 | def __init__(self, bot): 11 | self.bot = bot 12 | self.APIURL = 'https://api.gwentapi.com/v0/cards?name=' 13 | 14 | @commands.command(pass_context=True, aliases=['gwint', 'gwentcard']) 15 | @commands.cooldown(1, 2, commands.cooldowns.BucketType.server) 16 | async def gwent(self, ctx, *card: str): 17 | '''Gibt Informationen zu einer Gwent Karte aus 18 | 19 | Beispiel: 20 | ----------- 21 | 22 | :gwent Geralt 23 | ''' 24 | cardName = ' '.join(card) 25 | url = f'{self.APIURL}{cardName}' 26 | await self.bot.send_typing(ctx.message.channel) 27 | async with aiohttp.get(url) as firstResult: 28 | if firstResult.status == 200: 29 | json = await firstResult.json() 30 | async with aiohttp.get(json['results'][0]['href']) as r: 31 | json = await r.json() 32 | async with aiohttp.get(json['variations'][0]['href']) as image: 33 | imagejson = await r.json() 34 | image = imagejson['art']['thumbnailImage'] 35 | tempGwentCard = 'tmp\\tempGwent.png' 36 | async with aiohttp.get(image) as img: 37 | with open(tempGwentCard, 'wb') as f: 38 | f.write(await img.read()) 39 | with open(tempGwentCard, 'rb') as f: 40 | await self.bot.send_file(ctx.message.channel, f) 41 | os.remove(tempGwentCard) 42 | 43 | rarity = json['variations'][0]['rarity']['href'] 44 | if rarity == 'https://api.gwentapi.com/v0/rarities/u0zNKy4EULa_VU4JD5r4EA': 45 | color = 0xf1c40f #Legendary 46 | elif rarity == 'https://api.gwentapi.com/v0/rarities/-naHV1zlVuCFll-j-7T1ow': 47 | color = 0x5D92B1 #Rare 48 | elif rarity == 'https://api.gwentapi.com/v0/rarities/V_ImiYfTVhG_WaAOof9Rxg': 49 | color = 0x7D61BB #Epic 50 | else: 51 | color = 0xc0c0c0 #Common 52 | embed = discord.Embed(color=color) 53 | embed.set_footer(text='UUID: ' + json['uuid']) 54 | embed.set_thumbnail(url='https://i.imgur.com/WNYBsYp.png') 55 | embed.add_field(name='Name', value=json['name'], inline=True) 56 | if json['strength'] != '' and json['strength'] != None: 57 | embed.add_field(name='Stärke', value=json['strength'], inline=True) 58 | if json['group'] != '' and json['group'] != None: 59 | embed.add_field(name='Typ', value=json['group'], inline=True) 60 | if rarity != '' and rarity != None: 61 | embed.add_field(name='Seltenheit', value=rarity, inline=True) 62 | if json['faction'] != '' and json['faction'] != None: 63 | embed.add_field(name='Fraktion', value=json['faction']['name'], inline=True) 64 | if json['positions'] != '' and json['positions'] != None: 65 | embed.add_field(name='Reihe', value=', '.join(json['positions']), inline=True) 66 | embed.add_field(name='Text', value=json['info'], inline=False) 67 | await self.bot.say(embed=embed) 68 | else: 69 | msg = f':no_entry: Ich konnte keine Gwent Karte zu **{cardName}** finden :sweat:' 70 | await self.bot.say(msg) 71 | 72 | @gwent.error 73 | async def gwent_error(self, error, ctx): 74 | if isinstance(error, commands.errors.CommandOnCooldown): 75 | seconds = str(error)[34:] 76 | await self.bot.say(f':alarm_clock: Cooldown! Versuche es in {seconds} erneut') 77 | 78 | async def setup(bot): 79 | await bot.add_cog(gwent(bot)) 80 | -------------------------------------------------------------------------------- /font/LICENCE.txt: -------------------------------------------------------------------------------- 1 | ------------------------------- 2 | UBUNTU FONT LICENCE Version 1.0 3 | ------------------------------- 4 | 5 | PREAMBLE 6 | This licence allows the licensed fonts to be used, studied, modified and 7 | redistributed freely. The fonts, including any derivative works, can be 8 | bundled, embedded, and redistributed provided the terms of this licence 9 | are met. The fonts and derivatives, however, cannot be released under 10 | any other licence. The requirement for fonts to remain under this 11 | licence does not require any document created using the fonts or their 12 | derivatives to be published under this licence, as long as the primary 13 | purpose of the document is not to be a vehicle for the distribution of 14 | the fonts. 15 | 16 | DEFINITIONS 17 | "Font Software" refers to the set of files released by the Copyright 18 | Holder(s) under this licence and clearly marked as such. This may 19 | include source files, build scripts and documentation. 20 | 21 | "Original Version" refers to the collection of Font Software components 22 | as received under this licence. 23 | 24 | "Modified Version" refers to any derivative made by adding to, deleting, 25 | or substituting -- in part or in whole -- any of the components of the 26 | Original Version, by changing formats or by porting the Font Software to 27 | a new environment. 28 | 29 | "Copyright Holder(s)" refers to all individuals and companies who have a 30 | copyright ownership of the Font Software. 31 | 32 | "Substantially Changed" refers to Modified Versions which can be easily 33 | identified as dissimilar to the Font Software by users of the Font 34 | Software comparing the Original Version with the Modified Version. 35 | 36 | To "Propagate" a work means to do anything with it that, without 37 | permission, would make you directly or secondarily liable for 38 | infringement under applicable copyright law, except executing it on a 39 | computer or modifying a private copy. Propagation includes copying, 40 | distribution (with or without modification and with or without charging 41 | a redistribution fee), making available to the public, and in some 42 | countries other activities as well. 43 | 44 | PERMISSION & CONDITIONS 45 | This licence does not grant any rights under trademark law and all such 46 | rights are reserved. 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining a 49 | copy of the Font Software, to propagate the Font Software, subject to 50 | the below conditions: 51 | 52 | 1) Each copy of the Font Software must contain the above copyright 53 | notice and this licence. These can be included either as stand-alone 54 | text files, human-readable headers or in the appropriate machine- 55 | readable metadata fields within text or binary files as long as those 56 | fields can be easily viewed by the user. 57 | 58 | 2) The font name complies with the following: 59 | (a) The Original Version must retain its name, unmodified. 60 | (b) Modified Versions which are Substantially Changed must be renamed to 61 | avoid use of the name of the Original Version or similar names entirely. 62 | (c) Modified Versions which are not Substantially Changed must be 63 | renamed to both (i) retain the name of the Original Version and (ii) add 64 | additional naming elements to distinguish the Modified Version from the 65 | Original Version. The name of such Modified Versions must be the name of 66 | the Original Version, with "derivative X" where X represents the name of 67 | the new work, appended to that name. 68 | 69 | 3) The name(s) of the Copyright Holder(s) and any contributor to the 70 | Font Software shall not be used to promote, endorse or advertise any 71 | Modified Version, except (i) as required by this licence, (ii) to 72 | acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with 73 | their explicit written permission. 74 | 75 | 4) The Font Software, modified or unmodified, in part or in whole, must 76 | be distributed entirely under this licence, and must not be distributed 77 | under any other licence. The requirement for fonts to remain under this 78 | licence does not affect any document created using the Font Software, 79 | except any version of the Font Software extracted from a document 80 | created using the Font Software may only be distributed under this 81 | licence. 82 | 83 | TERMINATION 84 | This licence becomes null and void if any of the above conditions are 85 | not met. 86 | 87 | DISCLAIMER 88 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 89 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 90 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF 91 | COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 92 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 93 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 94 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 95 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER 96 | DEALINGS IN THE FONT SOFTWARE. 97 | -------------------------------------------------------------------------------- /cogs/mod.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import asyncio 3 | import aiohttp 4 | import discord 5 | from discord.ext import commands 6 | from pytz import timezone 7 | import loadconfig 8 | 9 | class mod(commands.Cog): 10 | '''Praktische Befehle für Administratoren und Moderatoren''' 11 | 12 | def __init__(self, bot): 13 | self.bot = bot 14 | 15 | def _currenttime(self): 16 | return datetime.datetime.now(timezone(loadconfig.__timezone__)).strftime("%H:%M:%S") 17 | 18 | @commands.command(aliases=['prune']) 19 | @commands.has_permissions(ban_members = True) 20 | @commands.bot_has_permissions(manage_messages = True) 21 | async def purge(self, ctx, *limit): 22 | '''Löscht mehere Nachrichten auf einmal (MOD ONLY) 23 | 24 | Beispiel: 25 | ----------- 26 | 27 | :purge 100 28 | ''' 29 | try: 30 | limit = int(limit[0]) 31 | except IndexError: 32 | limit = 1 33 | deleted = 0 34 | while limit >= 1: 35 | cap = min(limit, 100) 36 | deleted += len(await ctx.channel.purge(limit=cap, before=ctx.message)) 37 | limit -= cap 38 | tmp = await ctx.send(f'**:put_litter_in_its_place:** {deleted} Nachrichten gelöscht') 39 | await asyncio.sleep(15) 40 | await tmp.delete() 41 | await ctx.message.delete() 42 | 43 | @commands.command() 44 | @commands.has_permissions(kick_members = True) 45 | @commands.bot_has_permissions(kick_members = True) 46 | async def kick(self, ctx, member: discord.Member = None, *reason): 47 | '''Kickt ein Mitglied mit einer Begründung (MOD ONLY) 48 | 49 | Beispiel: 50 | ----------- 51 | 52 | :kick @Der-Eddy#6508 53 | ''' 54 | if member is not None: 55 | if reason: 56 | reason = ' '.join(reason) 57 | else: 58 | reason = None 59 | await member.kick(reason=reason) 60 | else: 61 | await ctx.send('**:no_entry:** Kein Benutzer angegeben!') 62 | 63 | @commands.command() 64 | @commands.has_permissions(ban_members = True) 65 | @commands.bot_has_permissions(ban_members = True) 66 | async def ban(self, ctx, member: discord.Member=None, *reason): 67 | '''Bannt ein Mitglied mit einer Begründung (MOD ONLY) 68 | 69 | Beispiel: 70 | ----------- 71 | 72 | :ban @Der-Eddy#6508 73 | ''' 74 | if member is not None: 75 | if reason: 76 | reason = ' '.join(reason) 77 | else: 78 | reason = None 79 | await member.ban(reason=reason) 80 | else: 81 | await ctx.send('**:no_entry:** Kein Benutzer angegeben!') 82 | 83 | @commands.command() 84 | @commands.has_permissions(ban_members = True) 85 | @commands.bot_has_permissions(ban_members = True) 86 | async def unban(self, ctx, user: int=None, *reason): 87 | '''Entbannt ein Mitglied mit einer Begründung (MOD ONLY) 88 | Es muss die Benutzer-ID angegeben werden, Name + Discriminator reicht nicht 89 | 90 | Beispiel: 91 | ----------- 92 | 93 | :unban 102815825781596160 94 | ''' 95 | user = discord.User(id=user) 96 | if user is not None: 97 | if reason: 98 | reason = ' '.join(reason) 99 | else: 100 | reason = None 101 | await ctx.guild.unban(user, reason=reason) 102 | else: 103 | await ctx.send('**:no_entry:** Kein Benutzer angegeben!') 104 | 105 | @commands.command() 106 | @commands.has_permissions(kick_members = True) 107 | @commands.bot_has_permissions(ban_members = True) 108 | async def bans(self, ctx): 109 | '''Listet aktuell gebannte User auf (MOD ONLY)''' 110 | users = await ctx.guild.bans() 111 | if len(users) > 0: 112 | msg = f'`{"ID":21}{"Name":25} Begründung\n' 113 | for entry in users: 114 | userID = entry.user.id 115 | userName = str(entry.user) 116 | if entry.user.bot: 117 | username = '🤖' + userName #:robot: emoji 118 | reason = str(entry.reason) #Could be None 119 | msg += f'{userID:<21}{userName:25} {reason}\n' 120 | embed = discord.Embed(color=0xe74c3c) #Red 121 | embed.set_thumbnail(url=ctx.guild.icon_url) 122 | embed.set_footer(text=f'Server: {ctx.guild.name}') 123 | embed.add_field(name='Ranks', value=msg + '`', inline=True) 124 | await ctx.send(embed=embed) 125 | else: 126 | await ctx.send('**:negative_squared_cross_mark:** Es gibt keine gebannten Nutzer!') 127 | 128 | @commands.command(alias=['clearreactions']) 129 | @commands.has_permissions(manage_messages = True) 130 | @commands.bot_has_permissions(manage_messages = True) 131 | async def removereactions(self, ctx, messageid : str): 132 | '''Entfernt alle Emoji Reactions von einer Nachricht (MOD ONLY) 133 | 134 | Beispiel: 135 | ----------- 136 | 137 | :removereactions 247386709505867776 138 | ''' 139 | message = await ctx.channel.get_message(messageid) 140 | if message: 141 | await message.clear_reactions() 142 | else: 143 | await ctx.send('**:x:** Konnte keine Nachricht mit dieser ID finden!') 144 | 145 | @commands.command() 146 | async def permissions(self, ctx): 147 | '''Listet alle Rechte des Bots auf''' 148 | permissions = ctx.channel.permissions_for(ctx.me) 149 | 150 | embed = discord.Embed(title=':customs: Permissions', color=0x3498db) #Blue 151 | embed.add_field(name='Server', value=ctx.guild) 152 | embed.add_field(name='Channel', value=ctx.channel, inline=False) 153 | 154 | for item, valueBool in permissions: 155 | if valueBool == True: 156 | value = ':white_check_mark:' 157 | else: 158 | value = ':x:' 159 | embed.add_field(name=item, value=value) 160 | 161 | embed.timestamp = datetime.datetime.utcnow() 162 | await ctx.send(embed=embed) 163 | 164 | @commands.command() 165 | async def hierarchy(self, ctx): 166 | '''Listet die Rollen-Hierarchie des derzeitigen Servers auf''' 167 | msg = f'Rollen-Hierarchie für Server **{ctx.guild}**:\n\n' 168 | roleDict = {} 169 | 170 | for role in ctx.guild.roles: 171 | if role.is_default(): 172 | roleDict[role.position] = 'everyone' 173 | else: 174 | roleDict[role.position] = role.name 175 | 176 | for role in sorted(roleDict.items(), reverse=True): 177 | msg += role[1] + '\n' 178 | await ctx.send(msg) 179 | 180 | @commands.command(alies=['setrole', 'sr']) 181 | @commands.has_permissions(manage_roles = True) 182 | @commands.bot_has_permissions(manage_roles = True) 183 | async def setrank(self, ctx, member: discord.Member=None, *rankName: str): 184 | '''Vergibt einen Rang an einem Benutzer 185 | 186 | Beispiel: 187 | ----------- 188 | 189 | :setrole @Der-Eddy#6508 Member 190 | ''' 191 | rank = discord.utils.get(ctx.guild.roles, name=' '.join(rankName)) 192 | if member is not None: 193 | await member.add_roles(rank) 194 | await ctx.send(f':white_check_mark: Rolle **{rank.name}** wurde an **{member.name}** verteilt') 195 | else: 196 | await ctx.send(':no_entry: Du musst einen Benutzer angeben!') 197 | 198 | @commands.command(pass_context=True, alies=['rmrole', 'removerole', 'removerank']) 199 | @commands.has_permissions(manage_roles = True) 200 | @commands.bot_has_permissions(manage_roles = True) 201 | async def rmrank(self, ctx, member: discord.Member=None, *rankName: str): 202 | '''Entfernt einen Rang von einem Benutzer 203 | 204 | Beispiel: 205 | ----------- 206 | 207 | :rmrole @Der-Eddy#6508 Member 208 | ''' 209 | rank = discord.utils.get(ctx.guild.roles, name=' '.join(rankName)) 210 | if member is not None: 211 | await member.remove_roles(rank) 212 | await ctx.send(f':white_check_mark: Rolle **{rank.name}** wurde von **{member.name}** entfernt') 213 | else: 214 | await ctx.send(':no_entry: Du musst einen Benutzer angeben!') 215 | 216 | 217 | async def setup(bot): 218 | await bot.add_cog(mod(bot)) 219 | -------------------------------------------------------------------------------- /cogs/admin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import random 4 | import discord 5 | import asyncio 6 | import aiohttp 7 | from discord.ext import commands 8 | import loadconfig 9 | 10 | class admin(commands.Cog): 11 | '''Befehle für den Bot Admin''' 12 | 13 | def __init__(self, bot): 14 | self.bot = bot 15 | 16 | async def cog_command_error(self, ctx, error): 17 | print('Error in {0.command.qualified_name}: {1}'.format(ctx, error)) 18 | 19 | async def cog_check(self, ctx): 20 | return await ctx.bot.is_owner(ctx.author) 21 | 22 | @commands.command(aliases=['quit'], hidden=True) 23 | async def shutdown(self, ctx): 24 | '''Schaltet mich ab :( (BOT OWNER ONLY)''' 25 | await ctx.send('**:ok:** Bye!') 26 | #self.bot.gamesLoop.cancel() 27 | await self.bot.close() 28 | self.bot.clear() 29 | sys.exit(0) 30 | 31 | @commands.command(hidden=True) 32 | async def restart(self, ctx): 33 | '''Startet mich neu (BOT OWNER ONLY)''' 34 | await ctx.send('**:ok:** Bis gleich!') 35 | await self.bot.close() 36 | self.bot.clear() 37 | sys.exit(6) 38 | 39 | @commands.command(hidden=True) 40 | async def avatar(self, ctx, url: str): 41 | '''Setzt einen neuen Avatar (BOT OWNER ONLY)''' 42 | tempAvaFile = 'tempAva.png' 43 | async with aiohttp.get(''.join(url)) as img: 44 | with open(tempAvaFile, 'wb') as f: 45 | f.write(await img.read()) 46 | f = discord.File(tempAvaFile) 47 | await self.bot.edit_profile(avatar=f.read()) 48 | os.remove(tempAvaFile) 49 | asyncio.sleep(2) 50 | await ctx.send('**:ok:** Mein neuer Avatar!\n %s' % self.bot.user.avatar.url) 51 | 52 | @commands.command(hidden=True, aliases=['game']) 53 | async def changegame(self, ctx, gameType: str, *, gameName: str): 54 | '''Ändert das derzeit spielende Spiel (BOT OWNER ONLY)''' 55 | gameType = gameType.lower() 56 | if gameType == 'playing': 57 | activityType = discord.ActivityType.playing 58 | elif gameType == 'watching': 59 | activityType = discord.ActivityType.watching 60 | elif gameType == 'listening': 61 | activityType = discord.ActivityType.listening 62 | elif gameType == 'streaming': 63 | activityType = discord.ActivityType.streaming 64 | guildsCount = len(self.bot.guilds) 65 | memberCount = len(list(self.bot.get_all_members())) 66 | gameName = gameName.format(guilds = guildsCount, members = memberCount) 67 | await self.bot.change_presence(activity=discord.Activity(type=activityType, name=gameName)) 68 | await ctx.send(f'**:ok:** Ändere das Spiel zu: {gameType} **{gameName}**') 69 | 70 | @commands.command(hidden=True) 71 | async def changestatus(self, ctx, status: str): 72 | '''Ändert den Online Status vom Bot (BOT OWNER ONLY)''' 73 | status = status.lower() 74 | if status == 'offline' or status == 'off' or status == 'invisible': 75 | discordStatus = discord.Status.invisible 76 | elif status == 'idle': 77 | discordStatus = discord.Status.idle 78 | elif status == 'dnd' or status == 'disturb': 79 | discordStatus = discord.Status.dnd 80 | else: 81 | discordStatus = discord.Status.online 82 | await self.bot.change_presence(status=discordStatus) 83 | await ctx.send(f'**:ok:** Ändere Status zu: **{discordStatus}**') 84 | 85 | @commands.command(hidden=True) 86 | async def name(self, ctx, name: str): 87 | '''Ändert den globalen Namen vom Bot (BOT OWNER ONLY)''' 88 | await self.bot.edit_profile(username=name) 89 | msg = f':ok: Ändere meinen Namen zu: **{name}**' 90 | await ctx.send(msg) 91 | 92 | @commands.command(hidden=True, aliases=['guilds']) 93 | async def servers(self, ctx): 94 | '''Listet die aktuellen verbundenen Guilds auf (BOT OWNER ONLY)''' 95 | msg = '```js\n' 96 | msg += '{!s:19s} | {!s:>5s} | {} | {}\n'.format('ID', 'Member', 'Name', 'Owner') 97 | for guild in self.bot.guilds: 98 | msg += '{!s:19s} | {!s:>5s}| {} | {}\n'.format(guild.id, guild.member_count, guild.name, guild.owner) 99 | msg += '```' 100 | await ctx.send(msg) 101 | 102 | @commands.command(hidden=True) 103 | async def leaveserver(self, ctx, guildid: str): 104 | '''Tritt aus einem Server aus (BOT OWNER ONLY) 105 | 106 | Beispiel: 107 | ----------- 108 | 109 | :leaveserver 102817255661772800 110 | ''' 111 | if guildid == 'this': 112 | await ctx.guild.leave() 113 | return 114 | else: 115 | guild = self.bot.get_guild(guildid) 116 | if guild: 117 | await guild.leave() 118 | msg = f':ok: Austritt aus {guild.name} erfolgreich!' 119 | else: 120 | msg = ':x: Konnte keine passende Guild zu dieser ID finden!' 121 | await ctx.send(msg) 122 | 123 | @commands.command(hidden=True) 124 | async def echo(self, ctx, channel: str, *message: str): 125 | '''Gibt eine Nachricht als Bot auf einem bestimmten Channel aus (BOT OWNER ONLY)''' 126 | ch = self.bot.get_channel(int(channel)) 127 | msg = ' '.join(message) 128 | await ch.send(msg) 129 | await ctx.message.delete() 130 | 131 | @commands.command(hidden=True) 132 | async def discriminator(self, ctx, disc: str): 133 | '''Gibt Benutzer mit dem jeweiligen Discriminator zurück''' 134 | 135 | discriminator = disc 136 | memberList = '' 137 | 138 | for guild in self.bot.guilds: 139 | for member in guild.members: 140 | if member.discriminator == discriminator and member.discriminator not in memberList: 141 | memberList += f'{member}\n' 142 | 143 | if memberList: 144 | await ctx.send(memberList) 145 | else: 146 | await ctx.send(':x: Konnte niemanden finden') 147 | 148 | @commands.command(hidden=True) 149 | @commands.bot_has_permissions(manage_nicknames = True) 150 | async def nickname(self, ctx, *name): 151 | '''Ändert den Server Nickname vom Bot (BOT OWNER ONLY)''' 152 | nickname = ' '.join(name) 153 | await ctx.me.edit(nick=nickname) 154 | if nickname: 155 | msg = f':ok: Ändere meinen Server Nickname zu: **{nickname}**' 156 | else: 157 | msg = f':ok: Reset von meinem Server Nickname auf: **{ctx.me.name}**' 158 | await ctx.send(msg) 159 | 160 | @commands.command(hidden=True) 161 | @commands.bot_has_permissions(manage_nicknames = True) 162 | async def setnickname(self, ctx, member: discord.Member=None, *name): 163 | '''Ändert den Nickname eines Benutzer (BOT OWNER ONLY)''' 164 | if member == None: 165 | member = ctx.author 166 | nickname = ' '.join(name) 167 | await member.edit(nick=nickname) 168 | if nickname: 169 | msg = f':ok: Ändere Nickname von {member} zu: **{nickname}**' 170 | else: 171 | msg = f':ok: Reset von Nickname für {member} auf: **{member.name}**' 172 | await ctx.send(msg) 173 | 174 | @commands.command(hidden=True) 175 | async def geninvite(self, ctx, serverid: str): 176 | '''Generiert einen Invite für eine Guild wenn möglich (BOT OWNER ONLY)''' 177 | guild = self.bot.get_guild(int(serverid)) 178 | invite = await self.bot.create_invite(guild, max_uses=1, unique=False) 179 | msg = f'Invite für **{guild.name}** ({guild.id})\n{invite.url}' 180 | await ctx.author.send(msg) 181 | 182 | @commands.command(hidden=True, aliases=['wichteln']) 183 | async def wichtel(self, ctx, *participants: str): 184 | '''Nützlich für das Community Wichtel Event 2018 (BOT OWNER ONLY)''' 185 | participantsList = list(participants) 186 | random.shuffle(participantsList) 187 | msg = 'Wichtelpartner stehen fest:\n```' 188 | for i, val in enumerate(participantsList): 189 | if i == len(participantsList) - 1: 190 | msg += f'{val.ljust(10)} ===> {participantsList[0]}\n' 191 | else: 192 | msg += f'{val.ljust(10)} ===> {participantsList[i + 1]}\n' 193 | 194 | msg += '```' 195 | await ctx.send(msg) 196 | 197 | @commands.command(hidden=True) 198 | @commands.cooldown(1, 10, commands.cooldowns.BucketType.channel) 199 | async def test(self, ctx): 200 | '''Test Test Test''' 201 | await ctx.send('Test') 202 | await self.bot.AppInfo.owner.send('Test') 203 | await ctx.send(self.bot.cogs) 204 | 205 | async def setup(bot): 206 | await bot.add_cog(admin(bot)) 207 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import random 3 | import sqlite3 4 | import traceback 5 | import time 6 | import datetime 7 | import sys 8 | import os 9 | import hashlib 10 | import asyncio 11 | import aiohttp 12 | from collections import Counter 13 | from pytz import timezone 14 | import discord 15 | from discord.ext import commands 16 | import loadconfig 17 | 18 | __version__ = '1.6.5' 19 | description = '''Der-Eddys anime discord bot, developed with discord.py\n 20 | A full list of all commands are available here: ''' 21 | 22 | log = logging.getLogger('discord') 23 | logging.basicConfig(level=os.environ.get('LOGLEVEL', 'INFO')) 24 | 25 | def _currenttime(): 26 | return datetime.datetime.now(timezone(loadconfig.__timezone__)).strftime('%H:%M:%S') 27 | 28 | class ShinobuBot(commands.AutoShardedBot): 29 | def __init__(self, docker): 30 | intents = discord.Intents.default() 31 | intents.presences = True 32 | intents.members = True 33 | intents.message_content = True 34 | self.docker = docker 35 | super().__init__(command_prefix=loadconfig.__prefix__, description=description, intents=intents) 36 | 37 | async def _randomGame(self): 38 | #Check games.py to change the list of "games" to be played 39 | while True: 40 | guildCount = len(self.guilds) 41 | memberCount = len(list(self.get_all_members())) 42 | randomGame = random.choice(loadconfig.__games__) 43 | await self.change_presence(activity=discord.Activity(type=randomGame[0], name=randomGame[1].format(guilds = guildCount, members = memberCount))) 44 | await asyncio.sleep(loadconfig.__gamesTimer__) 45 | 46 | async def on_ready(self): 47 | if self.user.id == 701915238488080457: 48 | self.dev = True 49 | else: 50 | self.dev = False 51 | 52 | log.info('Logged in as') 53 | log.info(f'Bot-Name: {self.user.name} | ID: {self.user.id}') 54 | log.info(f'Dev Mode: {self.dev} | Docker: {self.docker}') 55 | log.info(f'Discord Version: {discord.__version__}') 56 | log.info(f'Bot Version: {__version__}') 57 | self.AppInfo = await self.application_info() 58 | log.info(f'Owner: {self.AppInfo.owner}') 59 | log.info('------') 60 | for cog in loadconfig.__cogs__: 61 | try: 62 | await self.load_extension(cog) 63 | except: 64 | log.warning(f'Couldn\'t load cog {cog}') 65 | self.commands_used = Counter() 66 | self.startTime = time.time() 67 | self.botVersion = __version__ 68 | self.userAgentHeaders = {'User-Agent': f'linux:shinobu_discordbot:v{__version__} (by Der-Eddy)'} 69 | self.gamesLoop = asyncio.ensure_future(self._randomGame()) 70 | 71 | async def on_command(self, ctx): 72 | self.commands_used[ctx.command.name] += 1 73 | msg = ctx.message 74 | # if isinstance(msg.channel, discord.Channel): 75 | # #dest = f'#{msg.channel.name} ({msg.guild.name})' 76 | # dest = f'#{msg.channel.name}' 77 | # elif isinstance(msg.channel, discord.DMChannel): 78 | # dest = 'Direct Message' 79 | # elif isinstance(msg.channel, discord.GroupChannel): 80 | # dest = 'Group Message' 81 | # else: 82 | # dest = 'Voice Channel' 83 | # log.info(f'{msg.created_at}: {msg.author.name} in {dest}: {msg.content}') 84 | 85 | async def on_message(self, message): 86 | #log.info(f'{message.author}: {message.content}') 87 | if message.author.bot or message.author.id in loadconfig.__blacklist__: 88 | return 89 | if isinstance(message.channel, discord.DMChannel): 90 | await message.author.send(':x: Sorry, but I don\'t accept commands through direct messages! Please use the `#bots` channel of your corresponding server!') 91 | return 92 | if self.dev and not await self.is_owner(message.author): 93 | return 94 | if self.user.mentioned_in(message) and message.mention_everyone is False: 95 | if 'help' in message.content.lower(): 96 | await message.channel.send('Eine volle Liste aller Commands gibts hier: https://github.com/Der-Eddy/discord_bot#commands-list') 97 | else: 98 | await message.add_reaction('👀') # :eyes: 99 | if 'loli' in message.clean_content.lower(): 100 | await message.add_reaction('🍭') # :lollipop: 101 | if 'instagram.com' in message.clean_content.lower(): 102 | await message.add_reaction('💩') # :poop: 103 | await self.process_commands(message) 104 | 105 | async def on_member_join(self, member): 106 | if member.guild.id == loadconfig.__botserverid__ and not self.dev: 107 | if member.id in loadconfig.__blacklist__: 108 | member.kick() 109 | await self.owner.send(f'Benutzer **{member}** automatisch gekickt') 110 | if loadconfig.__greetmsg__ != '': 111 | channel = member.guild.system_channel 112 | emojis = [':wave:', ':congratulations:', ':wink:', ':new:', ':cool:', ':white_check_mark:', ':tada:'] 113 | await channel.send(loadconfig.__greetmsg__.format(emoji = random.choice(emojis), member = member.mention)) 114 | 115 | async def on_member_remove(self, member): 116 | if member.guild.id == loadconfig.__botserverid__ and not self.dev: 117 | if loadconfig.__leavemsg__ != '': 118 | channel = member.guild.system_channel 119 | await channel.send(loadconfig.__leavemsg__.format(member = member.name)) 120 | 121 | async def on_guild_join(self, guild): 122 | embed = discord.Embed(title=':white_check_mark: Guild hinzugefügt', type='rich', color=0x2ecc71) #Green 123 | embed.set_thumbnail(url=guild.icon.url) 124 | embed.add_field(name='Name', value=guild.name, inline=True) 125 | embed.add_field(name='ID', value=guild.id, inline=True) 126 | embed.add_field(name='Besitzer', value=f'{guild.owner} ({guild.owner.id})', inline=True) 127 | embed.add_field(name='Region', value=guild.region, inline=True) 128 | embed.add_field(name='Mitglieder', value=guild.member_count, inline=True) 129 | embed.add_field(name='Erstellt am', value=guild.created_at, inline=True) 130 | await self.AppInfo.owner.send(embed=embed) 131 | 132 | async def on_guild_remove(self, guild): 133 | embed = discord.Embed(title=':x: Guild entfernt', type='rich', color=0xe74c3c) #Red 134 | embed.set_thumbnail(url=guild.icon.url) 135 | embed.add_field(name='Name', value=guild.name, inline=True) 136 | embed.add_field(name='ID', value=guild.id, inline=True) 137 | embed.add_field(name='Besitzer', value=f'{guild.owner} ({guild.owner.id})', inline=True) 138 | embed.add_field(name='Region', value=guild.region, inline=True) 139 | embed.add_field(name='Mitglieder', value=guild.member_count, inline=True) 140 | embed.add_field(name='Erstellt am', value=guild.created_at, inline=True) 141 | await self.AppInfo.owner.send(embed=embed) 142 | 143 | async def on_error(self, event, *args, **kwargs): 144 | if self.dev: 145 | traceback.print_exc() 146 | else: 147 | embed = discord.Embed(title=':x: Event Error', colour=0xe74c3c) #Red 148 | embed.add_field(name='Event', value=event) 149 | embed.description = '```py\n%s\n```' % traceback.format_exc() 150 | embed.timestamp = datetime.datetime.utcnow() 151 | try: 152 | await self.AppInfo.owner.send(embed=embed) 153 | except: 154 | pass 155 | 156 | async def on_command_error(self, ctx, error): 157 | if isinstance(error, commands.NoPrivateMessage): 158 | await ctx.author.send('This command cannot be used in private messages.') 159 | elif isinstance(error, commands.DisabledCommand): 160 | await ctx.channel.send(':x: Dieser Command wurde deaktiviert') 161 | else: 162 | if self.dev: 163 | try: 164 | log.warning(str(error)) 165 | except: 166 | raise error 167 | elif len(ctx.message.content) > 2: 168 | embed = discord.Embed(title=':x: Command Error', colour=0x992d22) #Dark Red 169 | embed.add_field(name='Error', value=error) 170 | embed.add_field(name='Guild', value=ctx.guild) 171 | embed.add_field(name='Channel', value=ctx.channel) 172 | embed.add_field(name='User', value=ctx.author) 173 | embed.add_field(name='Message', value=ctx.message.clean_content) 174 | embed.timestamp = datetime.datetime.utcnow() 175 | try: 176 | await self.AppInfo.owner.send(embed=embed) 177 | except: 178 | pass 179 | 180 | if __name__ == '__main__': 181 | sys.argv = ''.join(sys.argv[1:]) 182 | if sys.argv.lower() == 'docker': 183 | docker = True 184 | else: 185 | docker = False 186 | bot = ShinobuBot(docker) 187 | bot.run(loadconfig.__token__) 188 | -------------------------------------------------------------------------------- /font/LICENCE-FAQ.txt: -------------------------------------------------------------------------------- 1 | Ubuntu Font Family Licensing FAQ 2 | 3 | Stylistic Foundations 4 | 5 | The Ubuntu Font Family is the first time that a libre typeface has been 6 | designed professionally and explicitly with the intent of developing a 7 | public and long-term community-based development process. 8 | 9 | When developing an open project, it is generally necessary to have firm 10 | foundations: a font needs to maintain harmony within itself even across 11 | many type designers and writing systems. For the [1]Ubuntu Font Family, 12 | the process has been guided with the type foundry Dalton Maag setting 13 | the project up with firm stylistic foundation covering several 14 | left-to-right scripts: Latin, Greek and Cyrillic; and right-to-left 15 | scripts: Arabic and Hebrew (due in 2011). 16 | 17 | With this starting point the community will, under the supervision of 18 | [2]Canonical and [3]Dalton Maag, be able to build on the existing font 19 | sources to expand their character coverage. Ultimately everybody will 20 | be able to use the Ubuntu Font Family in their own written languages 21 | across the whole of Unicode (and this will take some time!). 22 | 23 | Licensing 24 | 25 | The licence chosen by any free software project is one of the 26 | foundational decisions that sets out how derivatives and contributions 27 | can occur, and in turn what kind of community will form around the 28 | project. 29 | 30 | Using a licence that is compatible with other popular licences is a 31 | powerful constraint because of the [4]network effects: the freedom to 32 | share improvements between projects allows free software to reach 33 | high-quality over time. Licence-proliferation leads to many 34 | incompatible licences, undermining the network effect, the freedom to 35 | share and ultimately making the libre movement that Ubuntu is a part of 36 | less effective. For all kinds of software, writing a new licence is not 37 | to be taken lightly and is a choice that needs to be thoroughly 38 | justified if this path is taken. 39 | 40 | Today it is not clear to Canonical what the best licence for a font 41 | project like the Ubuntu Font Family is: one that starts life designed 42 | by professionals and continues with the full range of community 43 | development, from highly commercial work in new directions to curious 44 | beginners' experimental contributions. The fast and steady pace of the 45 | Ubuntu release cycle means that an interim libre licence has been 46 | necessary to enable the consideration of the font family as part of 47 | Ubuntu 10.10 operating system release. 48 | 49 | Before taking any decision on licensing, Canonical as sponsor and 50 | backer of the project has reviewed the many existing licenses used for 51 | libre/open fonts and engaged the stewards of the most popular licenses 52 | in detailed discussions. The current interim licence is the first step 53 | in progressing the state-of-the-art in licensing for libre/open font 54 | development. 55 | 56 | The public discussion must now involve everyone in the (comparatively 57 | new) area of the libre/open font community; including font users, 58 | software freedom advocates, open source supporters and existing libre 59 | font developers. Most importantly, the minds and wishes of professional 60 | type designers considering entering the free software business 61 | community must be taken on board. 62 | 63 | Conversations and discussion has taken place, privately, with 64 | individuals from the following groups (generally speaking personally on 65 | behalf of themselves, rather than their affiliations): 66 | * [5]SIL International 67 | * [6]Open Font Library 68 | * [7]Software Freedom Law Center 69 | * [8]Google Font API 70 | 71 | Document embedding 72 | 73 | One issue highlighted early on in the survey of existing font licences 74 | is that of document embedding. Almost all font licences, both free and 75 | unfree, permit embedding a font into a document to a certain degree. 76 | Embedding a font with other works that make up a document creates a 77 | "combined work" and copyleft would normally require the whole document 78 | to be distributed under the terms of the font licence. As beautiful as 79 | the font might be, such a licence makes a font too restrictive for 80 | useful general purpose digital publishing. 81 | 82 | The situation is not entirely unique to fonts and is encountered also 83 | with tools such as GNU Bison: a vanilla GNU GPL licence would require 84 | anything generated with Bison to be made available under the terms of 85 | the GPL as well. To avoid this, Bison is [9]published with an 86 | additional permission to the GPL which allows the output of Bison to be 87 | made available under any licence. 88 | 89 | The conflict between licensing of fonts and licensing of documents, is 90 | addressed in two popular libre font licences, the SIL OFL and GNU GPL: 91 | * [10]SIL Open Font Licence: When OFL fonts are embedded in a 92 | document, the OFL's terms do not apply to that document. (See 93 | [11]OFL-FAQ for details. 94 | * [12]GPL Font Exception: The situation is resolved by granting an 95 | additional permission to allow documents to not be covered by the 96 | GPL. (The exception is being reviewed). 97 | 98 | The Ubuntu Font Family must also resolve this conflict, ensuring that 99 | if the font is embedded and then extracted it is once again clearly 100 | under the terms of its libre licence. 101 | 102 | Long-term licensing 103 | 104 | Those individuals involved, especially from Ubuntu and Canonical, are 105 | interested in finding a long-term libre licence that finds broad favour 106 | across the whole libre/open font community. The deliberation during the 107 | past months has been on how to licence the Ubuntu Font Family in the 108 | short-term, while knowingly encouraging everyone to pursue a long-term 109 | goal. 110 | * [13]Copyright assignment will be required so that the Ubuntu Font 111 | Family's licensing can be progressively expanded to one (or more) 112 | licences, as best practice continues to evolve within the 113 | libre/open font community. 114 | * Canonical will support and fund legal work on libre font licensing. 115 | It is recognised that the cost and time commitments required are 116 | likely to be significant. We invite other capable parties to join 117 | in supporting this activity. 118 | 119 | The GPL version 3 (GPLv3) will be used for Ubuntu Font Family build 120 | scripts and the CC-BY-SA for associated documentation and non-font 121 | content: all items which do not end up embedded in general works and 122 | documents. 123 | 124 | Ubuntu Font Licence 125 | 126 | For the short-term only, the initial licence is the [14]Ubuntu Font 127 | License (UFL). This is loosely inspired from the work on the SIL 128 | OFL 1.1, and seeks to clarify the issues that arose during discussions 129 | and legal review, from the perspective of the backers, Canonical Ltd. 130 | Those already using established licensing models such as the GPL, OFL 131 | or Creative Commons licensing should have no worries about continuing 132 | to use them. The Ubuntu Font Licence (UFL) and the SIL Open Font 133 | Licence (SIL OFL) are not identical and should not be confused with 134 | each other. Please read the terms precisely. The UFL is only intended 135 | as an interim license, and the overriding aim is to support the 136 | creation of a more suitable and generic libre font licence. As soon as 137 | such a licence is developed, the Ubuntu Font Family will migrate to 138 | it—made possible by copyright assignment in the interium. Between the 139 | OFL 1.1, and the UFL 1.0, the following changes are made to produce the 140 | Ubuntu Font Licence: 141 | * Clarification: 142 | 143 | 1. Document embedding (see [15]embedding section above). 144 | 2. Apply at point of distribution, instead of receipt 145 | 3. Author vs. copyright holder disambiguation (type designers are 146 | authors, with the copyright holder normally being the funder) 147 | 4. Define "Propagate" (for internationalisation, similar to the GPLv3) 148 | 5. Define "Substantially Changed" 149 | 6. Trademarks are explicitly not transferred 150 | 7. Refine renaming requirement 151 | 152 | Streamlining: 153 | 8. Remove "not to be sold separately" clause 154 | 9. Remove "Reserved Font Name(s)" declaration 155 | 156 | A visual demonstration of how these points were implemented can be 157 | found in the accompanying coloured diff between SIL OFL 1.1 and the 158 | Ubuntu Font Licence 1.0: [16]ofl-1.1-ufl-1.0.diff.html 159 | 160 | References 161 | 162 | 1. http://font.ubuntu.com/ 163 | 2. http://www.canonical.com/ 164 | 3. http://www.daltonmaag.com/ 165 | 4. http://en.wikipedia.org/wiki/Network_effect 166 | 5. http://scripts.sil.org/ 167 | 6. http://openfontlibrary.org/ 168 | 7. http://www.softwarefreedom.org/ 169 | 8. http://code.google.com/webfonts 170 | 9. http://www.gnu.org/licenses/gpl-faq.html#CanIUseGPLToolsForNF 171 | 10. http://scripts.sil.org/OFL_web 172 | 11. http://scripts.sil.org/OFL-FAQ_web 173 | 12. http://www.gnu.org/licenses/gpl-faq.html#FontException 174 | 13. https://launchpad.net/~uff-contributors 175 | 14. http://font.ubuntu.com/ufl/ubuntu-font-licence-1.0.txt 176 | 15. http://font.ubuntu.com/ufl/FAQ.html#embedding 177 | 16. http://font.ubuntu.com/ufl/ofl-1.1-ufl-1.0.diff.html 178 | -------------------------------------------------------------------------------- /cogs/forum.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import random 3 | import re 4 | import asyncio 5 | import xml.etree.ElementTree as ET 6 | import aiohttp 7 | import discord 8 | from discord.ext import commands 9 | import loadconfig 10 | 11 | class forum(commands.Cog): 12 | '''Forum spezifische Commands''' 13 | 14 | def __init__(self, bot): 15 | self.bot = bot 16 | self.discourseURL = 'https://www.kokoro-ko.de' 17 | 18 | async def cog_command_error(self, ctx, error): 19 | print('Error in {0.command.qualified_name}: {1}'.format(ctx, error)) 20 | 21 | @staticmethod 22 | async def _getDiscordTag(username, userAgentHeaders): 23 | checkUsername = False 24 | epvpusername = username 25 | if username.startswith('https://www.elitepvpers.com/forum/'): 26 | url = username 27 | checkUsername = True 28 | else: 29 | url = f'https://www.elitepvpers.com/forum/member.php?username={username}' 30 | async with aiohttp.ClientSession(cookies = loadconfig.__cookieJar__, headers = userAgentHeaders) as cs: 31 | async with cs.get(url) as r: 32 | content = await r.text() 33 | #with open('debug.html', 'w', encoding='utf-8') as file_: 34 | # file_.write(content) 35 | regex = r"
Discord<\/dt>\n
(?P.+)#(?P\d{4})<\/dd>" 36 | match = re.search(regex, content) 37 | if checkUsername: 38 | usernameregex = r"View Profile: (.*?)<\/title>" 39 | epvpusername = re.search(usernameregex, content) 40 | epvpusername = epvpusername.group(1) 41 | print(match.group(1)) 42 | print(match.group(2)) 43 | print(epvpusername) 44 | try: 45 | return (match.group(1) + '#' + match.group(2), epvpusername) 46 | except AttributeError: 47 | return ('', epvpusername) 48 | 49 | @commands.command(aliases=['epvp']) 50 | @commands.cooldown(1, 5, commands.cooldowns.BucketType.guild) 51 | async def epvpis(self, ctx, *user: str): 52 | '''Search for an username on elitepvpers.com 53 | 54 | Beispiel: 55 | ----------- 56 | 57 | :epvpis Der-Eddy 58 | ''' 59 | url = 'https://www.elitepvpers.com/forum/ajax.php?do=usersearch' 60 | payload = { 61 | 'do': 'usersearch', 62 | 'fragment': ' '.join(user) 63 | } 64 | async with aiohttp.ClientSession() as cs: 65 | async with cs.post(url, data = payload, headers = self.bot.userAgentHeaders) as r: 66 | root = ET.fromstring(await r.text()) 67 | if len(root) > 0: 68 | embed = discord.Embed(color=0xf1c40f) #golden 69 | embed.set_footer(text='A maximum of 15 user can be displayed') 70 | embed.set_thumbnail(url='https://abload.de/img/epvp_shield_hiresyskb3.png') 71 | msg = ':ok: I could find {} user!'.format(len(root)) 72 | for i in root: 73 | userURL = 'https://www.elitepvpers.com/forum/member.php?u=' + i.attrib['userid'] 74 | embed.add_field(name=i.text, value=userURL, inline=False) 75 | await ctx.send(msg, embed=embed) 76 | else: 77 | msg = f':no_entry: Couldn\'t find any user **{username}** :sweat:' 78 | await ctx.send(msg) 79 | 80 | # @epvpis.error 81 | # async def epvpis_error(self, error, ctx): 82 | # if isinstance(error, commands.errors.CommandOnCooldown): 83 | # seconds = str(error)[34:] 84 | # await ctx.send(f':alarm_clock: Cooldown! Try again in {seconds}') 85 | 86 | @commands.command(aliases=['verify']) 87 | @commands.cooldown(1, 5, commands.cooldowns.BucketType.guild) 88 | async def epvpverify(self, ctx, *user: str): 89 | '''Verifying a discord user via elitepvpers 90 | 91 | Beispiel: 92 | ----------- 93 | 94 | :epvpverify 95 | 96 | :epvpverify Der-Eddy 97 | 98 | :verify https://www.elitepvpers.com/forum/members/984054-der-eddy.html 99 | ''' 100 | #Eddys Server 101 | if ctx.message.guild.id == 102817255661772800: 102 | verifyRole = 'Member' 103 | #Coding Lounge 104 | elif ctx.message.guild.id == 161637499939192832: 105 | verifyRole = 'Verified Account' 106 | else: 107 | await ctx.send('**:no_entry:** This command only works on some selected servers!') 108 | return 109 | 110 | role = discord.utils.get(ctx.guild.roles, name=verifyRole) 111 | 112 | if len(user) == 0: 113 | username = ctx.author.name 114 | else: 115 | if user[0] == 'remove': 116 | try: 117 | await ctx.author.remove_roles(role) 118 | await ctx.send(f':ok: Role **{role}** removed') 119 | except: 120 | pass 121 | finally: 122 | return 123 | else: 124 | username = ' '.join(user) 125 | tmp = await ctx.send(f':ok: Trying to verify Discord user **{ctx.author}** with Elitepvpers user **{username}**...') 126 | async with ctx.channel.typing(): 127 | discriminator, username = await self._getDiscordTag(username, self.bot.userAgentHeaders) 128 | if str(ctx.author) == discriminator: 129 | if role in ctx.message.author.roles: 130 | await tmp.edit(content = f':negative_squared_cross_mark: You already have the role **{role}**!') 131 | else: 132 | try: 133 | await ctx.author.add_roles(role) 134 | if not username.startswith('https://www.elitepvpers.com/forum/'): 135 | await ctx.author.edit(nick = username) 136 | except: 137 | pass 138 | await tmp.edit(content = f':white_check_mark: User **{username}** successfully verified! Added to role **{role}**') 139 | else: 140 | await tmp.edit(content = f':x: Could not verify Discord user **{ctx.author}** with Elitepvpers user **{username}**') 141 | await ctx.author.send('I\'m messaging you because I couldn\'t verify you with your corresponding Elitepvpers account' + 142 | '\n\nYou will need to specify your Elitepvpers username with the `:verify` command in case your Discord username is' + 143 | '**not** the same as your Elitepvpers username.' + 144 | 'This can be done via `:verify [YOUR ELITEPVPERS USERNAME]`' + 145 | '\n\nAlso don\'t forget to add your Discord username + discriminator in your elitepvpers settings! ' + 146 | '(<https://www.elitepvpers.com/forum/profile.php?do=editprofile>) \nhttps://i.imgur.com/4ckQsjX.png') 147 | 148 | @epvpverify.error 149 | async def epvpverify_error(self, ctx, error): 150 | if isinstance(error, commands.errors.CommandOnCooldown): 151 | await ctx.send(str(error)) 152 | else: 153 | await ctx.send('Having currently difficulties to reach elitepvpers. Try it again in some hours.') 154 | 155 | # @commands.command(aliases=['user']) 156 | # @commands.cooldown(2, 1, commands.cooldowns.BucketType.guild) 157 | # async def kokoro(self, ctx, *user: str): 158 | # '''Gibt Benutzerdaten über einen Benutzer aus Kokoro-ko.de aus 159 | # 160 | # Beispiel: 161 | # ----------- 162 | # 163 | # :kokoro 164 | # 165 | # :user Eddy 166 | # ''' 167 | # if len(user) == 0: 168 | # username = ctx.author.name 169 | # else: 170 | # username = user[0] 171 | # 172 | # url = f'{self.discourseURL}/users/{username}.json' 173 | # async with aiohttp.ClientSession() as cs: 174 | # async with cs.put(url, data = loadconfig.__discourseAPIKey__) as r: 175 | # json = await r.json() 176 | # if json['user']['primary_group_flair_bg_color'] == None or True: 177 | # color = 0xff6600 #orange 178 | # else: 179 | # #color = hex(int(json['user']['primary_group_flair_bg_color'], 16)) 180 | # color = discord.Color(hex(int(json['user']['primary_group_flair_bg_color'], 16))) 181 | # print(color.value) 182 | # #currently not working?? 183 | # embed = discord.Embed(color=color) 184 | # embed.set_footer(text='kokoro-ko.de - Dein Anime und Gaming forum') 185 | # avatarURL = self.discourseURL + json['user']['avatar_template'] 186 | # embed.set_thumbnail(url=avatarURL.format(size = '124')) 187 | # if json['user']['name'] == '': 188 | # discordName = json['user']['username'] 189 | # else: 190 | # discordName = '{} ({})'.format(json['user']['username'], json['user']['name']) 191 | # embed.add_field(name='Username', value=discordName, inline=True) 192 | # embed.add_field(name='Vertrauensstufe', value=json['user']['trust_level'], inline=True) 193 | # if json['user']['title'] != '' and json['user']['title'] != None: 194 | # embed.add_field(name='Titel', value=json['user']['title'], inline=True) 195 | # embed.add_field(name='Registriert am', value=json['user']['created_at'], inline=True) 196 | # embed.add_field(name='Abzeichen', value=json['user']['badge_count'], inline=True) 197 | # embed.add_field(name='Beiträge', value=json['user']['post_count'], inline=True) 198 | # if json['user']['user_fields']['7'] != '' and json['user']['user_fields']['7'] != None: 199 | # embed.add_field(name='Discord', value=json['user']['user_fields']['7'], inline=True) 200 | # if json['user']['user_fields']['1'] != '' and json['user']['user_fields']['1'] != None: 201 | # embed.add_field(name='Steam', value='http://steamcommunity.com/id/' + json['user']['user_fields']['1'], inline=True) 202 | # groups = '' 203 | # for group in json['user']['groups']: 204 | # if group['automatic'] == False: 205 | # groups += group['name'] + ', ' 206 | # if groups != '': 207 | # embed.add_field(name='Gruppen', value=groups[:-2], inline=True) 208 | # embed.add_field(name='Profile Link', value=f'{self.discourseURL}/users/{username}/summary', inline=True) 209 | # await ctx.send(embed=embed) 210 | # else: 211 | # msg = f':no_entry: Ich konnte keinen Account **{username}** auf kokoro-ko.de finden :sweat:' 212 | # await ctx.send(msg) 213 | 214 | # @kokoro.error 215 | # async def kokoro_error(self, error, ctx): 216 | # if isinstance(error, commands.errors.CommandOnCooldown): 217 | # seconds = str(error)[34:] 218 | # await ctx.send(f':alarm_clock: Cooldown! Versuche es in {seconds} erneut') 219 | 220 | async def setup(bot): 221 | await bot.add_cog(forum(bot)) 222 | -------------------------------------------------------------------------------- /cogs/fun.py: -------------------------------------------------------------------------------- 1 | import random 2 | import urllib.parse 3 | import sqlite3 4 | import asyncio 5 | import aiohttp 6 | import discord 7 | from discord.ext import commands 8 | import loadconfig 9 | 10 | class fun(commands.Cog): 11 | def __init__(self, bot): 12 | self.bot = bot 13 | 14 | async def cog_command_error(self, ctx, error): 15 | print('Error in {0.command.qualified_name}: {1}'.format(ctx, error)) 16 | 17 | def userOnline(self, memberList): 18 | online = [] 19 | for i in memberList: 20 | if i.status == discord.Status.online and i.bot == False: 21 | online.append(i) 22 | return online 23 | 24 | @commands.command(aliases=['javascript', 'nodejs', 'js']) 25 | async def java(self, ctx): 26 | '''Weil Java != Javscript''' 27 | await ctx.send(':interrobang: Meintest du jQuery, Javascript oder Node.js? https://abload.de/img/2016-05-102130191kzpu.png') 28 | 29 | @commands.command(aliases=['c++', 'c#', 'objective-c']) 30 | async def csharp(self, ctx): 31 | '''Wie soll man da überhaupt durchblicken???''' 32 | await ctx.send(':interrobang: Meintest du C, C++, C# oder Objective-C? https://i.imgur.com/Nd4aAXO.png') 33 | 34 | @commands.command() 35 | async def praise(self, ctx): 36 | '''Praise the Sun''' 37 | await ctx.send('https://i.imgur.com/K8ySn3e.gif') 38 | 39 | @commands.command() 40 | async def css(self, ctx): 41 | '''Counter Strike: Source''' 42 | await ctx.send('http://i.imgur.com/TgPKFTz.gif') 43 | 44 | @commands.command() 45 | async def countdown(self, ctx): 46 | '''It's the final countdown''' 47 | countdown = ['five', 'four', 'three', 'two', 'one'] 48 | for num in countdown: 49 | await ctx.send('**:{0}:**'.format(num)) 50 | await asyncio.sleep(1) 51 | await ctx.send('**:ok:** DING DING DING') 52 | 53 | @commands.command(aliases=['cat', 'randomcat']) 54 | async def neko(self, ctx): 55 | '''Zufällige Katzen Bilder nyan~''' 56 | #http://discordpy.readthedocs.io/en/latest/faq.html#what-does-blocking-mean 57 | async with aiohttp.ClientSession() as cs: 58 | async with cs.get('http://aws.random.cat/meow') as r: 59 | res = await r.json() 60 | emojis = [':cat2: ', ':cat: ', ':heart_eyes_cat: '] 61 | await ctx.send(random.choice(emojis) + res['file']) 62 | 63 | 64 | @commands.command(aliases=['rand']) 65 | async def random(self, ctx, *arg): 66 | '''Gibt eine zufällige Zahl oder Member aus 67 | 68 | Benutzung: 69 | ----------- 70 | 71 | :random 72 | Gibt eine zufällige Zahl zwischen 1 und 100 aus 73 | 74 | :random coin 75 | Wirft eine Münze (Kopf oder Zahl) 76 | 77 | :random 6 78 | Gibt eine zufällige Zahl zwischen 1 und 6 aus 79 | 80 | :random 10 20 81 | Gibt eine zufällige Zahl zwischen 10 und 20 aus 82 | 83 | :random user 84 | Gibt einen zufällige Benutzer der gerade online ist aus 85 | 86 | :random choice Dani Eddy Shinobu 87 | Wählt aus der vorgegebenen Liste einen Namen aus 88 | ''' 89 | if ctx.invoked_subcommand is None: 90 | if not arg: 91 | start = 1 92 | end = 100 93 | elif arg[0] == 'flip' or arg[0] == 'coin': 94 | coin = ['Kopf', 'Zahl'] 95 | await ctx.send(f':arrows_counterclockwise: {random.choice(coin)}') 96 | return 97 | elif arg[0] == 'choice': 98 | choices = list(arg) 99 | choices.pop(0) 100 | await ctx.send(f':congratulations: The winner is {random.choice(choices)}') 101 | return 102 | elif arg[0] == 'user': 103 | online = self.userOnline(ctx.guild.members) 104 | randomuser = random.choice(online) 105 | if ctx.channel.permissions_for(ctx.author).mention_everyone: 106 | user = randomuser.mention 107 | else: 108 | user = randomuser.display_name 109 | await ctx.send(f':congratulations: The winner is {user}') 110 | return 111 | elif len(arg) == 1: 112 | start = 1 113 | end = int(arg[0]) 114 | elif len(arg) == 2: 115 | start = int(arg[0]) 116 | end = int(arg[1]) 117 | await ctx.send(f'**:arrows_counterclockwise:** Zufällige Zahl ({start} - {end}): {random.randint(start, end)}') 118 | 119 | @commands.command() 120 | async def steinigt(self, ctx, member:str): 121 | '''Monty Python''' 122 | await ctx.send(f'R.I.P. {member}\nhttps://media.giphy.com/media/l41lGAcThnMc29u2Q/giphy.gif') 123 | 124 | @commands.command(aliases=['hypu', 'train']) 125 | async def hype(self, ctx): 126 | '''HYPE TRAIN CHOO CHOO''' 127 | hypu = ['https://cdn.discordapp.com/attachments/102817255661772800/219514281136357376/tumblr_nr6ndeEpus1u21ng6o1_540.gif', 128 | 'https://cdn.discordapp.com/attachments/102817255661772800/219518372839161859/tumblr_n1h2afSbCu1ttmhgqo1_500.gif', 129 | 'https://gfycat.com/HairyFloweryBarebirdbat', 130 | 'https://i.imgur.com/PFAQSLA.gif', 131 | 'https://abload.de/img/ezgif-32008219442iq0i.gif', 132 | 'https://i.imgur.com/vOVwq5o.jpg', 133 | 'https://i.imgur.com/Ki12X4j.jpg', 134 | 'https://media.giphy.com/media/b1o4elYH8Tqjm/giphy.gif'] 135 | msg = f':train2: CHOO CHOO {random.choice(hypu)}' 136 | await ctx.send(msg) 137 | 138 | @commands.command() 139 | async def xkcd(self, ctx, *searchterm: str): 140 | '''Zeigt den letzten oder zufälligen XKCD Comic 141 | 142 | Beispiel: 143 | ----------- 144 | 145 | :xkcd 146 | 147 | :xkcd random 148 | ''' 149 | apiUrl = 'https://xkcd.com{}info.0.json' 150 | async with aiohttp.ClientSession() as cs: 151 | async with cs.get(apiUrl.format('/')) as r: 152 | js = await r.json() 153 | if ''.join(searchterm) == 'random': 154 | randomComic = random.randint(0, js['num']) 155 | async with cs.get(apiUrl.format('/' + str(randomComic) + '/')) as r: 156 | if r.status == 200: 157 | js = await r.json() 158 | comicUrl = 'https://xkcd.com/{}/'.format(js['num']) 159 | date = '{}.{}.{}'.format(js['day'], js['month'], js['year']) 160 | msg = '**{}**\n{}\nAlt Text:```{}```XKCD Link: <{}> ({})'.format(js['safe_title'], js['img'], js['alt'], comicUrl, date) 161 | await ctx.send(msg) 162 | 163 | @commands.command(aliases=['witz', 'joke']) 164 | async def pun(self, ctx): 165 | '''Weil jeder schlechte Witze mag''' 166 | #ToDo: Add some way to fetch https://github.com/derphilipp/Flachwitze 167 | puns = ['Was sagt das eine Streichholz zum anderen Streichholz?\n Komm, lass uns durchbrennen', 168 | 'Wieviele Deutsche braucht man um eine Glühbirne zu wechseln?\n Einen, wir sind humorlos und effizient.', 169 | 'Wo wohnt die Katze?\n Im Miezhaus.', 170 | 'Wie begrüßen sich zwei plastische Chirurgen?\n "Was machst du denn heute für ein Gesicht?"', 171 | 'Warum essen Veganer kein Huhn?\n Könnte Ei enthalten', 172 | '85% der Frauen finden ihren Arsch zu dick, 10% zu dünn, 5% finden ihn so ok, wie er ist und sind froh, dass sie ihn geheiratet haben...', 173 | 'Meine Freundin meint, ich wär neugierig...\n...zumindest\' steht das in ihrem Tagebuch.', 174 | '"Schatz, Ich muss mein T-Shirt waschen! Welches Waschmaschinen Programm soll ich nehmen?" - "Was steht denn auf dem T-Shirt drauf?"\n "Slayer!"', 175 | 'Gestern erzählte ich meinem Freund, dass ich schon immer dieses Ding aus Harry Potter reiten wollte.\n"einen Besen?" "nein, Hermine."', 176 | 'Warum gehen Ameisen nicht in die Kirche?\nSie sind in Sekten.', 177 | 'Was steht auf dem Grabstein eines Mathematikers?\n"Damit hat er nicht gerechnet."', 178 | 'Wenn ein Yogalehrer seine Beine senkrecht nach oben streckt und dabei furzt, welche Yoga Figur stellt er da?\n Eine Duftkerze', 179 | 'Warum ging der Luftballon kaputt?\n Aus Platzgründen.', 180 | 'Ich wollte Spiderman anrufen, aber er hatte kein Netz und beim Bäcker war alles belegt.', 181 | 'Was vermisst eine Schraube am meisten? Einen Vater', 182 | 'Geht ein Panda über die Straße. Bam....Bus!', 183 | 'Unterhalten sich zwei Gletscher. Sagt der eine: "Was meinst du, was wird die Zukunft bringen?" Sagt der Andere: "Naja, wir werden Seen."', 184 | 'Wenn sich ein Professor ein Brot macht ist das dann wissenschaftlich belegt?', 185 | 'Knabbern zwei Männern an einer Eisenbahnschiene. Sagt der eine: "Ganz schön hart, oder?"\nSagt der andere: "Aber guck mal, da drübern ist ne Weiche"', 186 | 'Warum sind bei IKEA Pfeile auf dem Boden?\nWeil es ein Einrichtungshaus ist', 187 | 'Was macht die Security in der Nudelfabrik?\nDie Pasta auf.', 188 | 'Wie nennt man einen kleinwüchsigen Securitymenschen?\nSicherheitshalber', 189 | 'Habe bei Weight Watchers angerufen. Hat keiner abgenommen.\nDanach beim DJ. Hat aber aufgelegt.' 190 | 'Meine Schwester hat eine Tochter bekommen.\nDa wurde mein Wunsch nach einem Neffen zur Nichte gemacht.', 191 | 'Praktizieren sie Inzest?\n"Mitnichten"', 192 | 'Wann sinkt ein U-Boot?\nAm Tag der offenen Tür.', 193 | 'Auf St. Pauli wurde letztens ein Sarg gefunden. Er konnte aber nicht geöffnet werden, war ein Zuhälter drin!', 194 | 'Treffen sich zwei Anwälte. Fragt der eine "Na, wie geht\'s?" Antwortet der andere "Schlecht. Ich kann nicht klagen"', 195 | 'Treffen sich zwei Jäger. Beide tot.', 196 | 'Treffen sich zwei Päpste.', 197 | 'Treffen sich zwei Psychologen, sagt der eine: "Dir geht\'s gut, wie geht\'s mir?"', 198 | 'Treffen sich zwei Linksextreme in einer Bar, kommen drei Splittergruppen raus.', 199 | 'Was macht man mit nem Hund ohne Beine?\nUm die Häuser ziehen.', 200 | 'Wo findest du nen Hund ohne Beine?\nDa wo du ihn liegen lassen hast.', 201 | 'Was macht eine Bombe im Bordell?\nPuff', 202 | 'Und was macht eine Bombe im Treppenhaus?\nHochgehen', 203 | 'Wo war Lucy nach der Explosion?\nÜberall', 204 | 'Egal, wie dicht du bist. Göthe war dichter!', 205 | 'Egal, wie gut du fährst. Züge fahren Güter!', 206 | 'Egal, wie sauer du bist, Dinos sind Saurier!', 207 | 'Egal, wie leer du bist, es gibt Menschen die sind Lehrer.', 208 | 'Wissenschaftler haben herausgefunden\nund sind dann wieder reingegangen.', 209 | 'Was ist klein, braun, rund und sitzt hinter Gittern? Eine Knastanie.', 210 | 'Was liegt am Strand und kann nicht richtig reden? - Eine Nuschel!', 211 | 'Was ist grün und klopft an die Tür? - Klopfsalat', 212 | 'Was ist rot und steht am Straßenrand? Eine Hagenutte', 213 | 'Und was ist blau und steht am Wegesrand? Eine Frostituierte', 214 | 'Was ist rosa und schwimmt durchs Meer? Eine Meerjungsau.', 215 | 'Was ist braun und schwimmt auch im Meer? Ein U-Brot.', 216 | 'Was raucht und springt durch den Wald? Ein Kaminchen.', 217 | 'Was machen Bits am liebsten? Busfahren.', 218 | 'Warum ist der Programmierer in der Dusche gestorben? Auf der Flasche stand “einschäumen, ausspülen, wiederholen"', 219 | 'Wo gehen Datenspeicher hin, wenn sie sich prügeln wollen? In den Byte Club.\n Und Regel Nummer Eins: Ihr verliert kein dword über den Byte Club!', 220 | 'Wer wohnt im Dschungel und schummelt? Mogli', 221 | 'Geht ein Mann zum Arzt weil er sich schlecht fühlt. Sagt der Arzt: "Sie müssen mit dem Masturbieren aufhören!"\nSagt der Mann: "Wieso das denn?!".\nSagt der Arzt: "Ja, sonst kann ich Sie nicht untersuchen."', 222 | 'Wie heißt ein Spanier ohne Auto?\nCarlos', 223 | 'Wie nennt man ein Cowboy ohne Pferd?\nSattelschlepper', 224 | 'Kommt ein Cowboy aus dem Frisiersalon heraus\nPony weg', 225 | 'Wie nennt man einen Schäfer, der seine Schafe schlägt?\nMähdrescher', 226 | 'Was trinkt die Chefin?\nLeitungswasser', 227 | 'Vampir in der Verkehrskontrolle.\n"Haben Sie was getrunken?"\n"Ja, zwei Radler."', 228 | 'Wie nennt man jemanden, der DIN A4 Blätter scannt?\nScandinavier', 229 | 'Wie nennt man einen Europäer aus Lettland?\nEuropalette', 230 | 'Hab nem Hipster ins Bein geschossen\nJetzt hopster', 231 | 'Wie viel wiegt ein Influencer?\nEin Instagramm', 232 | 'Was ist gelb und kann nicht schwimmen?\nEin Bagger\nUnd warum nicht?\nHat nur einen Arm', 233 | 'Was hat ein Mann ohne Beine?\nErdnüsse', 234 | 'Welcher Vogel hat Darth Vader auf denn Kopf geschissen?\nDer Star wars', 235 | 'Wie heißt ein Veganer Russe?\nMooskauer', 236 | 'Was ist der Unterschied zwischen Grießbrei und einem Epileptiker?\nDer Grießbrei liegt in Zucker und Zimt, der Epileptiker liegt im Zimmer und zuckt.', 237 | 'Was macht ein Clown im Büro?\nFaxen', 238 | 'Was ist grūn und nuschelt im Gurkensalat?\nDill Schweiger', 239 | 'Was ist die Vergangenheitsform von Tomate? Passierte Tomate', 240 | 'Gehören abgetriebene Babys eigentlich auch zu den entfernen Verwandten?', 241 | 'Kommt ein Dachdecker in ne Bar\nDa sagt der Barkeeper: "Der geht aufs Haus!"', 242 | 'Was spricht man in der Sauna? Schwitzerdeutsch.', 243 | 'Was ist grün und wird auf Knopfdruck rot?\nEin Frosch im Mixer', 244 | 'Was ist weiß und fliegt über die Wiese?\nBiene Majo', 245 | 'Warum trinken Veganer kein Leitungswasser?\nWeil es aus dem Hahn kommt'] 246 | emojis = [':laughing:', ':smile:', ':joy:', ':sob:', ':rofl:'] 247 | msg = f'{random.choice(emojis)} {random.choice(puns)}' 248 | await ctx.send(msg) 249 | 250 | async def setup(bot): 251 | await bot.add_cog(fun(bot)) 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Avatar](img/ava.png) 2 | ![Slogan](img/title.png) 3 | ===================== 4 | 5 | [![Python3](https://img.shields.io/badge/python-3.9-blue.svg)](https://github.com/Der-Eddy/discord_bot) 6 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/Der-Eddy/discord_bot/master/LICENSE) 7 | [![Discord Server](https://img.shields.io/badge/Support-Discord%20Server-blue.svg)](https://discord.gg/xm2TQFt) 8 | ![Docker](https://github.com/Der-Eddy/discord_bot/workflows/Docker/badge.svg) 9 | [![CodeQL](https://github.com/Der-Eddy/discord_bot/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/Der-Eddy/discord_bot/actions/workflows/codeql-analysis.yml) 10 | 11 | **ATTENTION: This bot uses the new alpha version of [discord.py v2.0](https://github.com/Rapptz/discord.py), if you want to use my bot with the old legacy discord.py version check out the [legacy branch](https://github.com/Der-Eddy/discord_bot/tree/0.18.10-legacy).** 12 | This is mostly a german discord chat bot made with [discord.py v2.0](https://github.com/Rapptz/discord.py). 13 | If you are looking for a python discord bot to host for yourself, you should rather take a look at [Red Bot](https://github.com/Twentysix26/Red-DiscordBot) if you want a highly customizable self-hosted python bot. Shinobu is only meant to be run on my own server. 14 | 15 | [untitled.webm](https://github.com/Der-Eddy/discord_bot/assets/1234101/265175da-2cea-4edc-952f-f7c29d27916a) 16 | 17 | Commands List 18 | ------------- 19 | **Info:** Diese Liste gilt für den Standardprefix `:` 20 | 21 | ### Generic ### 22 | 23 | Command und Aliases | Beschreibung | Nutzung 24 | ----------------|--------------|------- 25 | `:help` | Zeigt eine Liste aller öffentlichen Commands | `:help`, `:help kawaii` 26 | 27 | ### Utility ### 28 | 29 | Command und Aliases | Beschreibung | Nutzung 30 | ----------------|--------------|------- 31 | `:status`, `:s`, `:uptime`, `:up` | Listet einige Informationen zum Bot aus | `:status` 32 | `:ping` | Misst die Response Time | `:ping` 33 | `:github` | Verlinkt zu diesem GitHub Repo | `:github` 34 | `:about`, `:info` | Informationen über Shinobu Oshino | `:about` 35 | `:log`, `:archive` | Archiviert den Log des derzeitigen Channels und läd diesen als Attachment hoch | `:log 10` 36 | `:invite` | Erstellt einen Invite Link für den derzeitigen Channel | `:invite` 37 | `:whois` | Gibt Informationen über einen Benutzer aus | `:whois @Der-Eddy#6508` 38 | `:emoji`, `:e` | Gibt eine vergrößerte Version eines angegebenen Emojis zurück | `:emoji Emilie` 39 | `:emojis`| Gibt alle Emojis aus auf welche der Bot Zugriff hat | `:emojis` 40 | `:server`, `:serverinfo`, `:guild`, `:membercount` | Gibt Informationen über die derzeitge Discord Guild aus | `:server` 41 | `:timer`, `:reminder` | Setzt einen Timer und benachrichtigt einen dann | `:timer 13m Pizza`, `:timer 2h` 42 | `:source`| Zeigt den Quellcode für einen Befehl auf GitHub an | `:source kawaii` 43 | `:commands`| Zeigt an wie oft welcher Command benutzt wurde seit dem letzten Startup | `:commands` 44 | `:roleUsers`| Listet alle Benutzer einer Rolle auf | `:roleUsers Admins` 45 | `:games` | Zeigt welche Spiele wie oft auf dem Server gerade gespielt werden | `:games` 46 | `:spoiler` | Erstellt ein GIF Bild welches beim Hover einen Spoiler Text anzeigt | `:spoiler` 47 | `:ranks`, `:rank`, `:role`, `:roles` | Auflistung aller Ränge oder beitritt eines bestimmten Ranges | `:ranks`, `:ranks Python'` 48 | `:addvote` | Fügt Emotes als Reactions hinzu für Abstimmungen/Umfragen | `:addvotes`, `:vote`, `:votes` 49 | 50 | ### Anime ### 51 | 52 | Command und Aliases | Beschreibung | Nutzung 53 | ----------------|--------------|------- 54 | `:kawaii` | Gibt ein zufälliges kawaii Bild aus | `:kawaii` 55 | `:hello`, `:wave`, `:hi`, `:ohaiyo` | Nonsense gifs zum Hallo sagen | `:hello` 56 | `:nep`, `:nepu`, `:topnep` | Can't stop the Nep | `:nep` 57 | `:pat` | /r/headpats Pat Pat Pat :3 | `:pat @Der-Eddy#6508` 58 | `:ratewaifu`, `:rate`, `:waifu` | Bewertet deine Waifu | `:ratewaifu Shinobu` 59 | `:anime`, `:anilist` | Sucht auf AniList.co nach einem Anime und gibt die Basis-Informationen zurück | `:anime Mushishi` 60 | `:manga` | Sucht auf AniList.co nach einem Manga und gibt die Basis-Informationen zurück | `:manga Air Gear` 61 | `:saucenao`, `:sauce`, `:iqdb` | Versucht die Quelle eines Anime Bildes zu finden | `:saucenao https://i.imgur.com/nmnVtgs.jpg` 62 | 63 | ### Fun ### 64 | 65 | Command und Aliases | Beschreibung | Nutzung 66 | ----------------|--------------|------- 67 | `:java`, `:javascript`, `:nodejs`, `:js` | Weil Java != Javscript | `:java` 68 | `:csharp`, `:c++`, `:c#`, `:objective-c` | Wie soll man da überhaupt durchblicken??? | `:csharp` 69 | `:praise` | Praise the sun | `:praise` 70 | `:css` | Counter Strike: Source | `:css` 71 | `:countdown` | It's the final countdown | `:countdown` 72 | `:neko`, `:cat`, `:randomcat` | Zufällige Katzen Bilder nyan~ | `:neko` 73 | `:random`, `:rand` | Gibt eine zufällige Zahl oder Member aus | `:random`, `:random coin`, `:random 6`, `:random 10 20`, `:random user` 74 | `:steinigt` | Monty Python | `:steinigt @Ravenstorm#1191` 75 | `:hype`, `:hypu`, `:train` | HYPE TRAIN CHOO CHOO | `:hype` 76 | `:xkcd` | Zeigt den letzten oder zufälligen XKCD Comic | `:xkcd`, `:xkcd random` 77 | `:reaction`, `:r`, `:addreaction` | Fügt der letzten Nachricht ein Emoji als Reaction hinzu oder einer bestimmten Nachricht | `:reaction ok_hand`, `:reaction sunglasses 247386709505867776` 78 | `:pun`, `:witz`, `:joke` | Weil jeder schlechte Witze mag | `:pun` 79 | 80 | ### Mod ### 81 | 82 | Command und Aliases | Beschreibung | Nutzung 83 | ----------------|--------------|------- 84 | `:purge`, `:prune` | Löscht mehere Nachrichten auf einmal. **MOD ONLY** | `:purge 100` 85 | `:kick` | Kickt ein Mitglied mit einer Begründung. **MOD ONLY** | `:kick @Der-Eddy#6508`, `:kick @Der-Eddy#6508 Spammt Werbung` 86 | `:ban` | Bannt ein Mitglied mit einer Begründung. **MOD ONLY** | `:ban @Der-Eddy#6508`, `:ban @Der-Eddy#6508 Spammt Werbung` 87 | `:unban` | Entbannt ein Mitglied mit einer Begründung. **MOD ONLY** | `:unban 102815825781596160` 88 | `:bans` | Listet aktuell gebannte User auf. **MOD ONLY** | `:bans` 89 | `:removereactions` | Entfernt alle Emoji Reactions von einer Nachricht. **MOD ONLY** | `:removereactions 247386709505867776` 90 | `:permissions` | Listet alle Rechte des Bots auf. **ADMIN OR BOT OWNER ONLY** | `:permissions` 91 | `:hierarchy` | Listet die Rollen-Hierarchie des derzeitigen Servers auf. **ADMIN OR BOT OWNER ONLY** | `:hierarchy` 92 | `:setrank`, `:setrole`, `:sr` | Vergibt einen Rang an einem Benutzer. **MOD ONLY** | `:setrole @Der-Eddy#6508 Member` 93 | `:rmrank`, `:rmrole`, `:removerole`, `:removerank` | Entfernt einen Rang von einem Benutzer. **MOD ONLY** | `:rmrole @Der-Eddy#6508 Member` 94 | 95 | ### Admin ### 96 | 97 | Command und Aliases | Beschreibung | Nutzung 98 | ----------------|--------------|------- 99 | `:shutdown`, `:quit` | Schaltet den Bot aus. **BOT OWNER ONLY** | `:shutdown` 100 | `:restart` | Startet den Bot neu. **BOT OWNER ONLY** | `:restart` 101 | `:avatar` | Setzt einen neuen Avatar. **BOT OWNER ONLY** | `:avatar https://i.imgur.com/iJlPa3V.png` 102 | `:changegame`, `:game` | Ändert das derzeit spielende Spiel. **BOT OWNER ONLY** | `:changegame playing Dark Souls`, `:game listening Pendulum` 103 | `:changestatus` | Ändert den Online Status vom Bot. **BOT OWNER ONLY** | `:changestatus idle` 104 | `:name` | Ändert den globalen Namen vom Bot. **BOT OWNER ONLY** | `:name Shinobu-chan` 105 | `:servers` | Listet die aktuellen verbundenen Server auf. **BOT OWNER ONLY** | `:servers` 106 | `:leaveserver` | Schaltet den Bot aus. **BOT OWNER ONLY** | `:leaveserver 102817255661772800` 107 | `:echo` | Gibt eine Nachricht als Bot auf einem bestimmten Channel aus. **BOT OWNER ONLY** | `:echo 102817255661772800 Ich bin ein Bot!` 108 | `:nickname` | Ändert den Server Nickname vom Bot. **BOT OWNER ONLY** | `:nickname Shinobu` 109 | `:setnickname` | Ändert den Nickname eines Benutzer. **BOT OWNER ONLY** | `:setnickname @Der-Eddy#6508 Shinobu` 110 | `:geninvite` | Generiert einen Invite für einen Server wenn möglich. **BOT OWNER ONLY** | `:geninvite 102817255661772800` 111 | 112 | 113 | Run (Docker method) 114 | ------------- 115 | 116 | GitHub Actions erstellt automatisch ein Docker image unter `ghcr.io/der-eddy/shinobu_bot` (AMD64 und ARM64). Unter `docker-compose.examle.yml` findet ihr ein Beispiel wie man dieses einsetzt: 117 | 118 | version: '3.9' 119 | 120 | services: 121 | discord_bot: 122 | container_name: discord_bot 123 | image: ghcr.io/der-eddy/shinobu_bot:latest 124 | restart: always 125 | volumes: 126 | - discord_bot_data:/discord_bot/config 127 | environment: 128 | DISCORD_TOKEN: 'INSERT BOT TOKEN HERE' 129 | DISCORD_PREFIX: ':' #OPTIONAL Prefix for all commands, defaults to colon 130 | DISCORD_BOTSERVERID: '102817255661772800' #OPTIONAL Specifies the main serverid from which the server-/modlog should be taken + some other nito features 131 | DISCORD_KAWAIICHANNEL: '207909155556687872' #OPTIONAL specified a channel where the :kawaii commands gets this pinned messages 132 | DISCORD_GREETMSG: '{emoji} Welcome {member} on my server!' #OPTIONAL sends a greet message to new user in the botserverid system channel 133 | DISCORD_LEAVEMSG: ':sad: {member} left the server' #OPTIONAL sends a leave message to the botserverid system channel 134 | 135 | volumes: 136 | discord_bot_data: 137 | 138 | Anzumerken ist dass das Docker Image [Googles distroless](https://github.com/GoogleContainerTools/distroless) Python 3.7 Basis benutzt, d.h. das nicht mal eine Shell vorinstalliert ist. Ihr könnt natürlich auch euer eigenes Docker Image anhand der `Dockerfile` selber bauen lassen- 139 | 140 | Run (old method) 141 | ------------- 142 | Erstellt zuerst ein virtuelles environment für Python über `python3.7 -m venv env` und aktiviert es über `source env/bin/activate` (Linux only). Anschließend könnt ihr alle benötigten Abhängikeiten über `pip install -r requirements.txt` installieren. 143 | (Um aus der virtuellen Umgebung wieder raus zu kommen einfach `deactivate` eintippen) 144 | 145 | Anschließend started ihr das Script direkt über `python3 main.py` oder erstellt eine systemd service unit, ein Beispiel findet ihr unter `discord.service.example`: 146 | 147 | [Unit] 148 | Description=Shinobu Discord Bot 149 | After=multi-user.target 150 | [Service] 151 | WorkingDirectory=/home/eddy/discord_bot 152 | Environment="PYTHONHASHSEED=0" 153 | User=eddy 154 | Group=eddy 155 | ExecStart=/usr/bin/python3 /home/eddy/discord_bot/main.py 156 | Type=idle 157 | Restart=on-failure 158 | RestartSec=15 159 | TimeoutStartSec=15 160 | 161 | [Install] 162 | WantedBy=multi-user.target 163 | 164 | Nach `/etc/systemd/system/discord.service` kopieren und anpassen. Nicht vergessen die Unit zu starten via `sudo systemctl start discord.service` bzw. Autostart via `sudo systemctl enable discord.service`. 165 | 166 | 167 | Einstellungen 168 | ------------- 169 | Vor dem Start muss im Ordner `config` eine Datei namens `config.py` angelegt werden, ein Beispiel einer solchen gibt es in `config.example.py` zu finden: 170 | 171 | __token__ = 'INSERT BOT TOKEN HERE' 172 | __prefix__ = ':' #OPTIONAL Prefix for all commands, defaults to colon 173 | __timezone__ = 'Europe/Berlin' #OPTIONAL 174 | __botserverid__ = 102817255661772800 #OPTIONAL Specifies the main serverid from which the server-/modlog should be taken + some other nito features 175 | __kawaiichannel__ = 207909155556687872 #OPTIONAL specified a channel where the :kawaii commands gets this pinned messages 176 | __greetmsg__ = '{emoji} Welcome {member} on my server!' #OPTIONAL sends a greet message to new user in the botserverid system channel 177 | __leavemsg__ = ':sad: {member} left the server' #OPTIONAL sends a leave message to the botserverid system channel 178 | 179 | Alternativ für Heroku oder Docker Umgebungen können environment variables statt einer `config.py` Datei genutzt werden: 180 | 181 | DISCORD_TOKEN: 'INSERT BOT TOKEN HERE' 182 | DISCORD_PREFIX: ':' #OPTIONAL Prefix for all commands, defaults to colon 183 | DISCORD_TIMEZONE: 'Europe/Berlin' #OPTIONAL 184 | DISCORD_BOTSERVERID: '102817255661772800' #OPTIONAL Specifies the main serverid from which the server-/modlog should be taken + some other nito features 185 | DISCORD_KAWAIICHANNEL: '207909155556687872' #OPTIONAL specified a channel where the :kawaii commands gets this pinned messages 186 | DISCORD_GREETMSG: '{emoji} Welcome {member} on my server!' #OPTIONAL sends a greet message to new user in the botserverid system channel 187 | DISCORD_LEAVEMSG: ':sad: {member} left the server' #OPTIONAL sends a leave message to the botserverid system channel 188 | 189 | 190 | In `games.py` kann man die Titel der "Playing-" Rotation anpassen. Platzhalter wie `{servers}` oder `{members}` sind möglich. 191 | 192 | __games__ = [ 193 | (discord.ActivityType.playing, 'with Eddy-Senpai'), 194 | (discord.ActivityType.playing, 'with Cats'), 195 | (discord.ActivityType.playing, 'try :help'), 196 | (discord.ActivityType.playing, 'try :about'), 197 | (discord.ActivityType.playing, 'with VS Code'), 198 | (discord.ActivityType.playing, 'with Python'), 199 | (discord.ActivityType.playing, 'with async'), 200 | (discord.ActivityType.playing, 'with Karen-chan'), 201 | (discord.ActivityType.playing, 'with Hinata-chan'), 202 | (discord.ActivityType.playing, 'with Eduard Laser'), 203 | (discord.ActivityType.watching, 'over {guilds} Server'), 204 | (discord.ActivityType.watching, 'over {members} Members'), 205 | (discord.ActivityType.watching, 'Trash Animes'), 206 | (discord.ActivityType.watching, 'you right now'), 207 | (discord.ActivityType.watching, 'Hentai'), 208 | (discord.ActivityType.listening, 'Podcasts') 209 | ] 210 | __gamesTimer__ = 2 * 60 #2 minutes 211 | 212 | Erweiterungen (Cogs) die beim starten aktiviert werden sollen, kann man in `cogs.py` einstellen: 213 | 214 | __cogs__ = [ 215 | 'cogs.admin', 216 | 'cogs.mod', 217 | 'cogs.fun', 218 | 'cogs.anime', 219 | 'cogs.utility', 220 | 'cogs.help' 221 | ] 222 | 223 | 224 | Support 225 | ------------- 226 | Gibts auf meinem Discord Server: `https://discord.gg/xm2TQFt` 227 | 228 | 229 | Troubleshooting 230 | ------------- 231 | Sollte z.B. aus irgendeinem Grund die mod.py cog nicht geladen werden, kann der Bot vom Bot Besitzer über `:shutdown_backup` heruntergefahren werden. 232 | Weitere Tipps folgen 233 | 234 | List of requirements 235 | ------------- 236 | 237 | python>=3.9.0 238 | discord.py 239 | aiohttp 240 | websockets 241 | pytz 242 | pillow 243 | 244 | For a pinned version check `requirements.txt` 245 | 246 | 247 | License 248 | ------------- 249 | MIT License 250 | 251 | Copyright (c) 2016 Eduard Nikoleisen 252 | 253 | Permission is hereby granted, free of charge, to any person obtaining a copy 254 | of this software and associated documentation files (the "Software"), to deal 255 | in the Software without restriction, including without limitation the rights 256 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 257 | copies of the Software, and to permit persons to whom the Software is 258 | furnished to do so, subject to the following conditions: 259 | 260 | The above copyright notice and this permission notice shall be included in all 261 | copies or substantial portions of the Software. 262 | 263 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 264 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 265 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 266 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 267 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 268 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 269 | SOFTWARE. 270 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-whitelist= 7 | 8 | # Specify a score threshold to be exceeded before program exits with error. 9 | fail-under=10.0 10 | 11 | # Add files or directories to the blacklist. They should be base names, not 12 | # paths. 13 | ignore=CVS 14 | 15 | # Add files or directories matching the regex patterns to the blacklist. The 16 | # regex matches against base names, not paths. 17 | ignore-patterns= 18 | 19 | # Python code to execute, usually for sys.path manipulation such as 20 | # pygtk.require(). 21 | #init-hook= 22 | 23 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 24 | # number of processors available to use. 25 | jobs=0 26 | 27 | # Control the amount of potential inferred values when inferring a single 28 | # object. This can help the performance when dealing with large functions or 29 | # complex, nested conditions. 30 | limit-inference-results=100 31 | 32 | # List of plugins (as comma separated values of python module names) to load, 33 | # usually to register additional checkers. 34 | load-plugins= 35 | 36 | # Pickle collected data for later comparisons. 37 | persistent=yes 38 | 39 | # When enabled, pylint would attempt to guess common misconfiguration and emit 40 | # user-friendly hints instead of false-positive error messages. 41 | suggestion-mode=yes 42 | 43 | # Allow loading of arbitrary C extensions. Extensions are imported into the 44 | # active Python interpreter and may run arbitrary code. 45 | unsafe-load-any-extension=no 46 | 47 | 48 | [MESSAGES CONTROL] 49 | 50 | # Only show warnings with the listed confidence levels. Leave empty to show 51 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 52 | confidence= 53 | 54 | # Disable the message, report, category or checker with the given id(s). You 55 | # can either give multiple identifiers separated by comma (,) or put this 56 | # option multiple times (only on the command line, not in the configuration 57 | # file where it should appear only once). You can also use "--disable=all" to 58 | # disable everything first and then reenable specific checks. For example, if 59 | # you want to run only the similarities checker, you can use "--disable=all 60 | # --enable=similarities". If you want to run only the classes checker, but have 61 | # no Warning level messages displayed, use "--disable=all --enable=classes 62 | # --disable=W". 63 | disable=print-statement, 64 | parameter-unpacking, 65 | unpacking-in-except, 66 | old-raise-syntax, 67 | backtick, 68 | long-suffix, 69 | old-ne-operator, 70 | old-octal-literal, 71 | import-star-module-level, 72 | non-ascii-bytes-literal, 73 | raw-checker-failed, 74 | bad-inline-option, 75 | locally-disabled, 76 | file-ignored, 77 | suppressed-message, 78 | useless-suppression, 79 | deprecated-pragma, 80 | use-symbolic-message-instead, 81 | apply-builtin, 82 | basestring-builtin, 83 | buffer-builtin, 84 | cmp-builtin, 85 | coerce-builtin, 86 | execfile-builtin, 87 | file-builtin, 88 | long-builtin, 89 | raw_input-builtin, 90 | reduce-builtin, 91 | standarderror-builtin, 92 | unicode-builtin, 93 | xrange-builtin, 94 | coerce-method, 95 | delslice-method, 96 | getslice-method, 97 | setslice-method, 98 | no-absolute-import, 99 | old-division, 100 | dict-iter-method, 101 | dict-view-method, 102 | next-method-called, 103 | metaclass-assignment, 104 | indexing-exception, 105 | raising-string, 106 | reload-builtin, 107 | oct-method, 108 | hex-method, 109 | nonzero-method, 110 | cmp-method, 111 | input-builtin, 112 | round-builtin, 113 | intern-builtin, 114 | unichr-builtin, 115 | map-builtin-not-iterating, 116 | zip-builtin-not-iterating, 117 | range-builtin-not-iterating, 118 | filter-builtin-not-iterating, 119 | using-cmp-argument, 120 | eq-without-hash, 121 | div-method, 122 | idiv-method, 123 | rdiv-method, 124 | exception-message-attribute, 125 | invalid-str-codec, 126 | sys-max-int, 127 | bad-python3-import, 128 | deprecated-string-function, 129 | deprecated-str-translate-call, 130 | deprecated-itertools-function, 131 | deprecated-types-field, 132 | next-method-defined, 133 | dict-items-not-iterating, 134 | dict-keys-not-iterating, 135 | dict-values-not-iterating, 136 | deprecated-operator-function, 137 | deprecated-urllib-function, 138 | xreadlines-attribute, 139 | deprecated-sys-function, 140 | exception-escape, 141 | comprehension-escape 142 | 143 | # Enable the message, report, category or checker with the given id(s). You can 144 | # either give multiple identifier separated by comma (,) or put this option 145 | # multiple time (only on the command line, not in the configuration file where 146 | # it should appear only once). See also the "--disable" option for examples. 147 | enable=c-extension-no-member 148 | 149 | 150 | [REPORTS] 151 | 152 | # Python expression which should return a score less than or equal to 10. You 153 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 154 | # which contain the number of messages in each category, as well as 'statement' 155 | # which is the total number of statements analyzed. This score is used by the 156 | # global evaluation report (RP0004). 157 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 158 | 159 | # Template used to display messages. This is a python new-style format string 160 | # used to format the message information. See doc for all details. 161 | #msg-template= 162 | 163 | # Set the output format. Available formats are text, parseable, colorized, json 164 | # and msvs (visual studio). You can also give a reporter class, e.g. 165 | # mypackage.mymodule.MyReporterClass. 166 | output-format=text 167 | 168 | # Tells whether to display a full report or only the messages. 169 | reports=no 170 | 171 | # Activate the evaluation score. 172 | score=yes 173 | 174 | 175 | [REFACTORING] 176 | 177 | # Maximum number of nested blocks for function / method body 178 | max-nested-blocks=5 179 | 180 | # Complete name of functions that never returns. When checking for 181 | # inconsistent-return-statements if a never returning function is called then 182 | # it will be considered as an explicit return statement and no message will be 183 | # printed. 184 | never-returning-functions=sys.exit 185 | 186 | 187 | [BASIC] 188 | 189 | # Naming style matching correct argument names. 190 | argument-naming-style=camelCase 191 | 192 | # Regular expression matching correct argument names. Overrides argument- 193 | # naming-style. 194 | #argument-rgx= 195 | 196 | # Naming style matching correct attribute names. 197 | attr-naming-style=camelCase 198 | 199 | # Regular expression matching correct attribute names. Overrides attr-naming- 200 | # style. 201 | #attr-rgx= 202 | 203 | # Bad variable names which should always be refused, separated by a comma. 204 | bad-names=foo, 205 | bar, 206 | baz, 207 | toto, 208 | tutu, 209 | tata 210 | 211 | # Bad variable names regexes, separated by a comma. If names match any regex, 212 | # they will always be refused 213 | bad-names-rgxs= 214 | 215 | # Naming style matching correct class attribute names. 216 | class-attribute-naming-style=any 217 | 218 | # Regular expression matching correct class attribute names. Overrides class- 219 | # attribute-naming-style. 220 | #class-attribute-rgx= 221 | 222 | # Naming style matching correct class names. 223 | class-naming-style=PascalCase 224 | 225 | # Regular expression matching correct class names. Overrides class-naming- 226 | # style. 227 | #class-rgx= 228 | 229 | # Naming style matching correct constant names. 230 | const-naming-style=UPPER_CASE 231 | 232 | # Regular expression matching correct constant names. Overrides const-naming- 233 | # style. 234 | #const-rgx= 235 | 236 | # Minimum line length for functions/classes that require docstrings, shorter 237 | # ones are exempt. 238 | docstring-min-length=-1 239 | 240 | # Naming style matching correct function names. 241 | function-naming-style=camelCase 242 | 243 | # Regular expression matching correct function names. Overrides function- 244 | # naming-style. 245 | #function-rgx= 246 | 247 | # Good variable names which should always be accepted, separated by a comma. 248 | good-names=i, 249 | j, 250 | k, 251 | ex, 252 | Run, 253 | _ 254 | 255 | # Good variable names regexes, separated by a comma. If names match any regex, 256 | # they will always be accepted 257 | good-names-rgxs= 258 | 259 | # Include a hint for the correct naming format with invalid-name. 260 | include-naming-hint=no 261 | 262 | # Naming style matching correct inline iteration names. 263 | inlinevar-naming-style=any 264 | 265 | # Regular expression matching correct inline iteration names. Overrides 266 | # inlinevar-naming-style. 267 | #inlinevar-rgx= 268 | 269 | # Naming style matching correct method names. 270 | method-naming-style=camelCase 271 | 272 | # Regular expression matching correct method names. Overrides method-naming- 273 | # style. 274 | #method-rgx= 275 | 276 | # Naming style matching correct module names. 277 | module-naming-style=camelCase 278 | 279 | # Regular expression matching correct module names. Overrides module-naming- 280 | # style. 281 | #module-rgx= 282 | 283 | # Colon-delimited sets of names that determine each other's naming style when 284 | # the name regexes allow several styles. 285 | name-group= 286 | 287 | # Regular expression which should only match function or class names that do 288 | # not require a docstring. 289 | no-docstring-rgx=^_ 290 | 291 | # List of decorators that produce properties, such as abc.abstractproperty. Add 292 | # to this list to register other decorators that produce valid properties. 293 | # These decorators are taken in consideration only for invalid-name. 294 | property-classes=abc.abstractproperty 295 | 296 | # Naming style matching correct variable names. 297 | variable-naming-style=camelCase 298 | 299 | # Regular expression matching correct variable names. Overrides variable- 300 | # naming-style. 301 | #variable-rgx= 302 | 303 | 304 | [FORMAT] 305 | 306 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 307 | expected-line-ending-format= 308 | 309 | # Regexp for a line that is allowed to be longer than the limit. 310 | ignore-long-lines=^\s*(# )?<?https?://\S+>?$ 311 | 312 | # Number of spaces of indent required inside a hanging or continued line. 313 | indent-after-paren=4 314 | 315 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 316 | # tab). 317 | indent-string=' ' 318 | 319 | # Maximum number of characters on a single line. 320 | max-line-length=100 321 | 322 | # Maximum number of lines in a module. 323 | max-module-lines=1000 324 | 325 | # Allow the body of a class to be on the same line as the declaration if body 326 | # contains single statement. 327 | single-line-class-stmt=no 328 | 329 | # Allow the body of an if to be on the same line as the test if there is no 330 | # else. 331 | single-line-if-stmt=no 332 | 333 | 334 | [LOGGING] 335 | 336 | # The type of string formatting that logging methods do. `old` means using % 337 | # formatting, `new` is for `{}` formatting. 338 | logging-format-style=old 339 | 340 | # Logging modules to check that the string format arguments are in logging 341 | # function parameter format. 342 | logging-modules=logging 343 | 344 | 345 | [MISCELLANEOUS] 346 | 347 | # List of note tags to take in consideration, separated by a comma. 348 | notes=FIXME, 349 | XXX, 350 | TODO 351 | 352 | # Regular expression of note tags to take in consideration. 353 | #notes-rgx= 354 | 355 | 356 | [SIMILARITIES] 357 | 358 | # Ignore comments when computing similarities. 359 | ignore-comments=yes 360 | 361 | # Ignore docstrings when computing similarities. 362 | ignore-docstrings=yes 363 | 364 | # Ignore imports when computing similarities. 365 | ignore-imports=no 366 | 367 | # Minimum lines number of a similarity. 368 | min-similarity-lines=4 369 | 370 | 371 | [SPELLING] 372 | 373 | # Limits count of emitted suggestions for spelling mistakes. 374 | max-spelling-suggestions=4 375 | 376 | # Spelling dictionary name. Available dictionaries: none. To make it work, 377 | # install the python-enchant package. 378 | spelling-dict= 379 | 380 | # List of comma separated words that should not be checked. 381 | spelling-ignore-words= 382 | 383 | # A path to a file that contains the private dictionary; one word per line. 384 | spelling-private-dict-file= 385 | 386 | # Tells whether to store unknown words to the private dictionary (see the 387 | # --spelling-private-dict-file option) instead of raising a message. 388 | spelling-store-unknown-words=no 389 | 390 | 391 | [STRING] 392 | 393 | # This flag controls whether inconsistent-quotes generates a warning when the 394 | # character used as a quote delimiter is used inconsistently within a module. 395 | check-quote-consistency=no 396 | 397 | # This flag controls whether the implicit-str-concat should generate a warning 398 | # on implicit string concatenation in sequences defined over several lines. 399 | check-str-concat-over-line-jumps=no 400 | 401 | 402 | [TYPECHECK] 403 | 404 | # List of decorators that produce context managers, such as 405 | # contextlib.contextmanager. Add to this list to register other decorators that 406 | # produce valid context managers. 407 | contextmanager-decorators=contextlib.contextmanager 408 | 409 | # List of members which are set dynamically and missed by pylint inference 410 | # system, and so shouldn't trigger E1101 when accessed. Python regular 411 | # expressions are accepted. 412 | generated-members= 413 | 414 | # Tells whether missing members accessed in mixin class should be ignored. A 415 | # mixin class is detected if its name ends with "mixin" (case insensitive). 416 | ignore-mixin-members=yes 417 | 418 | # Tells whether to warn about missing members when the owner of the attribute 419 | # is inferred to be None. 420 | ignore-none=yes 421 | 422 | # This flag controls whether pylint should warn about no-member and similar 423 | # checks whenever an opaque object is returned when inferring. The inference 424 | # can return multiple potential results while evaluating a Python object, but 425 | # some branches might not be evaluated, which results in partial inference. In 426 | # that case, it might be useful to still emit no-member and other checks for 427 | # the rest of the inferred objects. 428 | ignore-on-opaque-inference=yes 429 | 430 | # List of class names for which member attributes should not be checked (useful 431 | # for classes with dynamically set attributes). This supports the use of 432 | # qualified names. 433 | ignored-classes=optparse.Values,thread._local,_thread._local 434 | 435 | # List of module names for which member attributes should not be checked 436 | # (useful for modules/projects where namespaces are manipulated during runtime 437 | # and thus existing member attributes cannot be deduced by static analysis). It 438 | # supports qualified module names, as well as Unix pattern matching. 439 | ignored-modules= 440 | 441 | # Show a hint with possible names when a member name was not found. The aspect 442 | # of finding the hint is based on edit distance. 443 | missing-member-hint=yes 444 | 445 | # The minimum edit distance a name should have in order to be considered a 446 | # similar match for a missing member name. 447 | missing-member-hint-distance=1 448 | 449 | # The total number of similar names that should be taken in consideration when 450 | # showing a hint for a missing member. 451 | missing-member-max-choices=1 452 | 453 | # List of decorators that change the signature of a decorated function. 454 | signature-mutators= 455 | 456 | 457 | [VARIABLES] 458 | 459 | # List of additional names supposed to be defined in builtins. Remember that 460 | # you should avoid defining new builtins when possible. 461 | additional-builtins= 462 | 463 | # Tells whether unused global variables should be treated as a violation. 464 | allow-global-unused-variables=yes 465 | 466 | # List of strings which can identify a callback function by name. A callback 467 | # name must start or end with one of those strings. 468 | callbacks=cb_, 469 | _cb 470 | 471 | # A regular expression matching the name of dummy variables (i.e. expected to 472 | # not be used). 473 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 474 | 475 | # Argument names that match this expression will be ignored. Default to name 476 | # with leading underscore. 477 | ignored-argument-names=_.*|^ignored_|^unused_ 478 | 479 | # Tells whether we should check for unused import in __init__ files. 480 | init-import=no 481 | 482 | # List of qualified module names which can have objects that can redefine 483 | # builtins. 484 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 485 | 486 | 487 | [CLASSES] 488 | 489 | # List of method names used to declare (i.e. assign) instance attributes. 490 | defining-attr-methods=__init__, 491 | __new__, 492 | setUp, 493 | __post_init__ 494 | 495 | # List of member names, which should be excluded from the protected access 496 | # warning. 497 | exclude-protected=_asdict, 498 | _fields, 499 | _replace, 500 | _source, 501 | _make 502 | 503 | # List of valid names for the first argument in a class method. 504 | valid-classmethod-first-arg=cls 505 | 506 | # List of valid names for the first argument in a metaclass class method. 507 | valid-metaclass-classmethod-first-arg=cls 508 | 509 | 510 | [DESIGN] 511 | 512 | # Maximum number of arguments for function / method. 513 | max-args=5 514 | 515 | # Maximum number of attributes for a class (see R0902). 516 | max-attributes=7 517 | 518 | # Maximum number of boolean expressions in an if statement (see R0916). 519 | max-bool-expr=5 520 | 521 | # Maximum number of branch for function / method body. 522 | max-branches=12 523 | 524 | # Maximum number of locals for function / method body. 525 | max-locals=15 526 | 527 | # Maximum number of parents for a class (see R0901). 528 | max-parents=7 529 | 530 | # Maximum number of public methods for a class (see R0904). 531 | max-public-methods=20 532 | 533 | # Maximum number of return / yield for function / method body. 534 | max-returns=6 535 | 536 | # Maximum number of statements in function / method body. 537 | max-statements=50 538 | 539 | # Minimum number of public methods for a class (see R0903). 540 | min-public-methods=2 541 | 542 | 543 | [IMPORTS] 544 | 545 | # List of modules that can be imported at any level, not just the top level 546 | # one. 547 | allow-any-import-level= 548 | 549 | # Allow wildcard imports from modules that define __all__. 550 | allow-wildcard-with-all=no 551 | 552 | # Analyse import fallback blocks. This can be used to support both Python 2 and 553 | # 3 compatible code, which means that the block might have code that exists 554 | # only in one or another interpreter, leading to false positives when analysed. 555 | analyse-fallback-blocks=no 556 | 557 | # Deprecated modules which should not be used, separated by a comma. 558 | deprecated-modules=optparse,tkinter.tix 559 | 560 | # Create a graph of external dependencies in the given file (report RP0402 must 561 | # not be disabled). 562 | ext-import-graph= 563 | 564 | # Create a graph of every (i.e. internal and external) dependencies in the 565 | # given file (report RP0402 must not be disabled). 566 | import-graph= 567 | 568 | # Create a graph of internal dependencies in the given file (report RP0402 must 569 | # not be disabled). 570 | int-import-graph= 571 | 572 | # Force import order to recognize a module as part of the standard 573 | # compatibility libraries. 574 | known-standard-library= 575 | 576 | # Force import order to recognize a module as part of a third party library. 577 | known-third-party=enchant 578 | 579 | # Couples of modules and preferred modules, separated by a comma. 580 | preferred-modules= 581 | 582 | 583 | [EXCEPTIONS] 584 | 585 | # Exceptions that will emit a warning when being caught. Defaults to 586 | # "BaseException, Exception". 587 | overgeneral-exceptions=BaseException, 588 | Exception 589 | -------------------------------------------------------------------------------- /cogs/anime.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import random 3 | import re 4 | import asyncio 5 | import aiohttp 6 | import discord 7 | from discord.ext import commands 8 | import xml.etree.ElementTree as ET 9 | import loadconfig 10 | 11 | class anime(commands.Cog): 12 | '''Alles rund um Animes''' 13 | 14 | def __init__(self, bot): 15 | self.bot = bot 16 | 17 | async def cog_command_error(self, ctx, error): 18 | print('Error in {0.command.qualified_name}: {1}'.format(ctx, error)) 19 | 20 | def checkRole(self, user, roleRec): 21 | ok = False 22 | for all in list(user.roles): 23 | if all.name == roleRec: 24 | ok = True 25 | return ok 26 | 27 | @commands.command() 28 | async def kawaii(self, ctx): 29 | '''Gibt ein zufälliges kawaii Bild aus''' 30 | if loadconfig.__kawaiichannel__: 31 | pins = await self.bot.get_channel(loadconfig.__kawaiichannel__).pins() 32 | rnd = random.choice(pins) 33 | img = rnd.attachments[0].url 34 | emojis = [':blush:', ':flushed:', ':heart_eyes:', ':heart_eyes_cat:', ':heart:'] 35 | await ctx.send(f'{random.choice(emojis)} Von: {rnd.author.display_name}: {img}') 36 | else: 37 | await ctx.send('**:no_entry:** Es wurde kein Channel für den Bot eingestellt! Wende dich bitte an den Bot Admin') 38 | 39 | @commands.command(pass_context=True, hidden=True) 40 | async def nsfw(self, ctx): 41 | '''Vergibt die Rolle um auf die NSFW Channel zugreifen zu können''' 42 | if ctx.guild.id == loadconfig.__botserverid__: 43 | if loadconfig.__selfassignrole__: 44 | role = discord.utils.get(ctx.guild.roles, name=loadconfig.__selfassignrole__) 45 | if role in ctx.author.roles: 46 | try: 47 | await ctx.author.remove_roles(role) 48 | except: 49 | pass 50 | tmp = await ctx.send(f':x: Rolle **{role}** wurde entfernt') 51 | else: 52 | try: 53 | await ctx.author.add_roles(role) 54 | except: 55 | pass 56 | tmp = await ctx.send(f':white_check_mark: Rolle **{role}** wurde hinzugefügt') 57 | else: 58 | tmp = await ctx.send('**:no_entry:** Es wurde keine Rolle für den Bot eingestellt! Wende dich bitte an den Bot Admin') 59 | else: 60 | tmp = await ctx.send(f'**:no_entry:** This command don\'t work on this server!') 61 | await asyncio.sleep(2 * 60) 62 | await tmp.delete() 63 | await ctx.message.delete() 64 | 65 | @commands.command(aliases=['wave', 'hi', 'ohaiyo']) 66 | async def hello(self, ctx): 67 | '''Nonsense gifs zum Hallo sagen''' 68 | gifs = ['https://cdn.discordapp.com/attachments/102817255661772800/219512763607678976/large_1.gif', 69 | 'https://cdn.discordapp.com/attachments/102817255661772800/219512898563735552/large.gif', 70 | 'https://cdn.discordapp.com/attachments/102817255661772800/219518948251664384/WgQWD.gif', 71 | 'https://cdn.discordapp.com/attachments/102817255661772800/219518717426532352/tumblr_lnttzfSUM41qgcvsy.gif', 72 | 'https://cdn.discordapp.com/attachments/102817255661772800/219519191290478592/tumblr_mf76erIF6s1qj96p1o1_500.gif', 73 | 'https://cdn.discordapp.com/attachments/102817255661772800/219519729604231168/giphy_3.gif', 74 | 'https://cdn.discordapp.com/attachments/102817255661772800/219519737971867649/63953d32c650703cded875ac601e765778ce90d0_hq.gif', 75 | 'https://cdn.discordapp.com/attachments/102817255661772800/219519738781368321/17201a4342e901e5f1bc2a03ad487219c0434c22_hq.gif'] 76 | msg = f':wave: {random.choice(gifs)}' 77 | await ctx.send(msg) 78 | 79 | @commands.command(aliases=['nepu', 'topnep']) 80 | async def nep(self, ctx): 81 | '''Can't stop the Nep''' 82 | neps = ['https://cdn.discordapp.com/attachments/102817255661772800/219530759881359360/community_image_1421846157.gif', 83 | 'https://cdn.discordapp.com/attachments/102817255661772800/219535598187184128/tumblr_nv25gtvX911ubsb68o1_500.gif', 84 | 'https://cdn.discordapp.com/attachments/102817255661772800/219535698309545984/tumblr_mpub9tTuZl1rvrw2eo2_r1_500.gif', 85 | 'https://cdn.discordapp.com/attachments/102817255661772800/219535820430770176/dd9f3cc873f3e13fe098429388fc24242a545a21_hq.gif', 86 | 'https://cdn.discordapp.com/attachments/102817255661772800/219535828773371904/tumblr_nl62nrrPar1u0bcbmo1_500.gif', 87 | 'https://cdn.discordapp.com/attachments/102817255661772800/219535828995538944/dUBNqIH.gif', 88 | 'https://cdn.discordapp.com/attachments/102817255661772800/219535906942615553/b3886374588ec93849e1210449c4561fa699ff0d_hq.gif', 89 | 'https://cdn.discordapp.com/attachments/102817255661772800/219536353841381376/tumblr_nl9wb2qMFD1u3qei8o1_500.gif', 90 | 'https://cdn.discordapp.com/attachments/102817255661772800/219536345176080384/tumblr_njhahjh1DB1t0co30o1_500.gif', 91 | 'https://cdn.discordapp.com/attachments/102817255661772800/219536356223877120/tumblr_njkq53Roep1t0co30o1_500.gif', 92 | 'https://cdn.discordapp.com/attachments/102817255661772800/219536424121139210/tumblr_oalathnmFC1uskgfro1_400.gif', 93 | 'https://cdn.discordapp.com/attachments/102817255661772800/219536451807739904/tumblr_nfg22lqmZ31rjwa86o1_500.gif', 94 | 'https://cdn.discordapp.com/attachments/102817255661772800/219536686529380362/tumblr_o98bm76djb1vv3oz0o1_500.gif', 95 | 'https://cdn.discordapp.com/attachments/102817255661772800/219537181440475146/tumblr_mya4mdVhDv1rmk3cyo1_500.gif', 96 | 'https://i.imgur.com/4xnJN9x.png', 97 | 'https://i.imgur.com/bunWIWD.jpg'] 98 | nepnep = ['topnep', 99 | 'Can\'t pep the nep', 100 | 'Flat is justice', 101 | 'nep nep nep nep nep nep nep nep nep nep nep', 102 | 'Nepgear > your waifu'] 103 | msg = f'{random.choice(nepnep)} {random.choice(neps)}' 104 | await ctx.send(msg) 105 | 106 | @commands.command(aliases=['headpat']) 107 | async def pat(self, ctx, member: discord.Member = None): 108 | '''/r/headpats Pat Pat Pat :3 109 | 110 | Beispiel: 111 | ----------- 112 | 113 | :pat @Der-Eddy#6508 114 | ''' 115 | gifs = ['https://gfycat.com/PoisedWindingCaecilian', 116 | 'https://cdn.awwni.me/sou1.jpg', 117 | 'https://i.imgur.com/Nzxa95W.gifv', 118 | 'https://cdn.awwni.me/sk0x.png', 119 | 'https://i.imgur.com/N0UIRkk.png', 120 | 'https://cdn.awwni.me/r915.jpg', 121 | 'https://i.imgur.com/VRViMGf.gifv', 122 | 'https://i.imgur.com/73dNfOk.gifv', 123 | 'https://i.imgur.com/UXAKjRc.jpg', 124 | 'https://i.imgur.com/dzlDuNs.jpg', 125 | 'https://i.imgur.com/hPR7SOt.gif', 126 | 'https://i.imgur.com/IqGRUu4.gif', 127 | 'https://68.media.tumblr.com/f95f14437809dfec8057b2bd525e6b4a/tumblr_omvkl2SzeK1ql0375o1_500.gif', 128 | 'https://i.redd.it/0ffv8i3p1vrz.jpg', 129 | 'http://i.imgur.com/3dzA6OU.png', 130 | 'http://i.imgur.com/vkFKabZ.jpg', 131 | 'https://i.imgur.com/Lb4p20s.jpg', 132 | 'https://cdn.awwni.me/snot.jpg', 133 | 'https://i.imgur.com/5yEOa6u.jpg', 134 | 'https://i.redd.it/dc7oebkfsetz.jpg'] 135 | 136 | if member == ctx.me: 137 | msg = f'Arigato {ctx.author.mention} <:Hiding:322410632517517324> \n{random.choice(gifs)}' 138 | await ctx.send(msg) 139 | elif member is not None: 140 | msg = f'{ctx.author.mention} tätschelt dich {member.mention} :3 \n{random.choice(gifs)}' 141 | await ctx.send(msg) 142 | 143 | @commands.command(aliases=['rate', 'waifu']) 144 | async def ratewaifu(self, ctx, *, waifuName: str): 145 | '''Rate my waifu 146 | 147 | Beispiel: 148 | ----------- 149 | 150 | :ratewaifu Sagiri 151 | ''' 152 | waifu = waifuName.lower() 153 | bestWaifus = ['kobeni', 'emilia', 'shinobu', 'karen', 'shouko', 'shoko', 154 | 'minori', 'chidori', 'sagiri', 'mashiro', 'last order', 155 | 'saki', 'makoto', 'yui', 'nep', 'nepgear', 'taiga'] 156 | trashWaifus = ['shino', 'rikka'] 157 | #this lists are highly biased, but who cares ¯\_(ツ)_/¯ 158 | if waifu in bestWaifus: 159 | rating = 10 160 | elif waifu in trashWaifus: 161 | rating = 0 162 | else: 163 | rating = hash(waifu) % 10 164 | 165 | if waifu == 'emilia': 166 | emoji = '<:Emilia:230684388084416512>' 167 | elif waifu == 'shinobu': 168 | emoji = '<:Shinobu:303302053688770561>' 169 | elif waifu == 'mashiro': 170 | emoji = '<:mashiro:266233568626343936>' 171 | elif waifu == 'sagiri': 172 | emoji = '<:Sagiri:407630432319045634>' 173 | elif waifu == 'nep' or waifu == 'neptunia' or waifu == 'nepgear': 174 | emoji = '<:nep:261230988758220822>' 175 | elif rating < 2: 176 | emoji = ':put_litter_in_its_place:' 177 | elif rating < 5: 178 | emoji = '<:k3llyLUL:341946977266827264>' 179 | elif rating < 7: 180 | emoji = '<:k3llyTHINK:341946932639432704>' 181 | elif rating < 9: 182 | emojis = ['<:faeGasm:298772756412104704>', '<:naroGasm:341200647741243393>'] 183 | emoji = random.choice(emojis) 184 | elif rating < 10: 185 | emojis = ['<:kanoLewd:230662559458525185>', '<:fowShy:230662561580843008>', '<:mendoLewd:230662561169801216>'] 186 | emoji = random.choice(emojis) 187 | elif rating == 10: 188 | emojis = ['<:okhand:335170448666918923>', '<:nepnep:314906910061101057>', '<:gaku:249970768786489345>', '<:faeWant:313430419661914113>'] 189 | emoji = random.choice(emojis) 190 | 191 | msg = f'{emoji} Ich bewerte **{waifuName}** als **{rating}/10**' 192 | await ctx.send(msg) 193 | 194 | @commands.command(aliases=['anilist']) 195 | async def anime(self, ctx, *, animeName: str): 196 | '''Sucht auf AniList.co nach einem Anime und gibt die Basis-Informationen zurück 197 | 198 | Beispiel: 199 | ----------- 200 | 201 | :anime Mushishi 202 | ''' 203 | api = 'https://graphql.anilist.co' 204 | query = ''' 205 | query ($name: String){ 206 | Media(search: $name, type: ANIME) { 207 | id 208 | idMal 209 | description 210 | title { 211 | romaji 212 | english 213 | } 214 | coverImage { 215 | large 216 | } 217 | startDate { 218 | year 219 | month 220 | day 221 | } 222 | endDate { 223 | year 224 | month 225 | day 226 | } 227 | synonyms 228 | format 229 | status 230 | episodes 231 | duration 232 | nextAiringEpisode { 233 | episode 234 | } 235 | averageScore 236 | meanScore 237 | source 238 | genres 239 | tags { 240 | name 241 | } 242 | studios(isMain: true) { 243 | nodes { 244 | name 245 | } 246 | } 247 | siteUrl 248 | } 249 | } 250 | ''' 251 | variables = { 252 | 'name': animeName 253 | } 254 | 255 | async with aiohttp.ClientSession() as session: 256 | async with session.post(api, json={'query': query, 'variables': variables}, headers = self.bot.userAgentHeaders) as r: 257 | if r.status == 200: 258 | json = await r.json() 259 | data = json['data']['Media'] 260 | 261 | embed = discord.Embed(color=ctx.author.top_role.colour) 262 | embed.set_footer(text='API provided by AniList.co | ID: {}'.format(str(data['id']))) 263 | embed.set_thumbnail(url=data['coverImage']['large']) 264 | if data['title']['english'] == None or data['title']['english'] == data['title']['romaji']: 265 | embed.add_field(name='Titel', value=data['title']['romaji'], inline=False) 266 | else: 267 | embed.add_field(name='Titel', value='{} ({})'.format(data['title']['english'], data['title']['romaji']), inline=False) 268 | 269 | #embed.add_field(name='Beschreibung', value=data['description'], inline=False) 270 | if data['synonyms'] != []: 271 | embed.add_field(name='Synonyme', value=', '.join(data['synonyms']), inline=True) 272 | 273 | embed.add_field(name='Typ', value=data['format'].replace('_', ' ').title().replace('Tv', 'TV'), inline=True) 274 | if data['episodes'] > 1: 275 | embed.add_field(name='Folgen', value='{} à {} min'.format(data['episodes'], data['duration']), inline=True) 276 | else: 277 | embed.add_field(name='Dauer', value=str(data['duration']) + ' min', inline=True) 278 | 279 | embed.add_field(name='Gestartet', value='{}.{}.{}'.format(data['startDate']['day'], data['startDate']['month'], data['startDate']['year']), inline=True) 280 | if data['endDate']['day'] == None: 281 | embed.add_field(name='Released Folgen', value=data['nextAiringEpisode']['episode'] - 1, inline=True) 282 | elif data['episodes'] > 1: 283 | embed.add_field(name='Beendet', value='{}.{}.{}'.format(data['endDate']['day'], data['endDate']['month'], data['endDate']['year']), inline=True) 284 | 285 | embed.add_field(name='Status', value=data['status'].replace('_', ' ').title(), inline=True) 286 | 287 | try: 288 | embed.add_field(name='Haupt-Studio', value=data['studios']['nodes'][0]['name'], inline=True) 289 | except IndexError: 290 | pass 291 | embed.add_field(name='Ø Score', value=data['averageScore'], inline=True) 292 | embed.add_field(name='Genres', value=', '.join(data['genres']), inline=False) 293 | tags = '' 294 | for tag in data['tags']: 295 | tags += tag['name'] + ', ' 296 | embed.add_field(name='Tags', value=tags[:-2], inline=False) 297 | try: 298 | embed.add_field(name='Adaptiert von', value=data['source'].replace('_', ' ').title(), inline=True) 299 | except AttributeError: 300 | pass 301 | 302 | embed.add_field(name='AniList Link', value=data['siteUrl'], inline=False) 303 | embed.add_field(name='MyAnimeList Link', value='https://myanimelist.net/anime/' + str(data['idMal']), inline=False) 304 | await ctx.send(embed=embed) 305 | 306 | else: 307 | await ctx.send(':x: Konnte keinen passenden Anime finden!') 308 | 309 | @commands.command() 310 | async def manga(self, ctx, *, mangaName: str): 311 | '''Sucht auf AniList.co nach einem Manga und gibt die Basis-Informationen zurück 312 | 313 | Beispiel: 314 | ----------- 315 | 316 | :manga Air Gear 317 | ''' 318 | api = 'https://graphql.anilist.co' 319 | query = ''' 320 | query ($name: String){ 321 | Media(search: $name, type: MANGA) { 322 | id 323 | idMal 324 | description 325 | title { 326 | romaji 327 | english 328 | } 329 | coverImage { 330 | large 331 | } 332 | startDate { 333 | year 334 | month 335 | day 336 | } 337 | endDate { 338 | year 339 | month 340 | day 341 | } 342 | status 343 | chapters 344 | volumes 345 | averageScore 346 | meanScore 347 | genres 348 | tags { 349 | name 350 | } 351 | siteUrl 352 | } 353 | } 354 | ''' 355 | variables = { 356 | 'name': mangaName 357 | } 358 | 359 | async with aiohttp.ClientSession() as session: 360 | async with session.post(api, json={'query': query, 'variables': variables}, headers = self.bot.userAgentHeaders) as r: 361 | if r.status == 200: 362 | json = await r.json() 363 | data = json['data']['Media'] 364 | 365 | embed = discord.Embed(color=ctx.author.top_role.colour) 366 | embed.set_footer(text='API provided by AniList.co | ID: {}'.format(str(data['id']))) 367 | embed.set_thumbnail(url=data['coverImage']['large']) 368 | if data['title']['english'] == None or data['title']['english'] == data['title']['romaji']: 369 | embed.add_field(name='Titel', value=data['title']['romaji'], inline=False) 370 | else: 371 | embed.add_field(name='Titel', value='{} ({})'.format(data['title']['english'], data['title']['romaji']), inline=False) 372 | #embed.add_field(name='Beschreibung', value=data['description'], inline=False) 373 | if data['chapters'] != None: 374 | # https://github.com/AniList/ApiV2-GraphQL-Docs/issues/47 375 | embed.add_field(name='Kapitel', value=data['chapters'], inline=True) 376 | if data['volumes'] != None: 377 | embed.add_field(name='Bände', value=data['volumes'], inline=True) 378 | embed.add_field(name='Gestartet', value='{}.{}.{}'.format(data['startDate']['day'], data['startDate']['month'], data['startDate']['year']), inline=True) 379 | if data['endDate']['day'] != None: 380 | embed.add_field(name='Beendet', value='{}.{}.{}'.format(data['endDate']['day'], data['endDate']['month'], data['endDate']['year']), inline=True) 381 | embed.add_field(name='Status', value=data['status'].replace('_', ' ').title(), inline=True) 382 | embed.add_field(name='Ø Score', value=data['averageScore'], inline=True) 383 | embed.add_field(name='Genres', value=', '.join(data['genres']), inline=False) 384 | tags = '' 385 | for tag in data['tags']: 386 | tags += tag['name'] + ', ' 387 | embed.add_field(name='Tags', value=tags[:-2], inline=False) 388 | embed.add_field(name='AniList Link', value=data['siteUrl'], inline=False) 389 | embed.add_field(name='MyAnimeList Link', value='https://myanimelist.net/anime/' + str(data['idMal']), inline=False) 390 | await ctx.send(embed=embed) 391 | 392 | else: 393 | await ctx.send(':x: Konnte keinen passenden Manga finden!') 394 | 395 | @commands.command(aliases=['sauce', 'iqdb']) 396 | async def saucenao(self, ctx, url: str = None): 397 | '''Versucht die Quelle eines Anime Bildes zu finden 398 | 399 | Beispiel: 400 | ----------- 401 | 402 | :saucenao 403 | 404 | :saucenao https://i.imgur.com/nmnVtgs.jpg 405 | ''' 406 | 407 | if url == None: 408 | async for message in ctx.channel.history(before=ctx.message): 409 | try: 410 | url = message.attachments[0].url 411 | continue 412 | except IndexError: 413 | pass 414 | elif not url.endswith(('.jpg', '.png', '.bmp', '.gif', '.jpeg')): 415 | await ctx.send(':x: Keine korrekte URL angegeben!') 416 | return 417 | 418 | tmp = await ctx.send(f'Versuche die Quelle des Bildes <{url}> zu finden ...') 419 | saucenao = f'http://saucenao.com/search.php?db=999&url={url}' 420 | async with aiohttp.ClientSession(headers = self.bot.userAgentHeaders) as cs: 421 | async with cs.get(saucenao) as r: 422 | #Thanks to https://github.com/MistressMamiya/hsauce_bot/blob/master/get_source.py 423 | content = await r.text() 424 | content = content.split('Low similarity results')[0] # Get rid of the low similarity results 425 | artist = re.search(r'<strong>Creator: <\/strong>(.*?)<br', content) 426 | anime = re.search(r'<strong>Material: <\/strong>(.*?)<br', content) 427 | characters = re.search(r'<strong>Characters: <\/strong><br \/>(.*?)<br \/></div>', content) 428 | pixiv = re.search(r'<strong>Pixiv ID: </strong><a href=\"(.*?)\" class', content) 429 | danbooru = re.search(r'<a href=\"https://danbooru\.donmai\.us/post/show/(\d+)\">', content) 430 | gelbooru = re.search(r'<a href=\"https://gelbooru\.com/index\.php\?page=post&s=view&id=(\d+)\">', content) 431 | yandere = re.search(r'<a href=\"https://yande\.re/post/show/(\d+)\">', content) 432 | konachan = re.search(r'<a href=\"http://konachan\.com/post/show/(\d+)\">', content) 433 | sankaku = re.search(r'<a href=\"https://chan\.sankakucomplex\.com/post/show/(\d+)\">', content) 434 | 435 | embed = discord.Embed() 436 | embed.set_footer(text='Provided by https://saucenao.com') 437 | embed.set_thumbnail(url=url) 438 | if anime: 439 | embed.add_field(name='Anime', value=anime.group(1), inline=True) 440 | if artist: 441 | embed.add_field(name='Artist', value=artist.group(1), inline=True) 442 | if characters: 443 | embed.add_field(name='Charaktere', value=str(characters.group(1)).replace('<br />', ', '), inline=True) 444 | if pixiv: 445 | embed.add_field(name='Pixiv Link', value=pixiv.group(1), inline=False) 446 | if danbooru: 447 | embed.add_field(name='Danbooru Link', value='https://danbooru.donmai.us/post/show/' + danbooru.group(1), inline=False) 448 | if gelbooru: 449 | embed.add_field(name='Gelbooru Link', value='https://gelbooru.com/index.php?page=post&s=view&id=' + gelbooru.group(1), inline=False) 450 | if yandere: 451 | embed.add_field(name='Yande.re Link', value='https://yande.re/post/show/' + yandere.group(1), inline=False) 452 | if konachan: 453 | embed.add_field(name='Konachan Link', value='http://konachan.com/post/show/' + konachan.group(1), inline=False) 454 | if sankaku: 455 | embed.add_field(name='Sankaku Link', value='https://chan.sankakucomplex.com/post/show/' + sankaku.group(1), inline=False) 456 | 457 | if anime or artist or characters or pixiv or danbooru or gelbooru or yandere or konachan or sankaku: 458 | await tmp.edit(content='', embed=embed) 459 | else: 460 | await tmp.edit(content=':x: Konnte nichts finden!') 461 | 462 | # @commands.command(pass_context=True, hidden=True) 463 | # async def imgur(self, ctx, amount: int = None): 464 | # '''Lädt eine bestimmte Anzahl der letzten hochgeladenen Bilder im Channel bei Imgur hoch''' 465 | # await ctx.send(':new: Befehl in Arbeit!') 466 | # 467 | # @commands.command(pass_context=True, alias=['ani'], hidden=True) 468 | # async def anisearch(self, ctx, url: str = None): 469 | # '''Gibt Informationen über einen AniSearch.de User zurück''' 470 | # async with aiohttp.get(url) as r: 471 | # if r.status == 200: 472 | # content = await r.text() 473 | # animeRE = r"<td class=\"rtype2\">\w+</td><td>(\d+)</td>" 474 | # watchedAnimes = re.search(content, animeRE) 475 | # await ctx.send(str(watchedAnimes.group(0))) 476 | # else: 477 | # await ctx.send(':x: Konnte den Benutzer nicht finden (falsche URL?)') 478 | 479 | async def setup(bot): 480 | await bot.add_cog(anime(bot)) 481 | -------------------------------------------------------------------------------- /cogs/utility.py: -------------------------------------------------------------------------------- 1 | import time 2 | import os 3 | import platform 4 | import re 5 | import asyncio 6 | import inspect 7 | import textwrap 8 | from datetime import datetime, timedelta 9 | from collections import Counter 10 | import aiohttp 11 | import discord 12 | from discord.ext import commands 13 | from PIL import Image, ImageDraw, ImageFont 14 | from pytz import timezone 15 | import loadconfig 16 | 17 | #Stolen from https://github.com/Rapptz/RoboDanny/blob/b513a32dfbd4fdbd910f7f56d88d1d012ab44826/cogs/meta.py 18 | class TimeParser: 19 | def __init__(self, argument): 20 | compiled = re.compile(r"(?:(?P<hours>[0-9]{1,5})h)?(?:(?P<minutes>[0-9]{1,5})m)?(?:(?P<seconds>[0-9]{1,5})s)?$") 21 | self.original = argument 22 | try: 23 | self.seconds = int(argument) 24 | except ValueError as e: 25 | match = compiled.match(argument) 26 | if match is None or not match.group(0): 27 | raise commands.BadArgument('Falsche Zeit angegeben, gültig sind z.B. `4h`, `3m` oder `2s`') from e 28 | 29 | self.seconds = 0 30 | hours = match.group('hours') 31 | if hours is not None: 32 | self.seconds += int(hours) * 3600 33 | minutes = match.group('minutes') 34 | if minutes is not None: 35 | self.seconds += int(minutes) * 60 36 | seconds = match.group('seconds') 37 | if seconds is not None: 38 | self.seconds += int(seconds) 39 | 40 | if self.seconds <= 0: 41 | raise commands.BadArgument('Zu wenig Zeit angegeben, gültig sind z.B. `4h`, `3m` oder `2s`') 42 | 43 | if self.seconds > 604800: # 7 days 44 | raise commands.BadArgument('7 Tage sind ne lange Zeit, denkste du nicht auch?') 45 | 46 | @staticmethod 47 | def human_timedelta(dt): 48 | now = datetime.now(timezone(loadconfig.__timezone__)) 49 | delta = now - dt 50 | hours, remainder = divmod(int(delta.total_seconds()), 3600) 51 | minutes, seconds = divmod(remainder, 60) 52 | days, hours = divmod(hours, 24) 53 | years, days = divmod(days, 365) 54 | 55 | if days: 56 | if hours: 57 | return '%s und %s' % (Plural(Tag=days), Plural(Stunde=hours)) 58 | return Plural(day=days) 59 | 60 | if hours: 61 | if minutes: 62 | return '%s und %s' % (Plural(Stunde=hours), Plural(Minute=minutes)) 63 | return Plural(hour=hours) 64 | 65 | if minutes: 66 | if seconds: 67 | return '%s und %s' % (Plural(Minute=minutes), Plural(Sekunde=seconds)) 68 | return Plural(Minute=minutes) 69 | return Plural(Sekunde=seconds) 70 | 71 | class Plural: 72 | def __init__(self, **attr): 73 | iterator = attr.items() 74 | self.name, self.value = next(iter(iterator)) 75 | 76 | def __str__(self): 77 | v = self.value 78 | if v > 1: 79 | return '%s %sn' % (v, self.name) 80 | return '%s %s' % (v, self.name) 81 | 82 | class utility(commands.Cog): 83 | '''Allgemeine/nützliche Befehle welche nirgendwo sonst reinpassen''' 84 | 85 | def __init__(self, bot): 86 | self.bot = bot 87 | 88 | async def cog_command_error(self, ctx, error): 89 | print('Error in {0.command.qualified_name}: {1}'.format(ctx, error)) 90 | 91 | @staticmethod 92 | def _newImage(width, height, color): 93 | return Image.new("L", (width, height), color) 94 | 95 | @staticmethod 96 | def _getRoles(roles): 97 | string = '' 98 | for role in roles[::-1]: 99 | if not role.is_default(): 100 | string += f'{role.mention}, ' 101 | if string == '': 102 | return 'None' 103 | else: 104 | return string[:-2] 105 | 106 | @staticmethod 107 | def _getEmojis(emojis): 108 | string = '' 109 | for emoji in emojis: 110 | string += str(emoji) 111 | if string == '': 112 | return 'None' 113 | else: 114 | return string[:1000] #The maximum allowed charcter amount for embed fields 115 | 116 | @commands.command(aliases=['uptime', 'up']) 117 | async def status(self, ctx): 118 | '''Infos über den Bot''' 119 | timeUp = time.time() - self.bot.startTime 120 | hours = timeUp / 3600 121 | minutes = (timeUp / 60) % 60 122 | seconds = timeUp % 60 123 | 124 | admin = self.bot.AppInfo.owner 125 | users = 0 126 | channel = 0 127 | if len(self.bot.commands_used.items()): 128 | commandsChart = sorted(self.bot.commands_used.items(), key=lambda t: t[1], reverse=False) 129 | topCommand = commandsChart.pop() 130 | commandsInfo = '{} (Top-Command: {} x {})'.format(sum(self.bot.commands_used.values()), topCommand[1], topCommand[0]) 131 | else: 132 | commandsInfo = str(sum(self.bot.commands_used.values())) 133 | for guild in self.bot.guilds: 134 | users += len(guild.members) 135 | channel += len(guild.channels) 136 | 137 | embed = discord.Embed(color=ctx.me.top_role.colour) 138 | embed.set_footer(text='Dieser Bot ist Open-Source auf GitHub: https://github.com/Der-Eddy/discord_bot') 139 | embed.set_thumbnail(url=ctx.me.avatar.url) 140 | embed.add_field(name='Admin', value=admin, inline=False) 141 | embed.add_field(name='Uptime', value='{0:.0f} Stunden, {1:.0f} Minuten und {2:.0f} Sekunden\n'.format(hours, minutes, seconds), inline=False) 142 | embed.add_field(name='Beobachtete Benutzer', value=users, inline=True) 143 | embed.add_field(name='Beobachtete Server', value=len(self.bot.guilds), inline=True) 144 | embed.add_field(name='Beobachtete Channel', value=channel, inline=True) 145 | embed.add_field(name='Ausgeführte Commands', value=commandsInfo, inline=True) 146 | embed.add_field(name='Bot Version', value=self.bot.botVersion, inline=True) 147 | embed.add_field(name='Discord.py Version', value=discord.__version__, inline=True) 148 | embed.add_field(name='Python Version', value=platform.python_version(), inline=True) 149 | embed.add_field(name='Docker', value=str(self.bot.docker), inline=True) 150 | # embed.add_field(name='Speicher Auslastung', value=f'{round(memory_usage(-1)[0], 3)} MB', inline=True) 151 | embed.add_field(name='Betriebssystem', value=f'{platform.system()} {platform.release()} {platform.version()}', inline=False) 152 | await ctx.send('**:information_source:** Informationen über diesen Bot:', embed=embed) 153 | 154 | @commands.command() 155 | async def ping(self, ctx): 156 | '''Misst die Response Time''' 157 | ping = ctx.message 158 | pong = await ctx.send('**:ping_pong:** Pong!') 159 | delta = pong.created_at - ping.created_at 160 | delta = int(delta.total_seconds() * 1000) 161 | await pong.edit(content=f':ping_pong: Pong! ({delta} ms)\n*Discord WebSocket Latenz: {round(self.bot.latency, 5)} ms*') 162 | 163 | # @commands.command() 164 | # @commands.cooldown(1, 2, commands.cooldowns.BucketType.guild) 165 | # async def github(self, ctx): 166 | # '''In progress''' 167 | # url = 'https://api.github.com/repos/Der-Eddy/discord_bot/stats/commit_activity' 168 | # async with aiohttp.get(url) as r: 169 | # if r.status == 200: 170 | # content = await r.json() 171 | # commitCount = 0 172 | # for week in content: 173 | # commitCount += week['total'] 174 | # 175 | # embed = discord.Embed(title='GitHub Repo Stats', type='rich', color=0xf1c40f) #Golden 176 | # embed.set_thumbnail(url='https://assets-cdn.github.com/images/modules/logos_page/GitHub-Mark.png') 177 | # embed.add_field(name='Commits', value=commitCount, inline=True) 178 | # embed.add_field(name='Link', value='https://github.com/Der-Eddy/discord_bot') 179 | # await ctx.send(embed=embed) 180 | # else: 181 | # await ctx.send(':x: Konnte nicht aufs GitHub API zugreifen\nhttps://github.com/Der-Eddy/discord_bot') 182 | 183 | @commands.command(aliases=['info']) 184 | async def about(self, ctx): 185 | '''Info über mich''' 186 | msg = 'Shinobu Oshino gehört wohl zu den mysteriösesten Charakteren in Bakemonogatari. Sie war bis vorletzten Frühling ein hochangesehener, adeliger, skrupelloser Vampir, der weit über 500 Jahre alt ist. Gnadenlos griff sie Menschen an und massakrierte sie nach Belieben. Auch Koyomi Araragi wurde von ihr attackiert und schwer verwundet. Nur durch das Eingreifen des Exorzisten Meme Oshino konnte Kiss-shot Acerola-orion Heart-under-blade, wie sie damals bekannt war, bezwungen werden. Dabei verlor sie jedoch all ihre Erinnerungen und wurde von einer attraktiven, erwachsenen Frau in einen unschuldigen Mädchenkörper verwandelt.\n\n' 187 | msg += 'Seitdem lebt sie zusammen mit Meme in einem verlassenen Gebäude und wurde von ihm aufgenommen. Er gab ihr auch ihren Namen Shinobu. Das Vampirblut in ihr verlangt immer noch nach Opfern und da sich Koyomi in gewisser Art und Weise schuldig fühlt, stellt er sich regelmäßig als Nahrungsquelle für Shinobu zur Verfügung.\n\n' 188 | msg += 'Quelle: http://www.anisearch.de/character/6598,shinobu-oshino/\n\n' 189 | 190 | embed = discord.Embed(color=ctx.me.top_role.colour) 191 | embed.set_footer(text='Dieser Bot ist außerdem free, Open-Source, in Python und mit Hilfe von discord.py geschrieben! https://github.com/Der-Eddy/discord_bot\n') 192 | embed.set_thumbnail(url=ctx.me.avatar.url) 193 | embed.add_field(name='**:information_source: Shinobu Oshino (500 Jahre alt)**', value=msg, inline=False) 194 | await ctx.send(embed=embed) 195 | 196 | @commands.command(aliases=['archive']) 197 | @commands.cooldown(1, 60, commands.cooldowns.BucketType.channel) 198 | async def log(self, ctx, *limit: int): 199 | '''Archiviert den Log des derzeitigen Channels und läd diesen als Attachment hoch 200 | 201 | Beispiel: 202 | ----------- 203 | 204 | :log 100 205 | ''' 206 | if not limit: 207 | limit = 10 208 | else: 209 | limit = limit[0] 210 | logFile = f'{ctx.channel}.log' 211 | counter = 0 212 | with open(logFile, 'w', encoding='UTF-8') as f: 213 | f.write(f'Archivierte Nachrichten vom Channel: {ctx.channel} am {ctx.message.created_at.strftime("%d.%m.%Y %H:%M:%S")}\n') 214 | async for message in ctx.channel.history(limit=limit, before=ctx.message): 215 | try: 216 | attachment = '[Angehängte Datei: {}]'.format(message.attachments[0].url) 217 | except IndexError: 218 | attachment = '' 219 | f.write('{} {!s:20s}: {} {}\r\n'.format(message.created_at.strftime('%d.%m.%Y %H:%M:%S'), message.author, message.clean_content, attachment)) 220 | counter += 1 221 | msg = f':ok: {counter} Nachrichten wurden archiviert!' 222 | f = discord.File(logFile) 223 | await ctx.send(file=f, content=msg) 224 | os.remove(logFile) 225 | 226 | @log.error 227 | async def log_error(self, error, ctx): 228 | if isinstance(error, commands.errors.CommandOnCooldown): 229 | seconds = str(error)[34:] 230 | await ctx.send(f':alarm_clock: Cooldown! Versuche es in {seconds} erneut') 231 | 232 | @commands.command() 233 | async def invite(self, ctx): 234 | '''Erstellt einen Invite Link für den derzeitigen Channel''' 235 | invite = await ctx.channel.create_invite(unique=False) 236 | msg = f'Invite Link für **#{ctx.channel.name}** auf Server **{ctx.guild.name}**:\n`{invite}`' 237 | await ctx.send(msg) 238 | 239 | @commands.command() 240 | async def whois(self, ctx, member: discord.Member=None): 241 | '''Gibt Informationen über einen Benutzer aus 242 | 243 | Beispiel: 244 | ----------- 245 | 246 | :whois @Der-Eddy#6508 247 | ''' 248 | if member == None: 249 | member = ctx.author 250 | 251 | if member.top_role.is_default(): 252 | topRole = 'everyone' #to prevent @everyone spam 253 | topRoleColour = '#000000' 254 | else: 255 | topRole = member.top_role 256 | topRoleColour = member.top_role.colour 257 | 258 | if member is not None: 259 | embed = discord.Embed(color=member.top_role.colour) 260 | embed.set_footer(text=f'UserID: {member.id}') 261 | embed.set_thumbnail(url=member.avatar.url) 262 | if member.name != member.display_name: 263 | fullName = f'{member} ({member.display_name})' 264 | else: 265 | fullName = member 266 | embed.add_field(name=member.name, value=fullName, inline=False) 267 | embed.add_field(name='Discord beigetreten am', value='{}\n(Tage seitdem: {})'.format(member.created_at.strftime('%d.%m.%Y'), (datetime.now(timezone(loadconfig.__timezone__))-member.created_at).days), inline=True) 268 | embed.add_field(name='Server beigetreten am', value='{}\n(Tage seitdem: {})'.format(member.joined_at.strftime('%d.%m.%Y'), (datetime.now(timezone(loadconfig.__timezone__))-member.joined_at).days), inline=True) 269 | embed.add_field(name='Avatar Link', value=member.avatar.url, inline=False) 270 | embed.add_field(name='Rollen', value=self._getRoles(member.roles), inline=True) 271 | embed.add_field(name='Rollenfarbe', value='{} ({})'.format(topRoleColour, topRole), inline=True) 272 | embed.add_field(name='Status', value=member.status, inline=True) 273 | await ctx.send(embed=embed) 274 | else: 275 | msg = ':no_entry: Du hast keinen Benutzer angegeben!' 276 | await ctx.send(msg) 277 | 278 | @commands.command(aliases=['e']) 279 | async def emoji(self, ctx, emojiname: str): 280 | '''Gibt eine vergrößerte Version eines angegebenen Emojis zurück 281 | 282 | Beispiel: 283 | ----------- 284 | 285 | :emoji Emilia 286 | ''' 287 | emoji = discord.utils.find(lambda e: e.name.lower() == emojiname.lower(), self.bot.emojis) 288 | if emoji: 289 | tempEmojiFile = 'tempEmoji.png' 290 | async with aiohttp.ClientSession() as cs: 291 | async with cs.get(str(emoji.url)) as img: 292 | with open(tempEmojiFile, 'wb') as f: 293 | f.write(await img.read()) 294 | f = discord.File(tempEmojiFile) 295 | await ctx.send(file=f) 296 | os.remove(tempEmojiFile) 297 | else: 298 | await ctx.send(':x: Konnte das angegebene Emoji leider nicht finden :(') 299 | 300 | @commands.command(aliases=['emotes']) 301 | async def emojis(self, ctx): 302 | '''Gibt alle Emojis aus auf welche der Bot Zugriff hat''' 303 | msg = '' 304 | for emoji in self.bot.emojis: 305 | if len(msg) + len(str(emoji)) > 1000: 306 | await ctx.send(msg) 307 | msg = '' 308 | msg += str(emoji) 309 | await ctx.send(msg) 310 | 311 | @commands.command(pass_context=True, aliases=['serverinfo', 'guild', 'membercount']) 312 | async def server(self, ctx): 313 | '''Gibt Informationen über die derzeitge Discord Guild aus''' 314 | emojis = self._getEmojis(ctx.guild.emojis) 315 | #print(emojis) 316 | roles = self._getRoles(ctx.guild.roles) 317 | embed = discord.Embed(color=discord.Color.random()) 318 | embed.set_thumbnail(url=ctx.guild.icon.url) 319 | embed.set_footer(text='Es können evtl. Emojis fehlen') 320 | embed.add_field(name='Name', value=ctx.guild.name, inline=True) 321 | embed.add_field(name='ID', value=ctx.guild.id, inline=True) 322 | embed.add_field(name='Besitzer', value=ctx.guild.owner, inline=True) 323 | embed.add_field(name='Mitglieder', value=ctx.guild.member_count, inline=True) 324 | embed.add_field(name='Premium Mitglieder', value=ctx.guild.premium_subscription_count, inline=True) 325 | embed.add_field(name='Erstellt am', value=ctx.guild.created_at.strftime('%d.%m.%Y'), inline=True) 326 | if ctx.guild.system_channel: 327 | embed.add_field(name='Standard Channel', value=f'#{ctx.guild.system_channel}', inline=True) 328 | embed.add_field(name='AFK Voice Timeout', value=f'{int(ctx.guild.afk_timeout / 60)} min', inline=True) 329 | embed.add_field(name='Guild Shard', value=ctx.guild.shard_id, inline=True) 330 | embed.add_field(name='NSFW Level', value=str(ctx.guild.nsfw_level).removeprefix('NSFWLevel.'), inline=True) 331 | embed.add_field(name='MFA Level', value=str(ctx.guild.mfa_level).removeprefix('MFALevel.'), inline=True) 332 | if ctx.guild.splash: 333 | embed.add_field(name='Splash', value=ctx.guild.splash, inline=True) 334 | if ctx.guild.discovery_splash: 335 | embed.add_field(name='Discovery Splash', value=ctx.guild.discovery_splash, inline=True) 336 | if ctx.guild.banner: 337 | embed.add_field(name='Banner', value=ctx.guild.banner, inline=True) 338 | embed.add_field(name='Rollen', value=roles, inline=True) 339 | embed.add_field(name='Custom Emojis', value=emojis, inline=True) 340 | await ctx.send(embed=embed) 341 | 342 | #Shameful copied from https://github.com/Rapptz/RoboDanny/blob/b513a32dfbd4fdbd910f7f56d88d1d012ab44826/cogs/meta.py 343 | @commands.command(aliases=['reminder']) 344 | @commands.cooldown(1, 30, commands.cooldowns.BucketType.user) 345 | async def timer(self, ctx, time : TimeParser, *, message=''): 346 | '''Setzt einen Timer und benachrichtigt einen dann 347 | 348 | Beispiel: 349 | ----------- 350 | 351 | :timer 13m Pizza 352 | 353 | :timer 2h Stream startet 354 | ''' 355 | reminder = None 356 | completed = None 357 | message = message.replace('@everyone', '@\u200beveryone').replace('@here', '@\u200bhere') 358 | 359 | if not message: 360 | reminder = ':timer: Ok {0.mention}, Ich stelle einen Timer auf {1}.' 361 | completed = ':alarm_clock: Ding Ding Ding {0.mention}! Dein Timer ist abgelaufen.' 362 | else: 363 | reminder = ':timer: Ok {0.mention}, Ich stelle einen Timer für `{2}` auf {1}.' 364 | completed = ':alarm_clock: Ding Ding Ding {0.mention}! Dein Timer für `{1}` ist abgelaufen.' 365 | 366 | human_time = datetime.now(timezone(loadconfig.__timezone__)) - timedelta(seconds=time.seconds) 367 | human_time = TimeParser.human_timedelta(human_time) 368 | await ctx.send(reminder.format(ctx.author, human_time, message)) 369 | await asyncio.sleep(time.seconds) 370 | await ctx.send(completed.format(ctx.author, message, human_time)) 371 | 372 | @timer.error 373 | async def timer_error(self, ctx, error): 374 | if isinstance(error, commands.BadArgument): 375 | await ctx.send(str(error)) 376 | elif isinstance(error, commands.errors.CommandOnCooldown): 377 | seconds = str(error)[34:] 378 | await ctx.send(f':alarm_clock: Cooldown! Versuche es in {seconds} erneut') 379 | 380 | #Stolen from https://github.com/Rapptz/RoboDanny/blob/b513a32dfbd4fdbd910f7f56d88d1d012ab44826/cogs/meta.py 381 | @commands.command() 382 | async def source(self, ctx, *, command: str = None): 383 | '''Zeigt den Quellcode für einen Befehl auf GitHub an 384 | 385 | Beispiel: 386 | ----------- 387 | 388 | :source kawaii 389 | ''' 390 | source_url = 'https://github.com/Der-Eddy/discord_bot' 391 | if command is None: 392 | await ctx.send(source_url) 393 | return 394 | 395 | obj = self.bot.get_command(command.replace('.', ' ')) 396 | if obj is None: 397 | return await ctx.send(':x: Konnte den Befehl nicht finden') 398 | 399 | # since we found the command we're looking for, presumably anyway, let's 400 | # try to access the code itself 401 | src = obj.callback.__code__ 402 | lines, firstlineno = inspect.getsourcelines(src) 403 | sourcecode = inspect.getsource(src).replace('```', '') 404 | if not obj.callback.__module__.startswith('discord'): 405 | # not a built-in command 406 | location = os.path.relpath(src.co_filename).replace('\\', '/') 407 | else: 408 | location = obj.callback.__module__.replace('.', '/') + '.py' 409 | source_url = 'https://github.com/Rapptz/discord.py' 410 | 411 | if len(sourcecode) > 1900: 412 | final_url = '{}/blob/master/{}#L{}-L{}'.format(source_url, location, firstlineno, firstlineno + len(lines) - 1) 413 | else: 414 | final_url = '<{}/blob/master/{}#L{}-L{}>\n```Python\n{}```'.format(source_url, location, firstlineno, firstlineno + len(lines) - 1, sourcecode) 415 | 416 | await ctx.send(final_url) 417 | 418 | @commands.command(hidden=True) 419 | async def roleUsers(self, ctx, *roleName: str): 420 | '''Listet alle Benutzer einer Rolle auf''' 421 | roleName = ' '.join(roleName) 422 | role = discord.utils.get(ctx.guild.roles, name=roleName) 423 | msg = '' 424 | for member in ctx.guild.members: 425 | if role in member.roles: 426 | msg += f'{member.id} | {member}\n' 427 | 428 | if msg == '': 429 | await ctx.send(':x: Konnte keinen Benutzer mit dieser Rolle finden!') 430 | else: 431 | await ctx.send(msg) 432 | 433 | @commands.command(aliases=['activities']) 434 | async def games(self, ctx, *scope): 435 | '''Zeigt welche Spiele wie oft auf dem Server gerade gespielt werden''' 436 | games = Counter() 437 | for member in ctx.guild.members: 438 | for activity in member.activities: 439 | if isinstance(activity, discord.Game): 440 | games[str(activity)] += 1 441 | elif isinstance(activity, discord.Activity): 442 | games[activity.name] += 1 443 | msg = ':chart: Spiele die derzeit auf diesem Server gespielt werden\n' 444 | msg += '```js\n' 445 | msg += '{!s:40s}: {!s:>3s}\n'.format('Name', 'Anzahl') 446 | chart = sorted(games.items(), key=lambda t: t[1], reverse=True) 447 | for index, (name, amount) in enumerate(chart): 448 | if len(msg) < 1950: 449 | msg += '{!s:40s}: {!s:>3s}\n'.format(name, amount) 450 | else: 451 | amount = len(chart) - index 452 | msg += f'+ {amount} andere' 453 | break 454 | msg += '```' 455 | await ctx.send(msg) 456 | 457 | @commands.command() 458 | async def spoiler(self, ctx, *, text: str): 459 | '''Erstellt ein GIF Bild welches beim Hover einen Spoiler Text anzeigt''' 460 | #https://github.com/flapjax/FlapJack-Cogs/blob/master/spoiler/spoiler.py 461 | content = '**' + ctx.author.display_name + '** hat einen Text gespoilert:' 462 | try: 463 | await ctx.message.delete() 464 | except discord.errors.Forbidden: 465 | content += '\n*(Bitte lösche deinen eigenen Beitrag)*' 466 | 467 | lineLength = 60 468 | margin = (5, 5) 469 | fontFile = "font/Ubuntu-R.ttf" 470 | fontSize = 18 471 | fontColor = 150 472 | bgColor = 20 473 | font = ImageFont.truetype(fontFile, fontSize) 474 | 475 | textLines = [] 476 | for line in text.splitlines(): 477 | textLines.extend(textwrap.wrap(line, lineLength, replace_whitespace=False)) 478 | 479 | title = 'SPOILER! Hover zum lesen' 480 | width = font.getsize(title)[0] + 50 481 | height = 0 482 | 483 | for line in textLines: 484 | size = font.getsize(line) 485 | width = max(width, size[0]) 486 | height += size[1] + 2 487 | 488 | width += margin[0]*2 489 | height += margin[1]*2 490 | 491 | textFull = '\n'.join(textLines) 492 | 493 | spoilIMG = [self._newImage(width, height, bgColor) for _ in range(2)] 494 | spoilText = [title, textFull] 495 | 496 | for img, txt in zip(spoilIMG, spoilText): 497 | canvas = ImageDraw.Draw(img) 498 | canvas.multiline_text(margin, txt, font=font, fill=fontColor, spacing=4) 499 | 500 | path = f'tmp\\{ctx.message.id}.gif' 501 | 502 | spoilIMG[0].save(path, format='GIF', save_all=True, append_images=[spoilIMG[1]], duration=[0, 0xFFFF], loop=0) 503 | f = discord.File(path) 504 | await ctx.send(file=f, content=content) 505 | 506 | os.remove(path) 507 | 508 | # @commands.command(aliases=['rank', 'role', 'roles'], enabled=False) 509 | # async def ranks(self, ctx, *rankName: str): 510 | # '''Auflistung aller Ränge oder beitritt eines bestimmten Ranges 511 | 512 | # Beispiel: 513 | # ----------- 514 | 515 | # :rank 516 | 517 | # :rank Python 518 | # ''' 519 | # codingLoungeID = 161637499939192832 520 | # wshbrID = 247830763649761282 521 | # codingRankList = ['HTML + CSS', 'Javascript', 'C++ / C', '.NET', 'PHP', 'NSFW', 522 | # 'Java', 'Gourmet', 'Assembler', 'Python', 'Math', 'AutoIt', 523 | # 'Member', 'Clash', 'Books', 'Chess', 'Free Games', 'macOS', 'Linux', 'Windows', 'Rust'] 524 | # wshbrRankList = ['Chuunin', 'Genin'] 525 | # if ctx.guild.id == codingLoungeID: 526 | # rankList = codingRankList 527 | # elif ctx.guild.id == wshbrID: 528 | # rankList = wshbrRankList 529 | 530 | # if len(rankName) == 0 and ctx.guild.id not in [codingLoungeID, wshbrID] or ''.join(rankName) == 'all': 531 | # rolesList = '`' 532 | # for roleServer in ctx.guild.roles: 533 | # if not roleServer.is_default(): 534 | # count = 0 535 | # for member in ctx.guild.members: 536 | # if roleServer in member.roles: 537 | # count += 1 538 | # rolesList += f'{roleServer.name:30}{count} Members\n' 539 | # embed = discord.Embed(color=0xf1c40f) #Golden 540 | # embed.set_thumbnail(url=ctx.guild.icon_url) 541 | # embed.add_field(name='Ranks', value=rolesList + '`', inline=True) 542 | # await ctx.send(embed=embed) 543 | # elif len(rankName) == 0 and ctx.guild.id in [codingLoungeID, wshbrID]: 544 | # rolesList = '`' 545 | # for role in rankList: 546 | # count = 0 547 | # roleServer = discord.utils.get(ctx.guild.roles, name=role) 548 | # for member in ctx.guild.members: 549 | # if roleServer in member.roles: 550 | # count += 1 551 | # rolesList += f'{role:20}{count} Members\n' 552 | # embed = discord.Embed(color=0x3498db) #Blue 553 | # embed.set_thumbnail(url=ctx.guild.icon_url) 554 | # embed.set_footer(text='Use the ":rank RANKNAME" command to join a rank') 555 | # embed.add_field(name='Ranks', value=rolesList + '`', inline=True) 556 | # await ctx.send(embed=embed) 557 | # elif ctx.guild.id not in [codingLoungeID, wshbrID]: 558 | # await ctx.send(':x: This command only works on the Coding Lounge Server!') 559 | # elif ctx.guild.id in [codingLoungeID, wshbrID]: 560 | # synonyms = [] 561 | # synonyms.append(['html / css', 'HTML + CSS']) 562 | # synonyms.append(['html + css', 'HTML + CSS']) 563 | # synonyms.append(['html', 'HTML + CSS']) 564 | # synonyms.append(['css', 'HTML + CSS']) 565 | # synonyms.append(['javascript', 'Javascript']) 566 | # synonyms.append(['js', 'Javascript']) 567 | # synonyms.append(['c / c++', 'C++ / C']) 568 | # synonyms.append(['c++', 'C++ / C']) 569 | # synonyms.append(['c', 'C++ / C']) 570 | # synonyms.append(['c#', '.NET']) 571 | # synonyms.append(['.net', '.NET']) 572 | # synonyms.append(['vs', '.NET']) 573 | # synonyms.append(['php', 'PHP']) 574 | # synonyms.append(['nsfw', 'NSFW']) 575 | # synonyms.append(['porn', 'NSFW']) 576 | # synonyms.append(['java', 'Java']) 577 | # synonyms.append(['gourmet', 'Gourmet']) 578 | # synonyms.append(['assembler', 'Assembler']) 579 | # synonyms.append(['asm', 'Assembler']) 580 | # synonyms.append(['python', 'Python']) 581 | # synonyms.append(['math', 'Math']) 582 | # synonyms.append(['autoit', 'AutoIt']) 583 | # synonyms.append(['clash', 'Clash']) 584 | # synonyms.append(['chess', 'Chess']) 585 | # synonyms.append(['books', 'Books']) 586 | # synonyms.append(['free games', 'Free Games']) 587 | # synonyms.append(['free game', 'Free Games']) 588 | # synonyms.append(['genin', 'Genin']) 589 | # synonyms.append(['chuunin', 'Chuunin']) 590 | # synonyms.append(['linux', 'Linux']) 591 | # synonyms.append(['macos', 'macOS']) 592 | # synonyms.append(['mac', 'macOS']) 593 | # synonyms.append(['osx', 'macOS']) 594 | # synonyms.append(['windows', 'Windows']) 595 | # synonyms.append(['rust', 'Rust']) 596 | 597 | # synonyms_dict = dict(synonyms) 598 | 599 | # try: 600 | # rankName = synonyms_dict[' '.join(rankName).lower()] 601 | # except KeyError: 602 | # rankName = ' '.join(rankName) 603 | 604 | # if not rankName in rankList: 605 | # await ctx.send(':x: Couldn\'t find that rank! Use `:ranks` to list all available ranks') 606 | # return 607 | 608 | # rank = discord.utils.get(ctx.guild.roles, name=rankName) 609 | # if rank in ctx.message.author.roles: 610 | # try: 611 | # await ctx.author.remove_roles(rank) 612 | # except: 613 | # pass 614 | # await ctx.send(f':negative_squared_cross_mark: Rank **{rank}** removed from **{ctx.author.mention}**') 615 | # else: 616 | # try: 617 | # await ctx.author.add_roles(rank) 618 | # except: 619 | # pass 620 | # await ctx.send(f':white_check_mark: Rank **{rank}** added to **{ctx.author.mention}**') 621 | 622 | @commands.command(aliases=['vote', 'addvotes', 'votes']) 623 | async def addvote(self, ctx, votecount = 'bool'): 624 | '''Fügt Emotes als Reactions hinzu für Abstimmungen/Umfragen''' 625 | if votecount.lower() == 'bool': 626 | emote_list = ['✅', '❌'] 627 | elif votecount in ['2', '3', '4', '5', '6', '7', '8', '9', '10']: 628 | #emotes = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟'] 629 | #for whatever reason, the above won't work 630 | emotes = ['1\u20e3', '2\u20e3', '3\u20e3', '4\u20e3', '5\u20e3', '6\u20e3', '7\u20e3', '8\u20e3', '9\u20e3', '\U0001f51f'] 631 | emote_list = [] 632 | for i in range (0, int(votecount)): 633 | emote_list.append(emotes[i]) 634 | else: 635 | ctx.say(':x: Bitte gib eine Zahl zwischen 2 und 10 an') 636 | 637 | message = await ctx.channel.history(limit=1, before=ctx.message).flatten() 638 | try: 639 | await ctx.message.delete() 640 | except: 641 | pass 642 | 643 | for emote in emote_list: 644 | await message[0].add_reaction(emote) 645 | 646 | 647 | # This command needs to be at the end due to it's name 648 | @commands.command() 649 | async def commands(self, ctx): 650 | '''Zeigt an wie oft welcher Command benutzt wurde seit dem letzten Startup''' 651 | msg = ':chart: Liste der ausgeführten Befehle (seit letztem Startup)\n' 652 | msg += 'Insgesamt: {}\n'.format(sum(self.bot.commands_used.values())) 653 | msg += '```js\n' 654 | msg += '{!s:15s}: {!s:>4s}\n'.format('Name', 'Anzahl') 655 | chart = sorted(self.bot.commands_used.items(), key=lambda t: t[1], reverse=True) 656 | for name, amount in chart: 657 | msg += '{!s:15s}: {!s:>4s}\n'.format(name, amount) 658 | msg += '```' 659 | await ctx.send(msg) 660 | 661 | async def setup(bot): 662 | await bot.add_cog(utility(bot)) 663 | --------------------------------------------------------------------------------