Initial commit
This commit is contained in:
commit
0a43da51f1
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.idea/
|
||||||
|
|
||||||
|
__pycache__/
|
9
Dockerfile
Normal file
9
Dockerfile
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
FROM python:3.9
|
||||||
|
|
||||||
|
ADD . /kaizen-bot
|
||||||
|
|
||||||
|
RUN apt-get update -y && \
|
||||||
|
apt-get install mariadb-client mariadb-server python3-pip -y && \
|
||||||
|
./kaizen-bot-old/install.sh
|
||||||
|
|
||||||
|
CMD ["python", "/kaizen-bot/main.py", "run"]
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 ByteDream
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
11
README.md
Normal file
11
README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Kaizen bot (old)
|
||||||
|
|
||||||
|
Code for the discord bot of the official discord server from the german anime youtuber [kaizen](https://www.youtube.com/c/KaizenAnime).
|
||||||
|
It is outdated and ran until half a year ago, seen at the time of writing (20.04.2022).
|
||||||
|
|
||||||
|
The only changes I made to the code is removing hardcoded tokens (very secure I know) and add some comments just because I've felt the urge to comment the mess.
|
||||||
|
Additional comments are prefixed with `ADDED AFTERWARDS: `.
|
||||||
|
|
||||||
|
I don't know if the code is able to run anymore, may some dependencies broke, have changed or something else ¯\_(ツ)_/¯.
|
||||||
|
|
||||||
|
Feel free to use the code for whatever you want, but as a warning: it's ugly and nested af.
|
BIN
assets/kaizen-round.png
Normal file
BIN
assets/kaizen-round.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 220 KiB |
BIN
assets/kaizen.jpg
Normal file
BIN
assets/kaizen.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
assets/man_of_culture.jpg
Normal file
BIN
assets/man_of_culture.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
BIN
assets/rules.png
Normal file
BIN
assets/rules.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
5
install.sh
Normal file
5
install.sh
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
apt install git libmariadb3 libmariadb-dev
|
||||||
|
pip3 install aiohttp discord.py dreamutils mariadb tweepy twitchAPI git+git://github.com/Rapptz/discord-ext-menus
|
||||||
|
|
||||||
|
touch /var/log/kaizen.log
|
||||||
|
chmod ugo+rwx /var/log/kaizen.log
|
26
kaizenbot/__init__.py
Normal file
26
kaizenbot/__init__.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('kaizen')
|
||||||
|
# sets the logger output format
|
||||||
|
formatter = logging.Formatter('[%(asctime)s] - %(levelname)s: %(message)s', '%Y-%m-%d %H:%M:%S')
|
||||||
|
# creates the logger handlers
|
||||||
|
|
||||||
|
# configuring the logger
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
# console output
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# file output
|
||||||
|
file_handler = logging.FileHandler('/var/log/kaizen.log')
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
|
||||||
|
# --- #
|
||||||
|
|
||||||
|
# ADDED AFTERWARDS: yes a global variable, why use classes if this works too
|
||||||
|
glob = {'bot': None, 'transcript': {}, 'timers': [], 'twitter': None}
|
679
kaizenbot/commands.py
Normal file
679
kaizenbot/commands.py
Normal file
@ -0,0 +1,679 @@
|
|||||||
|
import asyncio
|
||||||
|
from copy import copy
|
||||||
|
import datetime
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands, menus
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
import signal
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from . import logger, glob
|
||||||
|
from . import flags
|
||||||
|
from .utils import Embeds, MenuListPageSource, role_names
|
||||||
|
|
||||||
|
|
||||||
|
class Chat(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
self.flag_parser = flags.Flag(raise_errors=False)
|
||||||
|
self.flag_parser.add_flag('--all',
|
||||||
|
store_type=flags.StoreType.store_bool,
|
||||||
|
allowed_roles=['Mod'],
|
||||||
|
not_allowed_role_message='Nur Mods können die `{flag}` flag benutzen',
|
||||||
|
wrong_store_type_message='Die `{flag}` flag darf keinen parameter haben',
|
||||||
|
show_help=False)
|
||||||
|
self.flag_parser.add_flag('--limit',
|
||||||
|
store_type=(flags.StoreType.store_value, 100),
|
||||||
|
parser=self._parse_history,
|
||||||
|
wrong_store_type_message='Die {flag} muss eine Zahl als parameter haben',
|
||||||
|
help='')
|
||||||
|
|
||||||
|
async def _parse_history(self, ctx: commands.Context, flag, value):
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except ValueError:
|
||||||
|
await ctx.send(f'Der parameter der `{flag}` muss eine Zahl sein')
|
||||||
|
return False
|
||||||
|
|
||||||
|
@commands.command(name='export', ignore_extra=False,
|
||||||
|
usage='export [flags]', help='Exportiert den Chat',
|
||||||
|
flags='flag_parser')
|
||||||
|
async def history(self, ctx: commands.Context, *, args):
|
||||||
|
parsed = await self.flag_parser.parse(args, ctx=ctx)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ADDED AFTERWARS: This never was really active in use
|
||||||
|
class Economy(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
self.cooldown = {}
|
||||||
|
self.cooldown_date = datetime.datetime.now()
|
||||||
|
|
||||||
|
self.shop_items = {
|
||||||
|
'color': [10000, self.bot.database.set_user_color_count, 'Nachrichten Farbe', True, 'Setzt die Farbe die rechts bei den Bot Nachrichten, die von <@!802659482693926943> kommen'],
|
||||||
|
'extra vote': [150, self.bot.database.set_user_extra_vote, 'Extra Vote', False, 'Kann eine Umfrage starten, selbst wenn man normalerweise noch Umfrage cooldown hätte']
|
||||||
|
}
|
||||||
|
|
||||||
|
@commands.command(name='buy', aliases=['b'], usage='buy <zu kaufendes item>', help='Kauft ein Shop Item. Tippe `$shop` um alle verfügbaren Items zu sehen')
|
||||||
|
async def buy(self, ctx: commands.Context, *item):
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Der Befehl steht wegen bearbeitung temporär nicht zur Verfügung'))
|
||||||
|
return
|
||||||
|
to_buy = ' '.join(item)
|
||||||
|
if values := self.shop_items.get(to_buy, None):
|
||||||
|
if self.bot.database.has_remove_user_gold(ctx.author.id, values[0]):
|
||||||
|
embed = discord.Embed(title='Item kauf')
|
||||||
|
embed.add_field(name=values[2], value=f'*{values[4]}*\n\n'
|
||||||
|
f'<@!{ctx.author.id}> erhält:\n'
|
||||||
|
f'```diff\n'
|
||||||
|
f'+1 {values[2]}'
|
||||||
|
f'```\n'
|
||||||
|
f'<@!{ctx.author.id}> bezahlt:\n'
|
||||||
|
f'```diff\n'
|
||||||
|
f'- {values[0]} Gold\n'
|
||||||
|
f'```')
|
||||||
|
message = await ctx.send(embed=embed)
|
||||||
|
await message.add_reaction('❌')
|
||||||
|
await message.add_reaction('✅')
|
||||||
|
|
||||||
|
def check(reaction: discord.Reaction, user):
|
||||||
|
return user.id == ctx.author.id and reaction.message.id == message.id and str(reaction.emoji) in ['❌', '✅']
|
||||||
|
|
||||||
|
try:
|
||||||
|
reaction, user = await self.bot.wait_for('reaction_add', timeout=30, check=check)
|
||||||
|
if reaction.emoji == '❌':
|
||||||
|
embed.colour = 0xff0000
|
||||||
|
msg = 'Der Kauf wurde abgebrochen'
|
||||||
|
elif reaction.emoji == '✅':
|
||||||
|
values[1](ctx.author.id, 1)
|
||||||
|
embed.colour = 0x00ff00
|
||||||
|
msg = f'**{values[2]}** wurde gekauft{f". Tippe `$use {to_buy}` um das Item zu benutzen!" if values[3] else "!"}'
|
||||||
|
else:
|
||||||
|
msg = 'Hackerman hat den Bot überlistet, weshalb diese Nachricht erscheint, die eigentlich niemals hätte erscheinen dürfen'
|
||||||
|
|
||||||
|
embed.clear_fields()
|
||||||
|
embed.add_field(name=values[2], value=f'*{values[4]}*\n\n'
|
||||||
|
f'<@!{ctx.author.id}> erhält:\n'
|
||||||
|
f'```diff\n'
|
||||||
|
f'+1 {values[2]}'
|
||||||
|
f'```\n'
|
||||||
|
f'<@!{ctx.author.id}> bezahlt:\n'
|
||||||
|
f'```diff\n'
|
||||||
|
f'- {values[0]} Gold\n'
|
||||||
|
f'```\n'
|
||||||
|
f'{msg}')
|
||||||
|
await message.edit(embed=embed)
|
||||||
|
except asyncio.exceptions.TimeoutError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Du hast nicht genügend Gold um dieses Item zu kaufen'))
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=f'Es existiert kein Item mit dem Name `{to_buy}`'))
|
||||||
|
|
||||||
|
@commands.command(name='items', aliases=['i'], usage='items', help='Zeigt alle Items im besitzt an')
|
||||||
|
async def items(self, ctx: commands.Context, *, user_mention: str = None):
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Der Befehl steht wegen bearbeitung temporär nicht zur Verfügung'))
|
||||||
|
return
|
||||||
|
id = ctx.author.id
|
||||||
|
if user_mention is not None:
|
||||||
|
regex_id = re.match(r'^<@(!)?(?P<id>\d{18})>', user_mention)
|
||||||
|
if regex_id:
|
||||||
|
id = regex_id.group('id')
|
||||||
|
else:
|
||||||
|
await Help(self.bot).show_help(ctx, ctx.command)
|
||||||
|
return
|
||||||
|
|
||||||
|
embeds = []
|
||||||
|
embed = discord.Embed(title='Items', description=f'Items von <@!{id}>')
|
||||||
|
embed.set_footer(text='Alle Items die mit 💫 beginnen können über `$use <item>` benutzt werden!')
|
||||||
|
for name, value in self.bot.database.get_user_items(id).items():
|
||||||
|
if len(embed.fields) >= 10:
|
||||||
|
embeds.append(embed)
|
||||||
|
embed = discord.Embed(title='Items', description=f'Items von <@!{id}>')
|
||||||
|
embed.set_footer(text='Alle Items die mit 💫 beginnen können über `$use <item>` benutzt werden!')
|
||||||
|
if name == 'gold':
|
||||||
|
embed.description += f'\n\n:moneybag: **{value}** *Gold*'
|
||||||
|
elif value > 0:
|
||||||
|
item = self.shop_items[name]
|
||||||
|
embed.description += f'\n{":dizzy:" if item[3] else ":sparkles:"} **{value}** · `{name}` · *{item[2]}*'
|
||||||
|
embeds.append(embed)
|
||||||
|
|
||||||
|
send_embeds = menus.MenuPages(source=MenuListPageSource(embeds), clear_reactions_after=True, timeout=30)
|
||||||
|
await send_embeds.start(ctx)
|
||||||
|
|
||||||
|
@commands.command(name='leaderboard', aliases=['l'], usage='leaderboard', help='Zeigt das Server Leaderboard an')
|
||||||
|
async def leaderboard(self, ctx: commands.Context):
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Der Befehl steht wegen bearbeitung temporär nicht zur Verfügung'))
|
||||||
|
return
|
||||||
|
gold_leaderboard = discord.Embed(title='Gold Leaderboard', description='*Man kann alle 2 Minuten zwischen 5 und 15 Gold bekommen, wenn man in diesen Zeitabständen eine Nachricht schreibt. '
|
||||||
|
'Die gedroppte Gold Anzahl wird mit deinem Kaizen-Sub Level addiert (Server Booster gelten als Sub Level 2).\n'
|
||||||
|
'Also je aktiver, desto mehr Gold:moneybag:^^*')
|
||||||
|
gold_leaderboard.set_footer(text='Tippe `$shop` um dir anzusehen, was du alles kaufen kannst!')
|
||||||
|
gold_text = ''
|
||||||
|
|
||||||
|
for i, (id, gold) in enumerate(self.bot.database.get_leaderboard().items(), 1):
|
||||||
|
gold_text += f'\n{i}. - `{gold}` Gold · __**{(await self.bot.guild.fetch_member(id)).display_name}**__'
|
||||||
|
|
||||||
|
gold_leaderboard.add_field(name='Top 10', value=gold_text)
|
||||||
|
|
||||||
|
menu = menus.MenuPages(source=MenuListPageSource([gold_leaderboard]))
|
||||||
|
await menu.start(ctx)
|
||||||
|
|
||||||
|
@commands.command(name='shop', aliases=['s'], usage='shop', help='Zeigt alle Shop Elemente an')
|
||||||
|
async def shop(self, ctx: commands.Context):
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Der Befehl steht wegen bearbeitung temporär nicht zur Verfügung'))
|
||||||
|
return
|
||||||
|
embeds = []
|
||||||
|
embed = discord.Embed(title='Show durchsuchen')
|
||||||
|
for name, item in self.shop_items.items():
|
||||||
|
if len(embed.fields) >= 10:
|
||||||
|
embeds.append(embed)
|
||||||
|
embed = discord.Embed(title='Show durchsuchen')
|
||||||
|
embed.add_field(name=item[2], value=f'*{item[4]}*\n'
|
||||||
|
f'```diff\n'
|
||||||
|
f'- {item[0]} Gold\n'
|
||||||
|
f'+ 1 {item[2]}\n'
|
||||||
|
f'> $buy {name}\n'
|
||||||
|
f'```', inline=False)
|
||||||
|
embeds.append(embed)
|
||||||
|
|
||||||
|
embeds = menus.MenuPages(source=MenuListPageSource(embeds), clear_reactions_after=True, timeout=30)
|
||||||
|
await embeds.start(ctx)
|
||||||
|
|
||||||
|
@commands.command(name='use', aliases=['u'], usage='use', help='Benutzt ein Item')
|
||||||
|
async def use(self, ctx: commands.Context, *args):
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Der Befehl steht wegen bearbeitung temporär nicht zur Verfügung'))
|
||||||
|
return
|
||||||
|
pass
|
||||||
|
|
||||||
|
# --- #
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
@commands.guild_only()
|
||||||
|
async def on_message(self, message: discord.Message):
|
||||||
|
return
|
||||||
|
if not message.author.bot and message.channel.id not in [812436978809307156, # rank abfrage
|
||||||
|
822938804461502524, # offene daten
|
||||||
|
813135345725341736, # karuta
|
||||||
|
813886662841073684, # rin
|
||||||
|
813487848027193398]: # quiz erraten
|
||||||
|
now = time.time()
|
||||||
|
cooldown = self.cooldown.get(message.author.id, [0.0, 0])
|
||||||
|
if cooldown[1] < 120 and cooldown[0] + 120 < now:
|
||||||
|
cooldown[0] = now
|
||||||
|
cooldown[1] += 1
|
||||||
|
self.cooldown[message.author.id] = cooldown
|
||||||
|
|
||||||
|
gold = random.randint(5, 15)
|
||||||
|
roles = role_names(message.author)
|
||||||
|
|
||||||
|
if 'Kaizen-Sub: Level 3' in roles:
|
||||||
|
gold += 3
|
||||||
|
elif 'Kaizen-Sub: Level 2' in roles:
|
||||||
|
gold += 2
|
||||||
|
elif 'Kaizen-Sub: Level 1' in roles:
|
||||||
|
gold += 1
|
||||||
|
if 'Server Booster' in roles:
|
||||||
|
gold += 2
|
||||||
|
|
||||||
|
self.bot.database.add_user_gold(message.author.id, gold)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
for key in self.cooldown:
|
||||||
|
self.cooldown[key] = [0.0, 0]
|
||||||
|
|
||||||
|
|
||||||
|
# ADDED AFTERWARDS: I had the idea of implementing familiarly relationship and show it in a tree
|
||||||
|
# but this was never finished (not that I really started to try working on it)
|
||||||
|
class Family(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command(name='tree', usage='tree', help='Zeigt den Familienstammbaum')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def family_tree(self, ctx: commands.Context):
|
||||||
|
self.bot.database.get_user_parents(ctx.author.id)
|
||||||
|
|
||||||
|
def _family_tree(self, id: int) -> List[List[str]]:
|
||||||
|
parents = self.bot.database.get_user_parents()
|
||||||
|
if parents:
|
||||||
|
|
||||||
|
return [].extend()
|
||||||
|
else:
|
||||||
|
return [self.bot.guild.fetch_member(id).display_name]
|
||||||
|
|
||||||
|
|
||||||
|
class Mod(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command(name='error', usage='error')
|
||||||
|
@commands.guild_only()
|
||||||
|
@commands.has_role('Mod')
|
||||||
|
async def error(self, ctx: commands.Context):
|
||||||
|
logger.info('NOTICE: Raising controlled exception...')
|
||||||
|
raise Exception('Exception raised by the error command')
|
||||||
|
|
||||||
|
@commands.command(name='restart', usage='restart')
|
||||||
|
@commands.guild_only()
|
||||||
|
@commands.has_role('Mod')
|
||||||
|
async def restart(self, ctx: commands.Context):
|
||||||
|
for thread in glob['timers']:
|
||||||
|
thread.cancel()
|
||||||
|
logger.info('Restarting...')
|
||||||
|
os.kill(os.getpid(), signal.SIGKILL)
|
||||||
|
|
||||||
|
@commands.command(name='transcript', usage='transcript', help='Schickt eine Datei mit dem gesamten Nachrichtenverlauf der letzten Stunde')
|
||||||
|
@commands.guild_only()
|
||||||
|
@commands.has_role('Mod')
|
||||||
|
async def transcript(self, ctx: commands.Context):
|
||||||
|
tmp = tempfile.mktemp('.txt')
|
||||||
|
with open(tmp, 'w+') as file:
|
||||||
|
for message in glob['transcript'].values():
|
||||||
|
file.write(message + '\n')
|
||||||
|
file.close()
|
||||||
|
await ctx.send(file=discord.File(tmp, 'transcript.txt'))
|
||||||
|
os.remove(tmp)
|
||||||
|
|
||||||
|
|
||||||
|
class Help(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
async def show_help(self, ctx: commands.Context, command: commands.Command):
|
||||||
|
embed = discord.Embed(title=f'`{command.name}` command\n\n', color=discord.Color(0xff0000))
|
||||||
|
embed.set_footer(text='<...> - required; [...] - optional; <...|...> - or\nDo NOT include <>, [] or | when executing the command')
|
||||||
|
embed.add_field(name='Usage', value=f'`{self.bot.command_prefix}{command.usage}`', inline=False)
|
||||||
|
embed.add_field(name='Description', value=command.help, inline=False)
|
||||||
|
if command_flags := flags.get_flags(command):
|
||||||
|
all_flags = {}
|
||||||
|
for flag, info in command_flags.flags().items():
|
||||||
|
if info['show_help']:
|
||||||
|
hash_sum = hash(str(info))
|
||||||
|
if hash_sum not in all_flags:
|
||||||
|
all_flags[hash_sum] = copy(info)
|
||||||
|
all_flags[hash_sum]['names'] = [flag]
|
||||||
|
else:
|
||||||
|
all_flags[hash_sum]['names'].append(flag)
|
||||||
|
embed.add_field(name='Flags', value='\n'.join([f'• `{"`, `".join(flag["names"])}` - {flag["help"].format(flag=flag["names"][0])}' for flag in all_flags.values()]), inline=False)
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.command(name='commands', usage='commands', description='Liste alle verfügbaren Befehle auf')
|
||||||
|
async def cmds(self, ctx: commands.Context):
|
||||||
|
commands = {command.name: command for command in self.bot.commands}
|
||||||
|
groups = {'default': []}
|
||||||
|
for command in sorted(commands):
|
||||||
|
if hasattr(command, 'group'):
|
||||||
|
if command.group not in groups:
|
||||||
|
groups[command.group] = []
|
||||||
|
groups[command.group].append(commands[command])
|
||||||
|
new_line = '\n'
|
||||||
|
embed = discord.Embed(title='Commands', description=f'Um Hilfe zu einem bestimmten Befehl zu bekommen, tippe `{self.bot.command_prefix}help [command]`',
|
||||||
|
color=discord.Color(0xff0000))
|
||||||
|
|
||||||
|
for group_name, group in groups.items():
|
||||||
|
embed.add_field(name=group_name, value=f'```• {f"{new_line}• ".join([self.bot.command_prefix + command.usage for command in group])}```', inline=False)
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.command(name='help', usage='help [command]', description='Zeigt Hilfe an')
|
||||||
|
async def help(self, ctx: commands.Context, command: Optional[str]):
|
||||||
|
if command:
|
||||||
|
if cmd := self.bot.get_command(command):
|
||||||
|
await self.show_help(ctx, cmd)
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(title=f'{self.bot.user.name} help', description=f'Der Befehl {command} existiert nicht!', color=discord.Color(0xff0000))
|
||||||
|
embed.add_field(name='Get help', value=f'Um Hilfe zu einem bestimmten Befehl zu bekommen, tippe `{self.bot.command_prefix}help [command]`', inline=False)
|
||||||
|
embed.add_field(name='Commands', value=f'Tippe `{self.bot.command_prefix}commands`, um eine Liste mit allen Befehlen zu bekommen', inline=False)
|
||||||
|
embed.set_footer(text='<...> - required; [...] - optional; <...|...> - or\nDo NOT include <>, [] or | when executing the command')
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
else:
|
||||||
|
embed = discord.Embed(title=f'{self.bot.user.name} help', color=discord.Color(0xff0000))
|
||||||
|
embed.add_field(name='Get help', value=f'Um Hilfe zu einem bestimmten Befehl zu bekommen, tippe `{self.bot.command_prefix}help [command]`', inline=False)
|
||||||
|
embed.add_field(name='Commands', value=f'Tippe `{self.bot.command_prefix}commands`, um eine Liste mit allen Befehlen zu bekommen', inline=False)
|
||||||
|
embed.set_footer(text='<...> - required; [...] - optional; <...|...> - or\nDo NOT include <>, [] or | when executing the command')
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
class Info(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
self.flag_parser = flags.Flag()
|
||||||
|
|
||||||
|
@commands.command(name='info', usage='info [@user]', help='Zeigt Infos über einen Nutzer an')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def info(self, ctx: commands.Context, *, user_mention: str = None):
|
||||||
|
id = ctx.author.id
|
||||||
|
if user_mention is not None:
|
||||||
|
regex_id = re.match(r'^<@(!)?(?P<id>\d{18})>', user_mention)
|
||||||
|
if regex_id:
|
||||||
|
id = regex_id.group('id')
|
||||||
|
else:
|
||||||
|
await Help(self.bot).show_help(ctx, ctx.command)
|
||||||
|
return
|
||||||
|
if (infos := self.bot.database.get_user_infos(id)) is None:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description="Der Nutzer existiert nicht"))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
member = await self.bot.guild.fetch_member(int(id))
|
||||||
|
name = member.display_name if member.display_name.lower().endswith(('s', 'z')) else member.display_name + "'s"
|
||||||
|
embed = discord.Embed(title=name + " Infos", color=member.roles[-1].color)
|
||||||
|
embed.set_thumbnail(url=member.avatar_url)
|
||||||
|
embed.set_footer(text='Tippe `$help addinfo` um zu sehen, was für Infos noch hinzugefügt / geändert werden können')
|
||||||
|
for key, value in infos.items():
|
||||||
|
if value is not None:
|
||||||
|
embed.add_field(name=key, value=value, inline=False)
|
||||||
|
if len(embed.fields) == 0:
|
||||||
|
embed.description = "Es wurden noch keine Infos eingetragen"
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
@commands.command(name='addinfo', aliases=['infoadd'], usage='addinfo <name, age | list | fav | waifu | husbando> <name, alter, link zu einer anime liste, lieblings anime, waifu, husbando>',
|
||||||
|
help='Fügt den Namen / das Alter / eine Anime Liste / einen Lieblings Anime / eine Waifu / ein Husbando zu einem Nutzer hinzu.\n\n'
|
||||||
|
'Tippe `$help removeinfo` um Infos wieder zu entfernen')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def infoadd(self, ctx: commands.Context, info_type: str, *info_value):
|
||||||
|
if info_type.lower() == 'name':
|
||||||
|
self.bot.database.set_user_name(ctx.author.id, ' '.join(info_value))
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description=f'Name (__{" ".join(info_value)}__) wurde hinzugefügt'))
|
||||||
|
elif info_type.lower() == 'age':
|
||||||
|
try:
|
||||||
|
age = int(info_value[0])
|
||||||
|
except ValueError:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='`age` sollte eine Zahl sein'))
|
||||||
|
return
|
||||||
|
if age < 0:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Hmmm noch nie von einer Person gehört die minus Jahre alt ist'))
|
||||||
|
return
|
||||||
|
elif age > 99:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Uff, bei so einem hohen Alter komm selbst ich in Bedrängnis und kann es nicht zu deinen Infos hinzufügen :/'))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.bot.database.set_user_age(ctx.author.id, age)
|
||||||
|
|
||||||
|
# ADDED AFTERWARDS: hehe
|
||||||
|
if age == 69:
|
||||||
|
embed = Embeds.success_embed(title='Alter wurde hinzugefügt')
|
||||||
|
embed.description = 'Ah, I see you\'re a man of culture as well'
|
||||||
|
embed.set_thumbnail(url='attachment://man_of_culture.jpg')
|
||||||
|
await ctx.send(embed=embed, file=discord.File(Path.cwd().joinpath('assets', 'man_of_culture.jpg')))
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description=f'Alter __{age}__ wurde hinzugefügt'))
|
||||||
|
elif info_type.lower() == 'list':
|
||||||
|
try:
|
||||||
|
requests.get(info_value[0])
|
||||||
|
except Exception:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Ich konnte mich nicht mit der gegeben URL verbinden'))
|
||||||
|
return
|
||||||
|
self.bot.database.set_user_list(ctx.author.id, info_value[0])
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description='Anime Liste wurde hinzugefügt'))
|
||||||
|
elif info_type.lower() == 'fav':
|
||||||
|
self.bot.database.set_user_fav(ctx.author.id, ' '.join(info_value))
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description=f'Lieblings Anime (__{" ".join(info_value)}__) wurde hinzugefügt'))
|
||||||
|
elif info_type.lower() == 'waifu':
|
||||||
|
self.bot.database.set_user_waifu(ctx.author.id, ' '.join(info_value))
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description=f'Waifu (__{" ".join(info_value)}__) wurde hinzugefügt'))
|
||||||
|
elif info_type.lower() == 'husbando':
|
||||||
|
self.bot.database.set_user_husbando(ctx.author.id, ' '.join(info_value))
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description=f'Husbando (__{" ".join(info_value)}__) wurde hinzugefügt'))
|
||||||
|
else:
|
||||||
|
await Help(self.bot).show_help(ctx, ctx.command)
|
||||||
|
|
||||||
|
@commands.command(name='removeinfo', aliases=['inforemove'], usage='removeinfo <name, age | list | fav | waifu | husbando>',
|
||||||
|
help='Entfernt Name / Alter / Anime Liste / lieblings Anime / Waifu / Husbando von einem Nutzer.\n\n'
|
||||||
|
'Tippe `$help addinfo` um Infos hinzuzufügen')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def inforemove(self, ctx: commands.Context, info_type: str):
|
||||||
|
if info_type.lower() == 'name':
|
||||||
|
self.bot.database.set_user_name(ctx.author.id, None)
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description='Name wurde entfernt'))
|
||||||
|
elif info_type.lower() == 'age':
|
||||||
|
self.bot.database.set_user_age(ctx.author.id, None)
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description='Alter wurde entfernt'))
|
||||||
|
elif info_type.lower() == "list":
|
||||||
|
self.bot.database.set_user_list(ctx.author.id, None)
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description='Anime Liste wurde entfernt'))
|
||||||
|
elif info_type.lower() == "fav":
|
||||||
|
self.bot.database.set_user_fav(ctx.author.id, None)
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description='Lieblings Anime wurde entfernt'))
|
||||||
|
elif info_type.lower() == "waifu":
|
||||||
|
self.bot.database.set_user_waifu(ctx.author.id, None)
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description='Waifu wurde entfernt'))
|
||||||
|
elif info_type.lower() == "husbando":
|
||||||
|
self.bot.database.set_user_husbando(ctx.author.id, None)
|
||||||
|
await ctx.send(embed=Embeds.success_embed(description='Husbando wurde entfernt'))
|
||||||
|
else:
|
||||||
|
await Help(self.bot).show_help(ctx, ctx.command)
|
||||||
|
|
||||||
|
|
||||||
|
class Kaizen(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command(name='kaizen', usage='kaizen', help='Alle Links zu den Social Media Kanälen von Kaizen')
|
||||||
|
async def links(self, ctx: commands.Context):
|
||||||
|
await Embeds.send_kaizen_infos(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class Vote(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
self.total_votes = []
|
||||||
|
self.next_normal_time = datetime.datetime.now()
|
||||||
|
self.sub_or_booster_time = {}
|
||||||
|
self.number_emojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣', '8️⃣', '9️⃣', '🔟']
|
||||||
|
|
||||||
|
self.flag_parser = flags.Flag(raise_errors=False,
|
||||||
|
double_flag_error='Die flag `{flag}` wurde schon gegeben',
|
||||||
|
flag_position_error='Flags müssen als letztes aufgerufen werden',
|
||||||
|
quotation_error='Es fehlen ein oder mehrere Anführungszeichen (`"` oder `\'`)')
|
||||||
|
self.flag_parser.add_flag('--duration',
|
||||||
|
store_type=(flags.StoreType.store_value, 60*5),
|
||||||
|
parser=self._duration_parser,
|
||||||
|
allowed_roles=['Mod', 'Server Booster', 'Kaizen-Sub'],
|
||||||
|
not_allowed_role_message='Nur Nutzer, die Server Booster oder Twich-Sub von Kaizen sind, können die `{flag}` flag benutzen!',
|
||||||
|
wrong_store_type_message='Die `{flag}` flag muss eine Zeitangabe haben (z.B. `{flag}=12m21s`)',
|
||||||
|
help='Zeit, bis das Ergebnis bekanntgegeben werden soll. Ist nur für Server Booster und Kaizen Twitch Subs verfügbar! (z.B. {flag}=12m21s)')
|
||||||
|
self.flag_parser.add_flag('--image',
|
||||||
|
store_type=flags.StoreType.store_value,
|
||||||
|
wrong_store_type_message='Die `{flag}` flag muss einen URL zu einem Bild haben (z.B. `{flag}="https://bytedream.org/darling.jpg"`)',
|
||||||
|
help='Fügt ein extra Bild zur Umfrage hinzu. Bild muss als URL angegeben sein (z.B. {flag}=https://bytedream.org/darling.jpg)')
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
async def _duration_parser(self, ctx: commands.Context, flag, value):
|
||||||
|
regex_time = re.match(r'^((?P<hour>\d*?)h)?((?P<min>\d*?)m)?((?P<sec>\d*?)s)?', str(value))
|
||||||
|
time = 0
|
||||||
|
if regex_time['hour']:
|
||||||
|
time += int(regex_time['hour']) * 60 * 60
|
||||||
|
if regex_time['min']:
|
||||||
|
time += int(regex_time['min']) * 60
|
||||||
|
if regex_time['sec']:
|
||||||
|
time += int(regex_time['sec'])
|
||||||
|
if time == 0:
|
||||||
|
await ctx.send(embed=Embeds.warn_embed(description=f'Die Zeit der `{flag}` flag sollte in Form von __12m21s__ oder __21m__ sein'))
|
||||||
|
return False
|
||||||
|
elif time > 60 * 60:
|
||||||
|
roles = role_names(ctx.author)
|
||||||
|
if 'Mod' not in roles and 'A&M Redakteur' not in roles:
|
||||||
|
await ctx.send(embed=Embeds.warn_embed(description=f'Die Gesamtzeit der `{flag}` flag kann höchstens 60 Minuten betragen!'))
|
||||||
|
return False
|
||||||
|
return time
|
||||||
|
|
||||||
|
@commands.command(name='vote', ignore_extra=False,
|
||||||
|
usage='vote "<title>" <2 bis 10 "Antwortmöglichkeiten"> [flags]', help='Startet eine Umfrage mit maximal 10 Antwortmöglichkeiten',
|
||||||
|
flags='flag_parser')
|
||||||
|
@commands.guild_only()
|
||||||
|
async def vote(self, ctx: commands.Context, *, args):
|
||||||
|
parsed = await self.flag_parser.parse(args, ctx=ctx)
|
||||||
|
if not parsed:
|
||||||
|
return
|
||||||
|
|
||||||
|
args = list(parsed.normal_args)
|
||||||
|
|
||||||
|
if len(args) == 0:
|
||||||
|
async with ctx.typing():
|
||||||
|
await asyncio.sleep(.7)
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=f'Syntax für den \'{self.bot.command_prefix}vote\':\n'
|
||||||
|
f'`{self.bot.command_prefix}vote [titel] [antwortmöglichkeiten] (dauer)`\n\n'
|
||||||
|
f'Argumente:\n'
|
||||||
|
f'`titel`: Titel der Umfrage ("Wie findet ihr Anime xy?")\n'
|
||||||
|
f'`antwortmöglichkeiten`: Bis zu zehn mögliche Antwortmöglichkeiten ("super" "ok" "schlecht")\n'
|
||||||
|
f'`dauer`: Zeit, bis das Ergebnis bekanntgegeben werden soll.\n\n'
|
||||||
|
f'Beispiel:\n'
|
||||||
|
f'`{self.bot.command_prefix}vote "Wie findet Ihr Anime xy?" "sehr gut" super ok schlecht "sehr schlecht" time=12m`\n\n'
|
||||||
|
f'Um Leerzeichen im Titel oder in den Antwortmöglichkeiten zu benutzen, die entsprechenden Dinge einfach mit Anführungszeichen (`"`) vorne und hinten versehen'))
|
||||||
|
return
|
||||||
|
elif len(args) <= 2:
|
||||||
|
await ctx.send(embed=Embeds.error_embed('Es müssen mindestens zwei Antwortmöglichkeit angegeben sein!'))
|
||||||
|
return
|
||||||
|
elif len(args) > 10 + 1: # the +1 is the title
|
||||||
|
await ctx.send(embed=Embeds.error_embed('Es können nur maximal 10 Antwortmöglichkeiten angegeben werden!'))
|
||||||
|
return
|
||||||
|
|
||||||
|
roles = role_names(ctx.author)
|
||||||
|
privileges = 'Mod' in roles or 'A&M Redakteur' in roles
|
||||||
|
booster = 'Server Booster' in roles
|
||||||
|
sub = 'Kaizen-Sub' in roles
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
|
if len(self.total_votes) >= 3 and not privileges:
|
||||||
|
difference = divmod(min(self.total_votes) - now.timestamp(), 60)
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=f'Es können maximal 3 Umfragen gleichzeitig laufen. In {round(difference[0])} Minuten und {round(difference[1])} Sekunden kann wieder eine neue Umfrage gestartet werden.'))
|
||||||
|
return
|
||||||
|
|
||||||
|
# check for cooldown
|
||||||
|
if privileges:
|
||||||
|
pass
|
||||||
|
elif booster or sub:
|
||||||
|
if self.sub_or_booster_time.get(ctx.author.id, now - datetime.timedelta(hours=1)) < now:
|
||||||
|
self.sub_or_booster_time[ctx.author.id] = now + datetime.timedelta(minutes=15)
|
||||||
|
elif self.next_normal_time < now:
|
||||||
|
self.next_normal_time = now + datetime.timedelta(minutes=40)
|
||||||
|
elif self.bot.database.set_user_extra_vote(ctx.author.id, -1):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if (t := self.sub_or_booster_time[ctx.author.id]) < self.next_normal_time:
|
||||||
|
difference = divmod((t - now).total_seconds(), 60)
|
||||||
|
else:
|
||||||
|
difference = divmod((self.next_normal_time - now).total_seconds(), 60)
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=f'Du musst leider noch {round(difference[0])} Minuten und {round(difference[1])} Sekunden warten, bis du den Befehl erneut benutzen kannst.'))
|
||||||
|
return
|
||||||
|
elif self.next_normal_time < now:
|
||||||
|
self.next_normal_time = now + datetime.timedelta(minutes=40)
|
||||||
|
else:
|
||||||
|
difference = divmod((self.next_normal_time - now).total_seconds(), 60)
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=f'Du musst leider noch {round(difference[0])} Minuten und {round(difference[1])} Sekunden warten, du den Befehl erneut benutzen kannst.\n'
|
||||||
|
f'Werde Server Booster oder Twich-Sub, um die Wartezeit zu verringern!'))
|
||||||
|
return
|
||||||
|
|
||||||
|
# send the message embed
|
||||||
|
args[0] = args[0] if args[0].endswith('?') else args[0] + '?'
|
||||||
|
embed = discord.Embed(title=f'**{args[0]}**',
|
||||||
|
description='\n'.join([f'{self.number_emojis[i]}: {answer}' for i, answer in enumerate(args[1:])]),
|
||||||
|
color=ctx.author.roles[-1].color)
|
||||||
|
if parsed.image:
|
||||||
|
embed.set_thumbnail(url=parsed.image)
|
||||||
|
end_time = datetime.datetime.now() + datetime.timedelta(seconds=parsed.duration)
|
||||||
|
embed.set_footer(text=f'Umfrage endet am {end_time.strftime("%d.%m.%Y")} um ca. {end_time.strftime("%H:%M")} Uhr')
|
||||||
|
async with ctx.typing():
|
||||||
|
await asyncio.sleep(.5)
|
||||||
|
message = await ctx.send(embed=embed)
|
||||||
|
for i in range(len(args[1:])):
|
||||||
|
await message.add_reaction(self.number_emojis[i])
|
||||||
|
|
||||||
|
if parsed.pin:
|
||||||
|
await message.pin()
|
||||||
|
await ctx.message.delete()
|
||||||
|
|
||||||
|
last_mention_id = message.id
|
||||||
|
|
||||||
|
self.total_votes.append(end_time.timestamp())
|
||||||
|
walked = 0
|
||||||
|
for _ in range(0, (parsed.duration - 5) // (60 * 10)):
|
||||||
|
await asyncio.sleep(60 * 10)
|
||||||
|
is_fresh = False
|
||||||
|
async for old_message in ctx.channel.history(limit=10):
|
||||||
|
if old_message.id == last_mention_id:
|
||||||
|
is_fresh = True
|
||||||
|
break
|
||||||
|
if not is_fresh:
|
||||||
|
last_mention_id = (await message.reply(embed=discord.Embed(description=f'Es läuft aktuell eine Umfrage, stimmt doch zur Frage `{args[0]}` ab!',
|
||||||
|
color=embed.color))).id
|
||||||
|
walked += 60 * 10
|
||||||
|
await asyncio.sleep(parsed.duration - walked)
|
||||||
|
|
||||||
|
self.total_votes.remove(end_time.timestamp())
|
||||||
|
|
||||||
|
if parsed.pin:
|
||||||
|
await message.unpin()
|
||||||
|
|
||||||
|
reactions = []
|
||||||
|
users = []
|
||||||
|
|
||||||
|
for reaction in (await ctx.channel.fetch_message(message.id)).reactions:
|
||||||
|
reactions.append(reaction)
|
||||||
|
async for user in reaction.users():
|
||||||
|
if not user.bot and user.id not in users:
|
||||||
|
users.append(user.id)
|
||||||
|
embed = discord.Embed(title=f'**{args[0]}**',
|
||||||
|
description='\n'.join([f'{self.number_emojis[i]}: {answer} - {reactions[i].count - 1} {"Stimme" if reactions[i].count - 1 == 1 else "Stimmen"}' for i, answer in enumerate(args[1:])]),
|
||||||
|
color=embed.color)
|
||||||
|
if parsed.image:
|
||||||
|
embed.set_thumbnail(url=parsed.image)
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
embed.set_footer(text=f'Umfrage endete am {now.strftime("%d.%m.%Y")} um {now.strftime("%H:%M:%S")} Uhr')
|
||||||
|
await message.clear_reactions()
|
||||||
|
await message.edit(embed=embed)
|
||||||
|
|
||||||
|
reaction_dict = {arg: count for arg, count in sorted({arg: reactions[i].count for i, arg in enumerate(args[1:])}.items(), key=lambda item: item[1], reverse=True)}
|
||||||
|
|
||||||
|
result_embed = discord.Embed(title=f'Umfrageergebnisse zu `{args[0]}`\n\n',
|
||||||
|
description='\n'.join([f'{i + 1}. {arg} - {count - 1} {"Stimme" if count - 1 == 1 else "Stimmen"}' for i, (arg, count) in enumerate(reaction_dict.items())]),
|
||||||
|
color=embed.color)
|
||||||
|
|
||||||
|
result_embed.description += f'\n\nInsgesamt mitgemacht: {len(users)}'
|
||||||
|
|
||||||
|
await ctx.send(embed=result_embed)
|
||||||
|
|
||||||
|
|
||||||
|
# ADDED AFTERWARDS: hehe pt. 2
|
||||||
|
class NSFW(commands.Cog):
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.id = 796873618026004481
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command(name='color', usage='color', help='Shows a random colored hentai image')
|
||||||
|
@commands.guild_only()
|
||||||
|
@commands.cooldown(1, 60 * 5, type=commands.BucketType.channel)
|
||||||
|
async def color(self, ctx: commands.Context):
|
||||||
|
if ctx.channel.id != self.id:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Dieser Befehl kann nur in <#796873618026004481> genutzt werden'))
|
||||||
|
else:
|
||||||
|
await ctx.send(file=discord.File(os.path.join('/srv/media/hentai/image', random.choice(os.listdir('/srv/media/hentai/image')))))
|
||||||
|
|
||||||
|
@commands.command(name='lewd', usage='lewd', help='Shows a random lewd')
|
||||||
|
@commands.guild_only()
|
||||||
|
@commands.cooldown(1, 60 * 5, type=commands.BucketType.channel)
|
||||||
|
async def lewd(self, ctx: commands.Context):
|
||||||
|
if ctx.channel.id != self.id:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description='Dieser Befehl kann nur in <#796873618026004481> genutzt werden'))
|
||||||
|
else:
|
||||||
|
page = random.randint(0, glob['twitter'].get_user('lewdxsthetic').statuses_count // 20)
|
||||||
|
await ctx.send(random.choice(glob['twitter'].user_timeline('lewdxsthetic', page=page, count=20))._json['entities']['media'][0]['media_url_https'])
|
203
kaizenbot/database.py
Normal file
203
kaizenbot/database.py
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import mariadb
|
||||||
|
import datetime
|
||||||
|
from typing import Any, Dict, List, Union
|
||||||
|
import discord
|
||||||
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
|
||||||
|
def __init__(self, user: str, password: str):
|
||||||
|
self.database: mariadb._mariadb.connection = mariadb.connect(user=user, password=password, host='127.0.0.1', port=3306, database='kaizen')
|
||||||
|
self.database.autocommit = True
|
||||||
|
self.cursor = self.database.cursor()
|
||||||
|
|
||||||
|
def sync(self):
|
||||||
|
self.cursor.execute('INSERT INTO info (id) (SELECT id FROM rules_accepted WHERE id NOT IN (SELECT id FROM info))')
|
||||||
|
self.cursor.execute('INSERT INTO economy (id) (SELECT id FROM rules_accepted WHERE id NOT IN (SELECT id FROM economy))')
|
||||||
|
|
||||||
|
# --- #
|
||||||
|
|
||||||
|
def get_message_id(self) -> Union[int, None]:
|
||||||
|
try:
|
||||||
|
self.cursor.execute('SELECT v FROM stuff WHERE k=?', ('message_id',))
|
||||||
|
return int(self.cursor.fetchone()[0])
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_message_id(self, id: int):
|
||||||
|
if id is None:
|
||||||
|
self.cursor.execute('DELETE FROM stuff WHERE k=?', ('message_id',))
|
||||||
|
else:
|
||||||
|
self.cursor.execute('INSERT INTO stuff (k, v) VALUES (?, ?)', ('message_id', str(id)))
|
||||||
|
|
||||||
|
def get_image_id(self) -> Union[int, None]:
|
||||||
|
try:
|
||||||
|
self.cursor.execute('SELECT v FROM stuff WHERE k=?', ('image_id',))
|
||||||
|
return int(self.cursor.fetchone()[0])
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_image_id(self, id: int):
|
||||||
|
if id is None:
|
||||||
|
self.cursor.execute('DELETE FROM stuff WHERE k=?', ('image_id',))
|
||||||
|
else:
|
||||||
|
self.cursor.execute('INSERT INTO stuff (k, v) VALUES (?, ?)', ('image_id', str(id)))
|
||||||
|
|
||||||
|
# --- user specific --- #
|
||||||
|
|
||||||
|
"""def user(self, member: discord.Member) -> Union[User, None]:
|
||||||
|
self.cursor.execute('SELECT * FROM rules_accepted WHERE id=?', (member.id,))
|
||||||
|
result = self.cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
return User(result[0], result[1], result[2], member.joined_at, result[3])
|
||||||
|
else:
|
||||||
|
self.cursor.execute('SELECT * FROM normal_user WHERE id=?', (member.id,))
|
||||||
|
result = self.cursor.fetchone()
|
||||||
|
if result:
|
||||||
|
return User(result[0], result[1], result[2], result[3], None, result[4], result[5])
|
||||||
|
else:
|
||||||
|
return None"""
|
||||||
|
|
||||||
|
def add_user(self, user: discord.Member, join_message_id: int) -> User:
|
||||||
|
self.cursor.execute('INSERT INTO normal_user (id, name, tag, joined, join_message) VALUES (?, ?, ?, ?, ?)', (user.id, user.display_name, str(user), user.joined_at, join_message_id))
|
||||||
|
return User(user.id, user.display_name, str(user), user.joined_at, join_message=join_message_id)
|
||||||
|
|
||||||
|
def get_all_users(self) -> Dict[int, User]:
|
||||||
|
users = {}
|
||||||
|
self.cursor.execute('SELECT * FROM normal_user')
|
||||||
|
for row in self.cursor.fetchall():
|
||||||
|
users[row[0]] = User(row[0], row[1], row[2], row[3], None, row[4], row[5])
|
||||||
|
|
||||||
|
self.cursor.execute('SELECT * FROM rules_accepted')
|
||||||
|
for row in self.cursor.fetchall():
|
||||||
|
users[row[0]] = User(row[0], row[1], row[2], None, row[3])
|
||||||
|
|
||||||
|
return users
|
||||||
|
|
||||||
|
def set_user_warning(self, user: User, warning_time: datetime.datetime):
|
||||||
|
self.cursor.execute('UPDATE normal_user SET warned=? WHERE id=?', (warning_time, user.id))
|
||||||
|
user.warning_time = warning_time
|
||||||
|
|
||||||
|
def add_user_accepted_rules(self, user: User, accepted_rules_datetime: datetime.datetime):
|
||||||
|
self.cursor.execute('DELETE FROM normal_user WHERE id=?', (user.id,))
|
||||||
|
self.cursor.execute('INSERT INTO rules_accepted (id, name, tag, accepted) VALUES (?, ?, ?, ?)', (user.id, user.name, user.tag, accepted_rules_datetime))
|
||||||
|
self.cursor.execute('INSERT INTO info (id) VALUES (?)', (user.id,))
|
||||||
|
self.cursor.execute('INSERT INTO economy (id) SELECT id FROM rules_accepted WHERE NOT EXISTS(SELECT id FROM rules_accepted WHERE id=?)', (user.id,))
|
||||||
|
user.warning_time = None
|
||||||
|
user.accepted_rules_date = accepted_rules_datetime
|
||||||
|
user.join_message = None
|
||||||
|
|
||||||
|
def reset_user(self, user: User):
|
||||||
|
self.cursor.execute('DELETE FROM rules_accepted WHERE id=?', (user.id,))
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
self.cursor.execute('INSERT INTO normal_user (id, name, tag, joined) VALUES (?, ?, ?, ?)', (user.id, user.name, user.tag, now))
|
||||||
|
user.joined = now
|
||||||
|
user.warning_time = None
|
||||||
|
user.accepted_rules_date = None
|
||||||
|
|
||||||
|
def remove_user(self, user: Union[User, int]):
|
||||||
|
if isinstance(user, User):
|
||||||
|
id = user.id
|
||||||
|
else:
|
||||||
|
id = user
|
||||||
|
self.cursor.execute('DELETE FROM normal_user WHERE id=?', (id,))
|
||||||
|
self.cursor.execute('DELETE FROM rules_accepted WHERE id=?', (id,))
|
||||||
|
self.cursor.execute('DELETE FROM info WHERE id=?', (id,))
|
||||||
|
|
||||||
|
def change_user_infos(self, user: User, new_name: str, new_tag: str):
|
||||||
|
self.cursor.execute('UPDATE normal_user SET name=?, tag=? WHERE id=?', (new_name, new_tag, user.id))
|
||||||
|
self.cursor.execute('UPDATE rules_accepted SET name=?, tag=? WHERE id=?', (new_name, new_tag, user.id))
|
||||||
|
user.name = new_name
|
||||||
|
user.tag = new_tag
|
||||||
|
|
||||||
|
# --- user infos --- #
|
||||||
|
|
||||||
|
def get_user_infos(self, id: int) -> Union[Dict[str, Any], None]:
|
||||||
|
self.cursor.execute('SELECT * FROM info WHERE id=?', (id,))
|
||||||
|
result = self.cursor.fetchone()
|
||||||
|
try:
|
||||||
|
return {
|
||||||
|
'Name': result[1],
|
||||||
|
'Alter': result[2],
|
||||||
|
'Anime Liste': result[3],
|
||||||
|
'Lieblings Anime': result[4],
|
||||||
|
'Waifu': result[5],
|
||||||
|
'Husbando': result[6],
|
||||||
|
}
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_user_name(self, id: int, name: str):
|
||||||
|
self.cursor.execute('UPDATE info SET name=? WHERE id=?', (name, id))
|
||||||
|
|
||||||
|
def set_user_age(self, id: int, age: [int, None]):
|
||||||
|
self.cursor.execute('UPDATE info SET age=? WHERE id=?', (age, id))
|
||||||
|
|
||||||
|
def set_user_list(self, id: int, list: Union[str, None]):
|
||||||
|
self.cursor.execute('UPDATE info SET list=? WHERE id=?', (list, id))
|
||||||
|
|
||||||
|
def set_user_fav(self, id: int, fav: str):
|
||||||
|
self.cursor.execute('UPDATE info SET fav=? WHERE id=?', (fav, id))
|
||||||
|
|
||||||
|
def set_user_waifu(self, id: int, waifu: str):
|
||||||
|
self.cursor.execute('UPDATE info SET waifu=? WHERE id=?', (waifu, id))
|
||||||
|
|
||||||
|
def set_user_husbando(self, id: int, husbando: str):
|
||||||
|
self.cursor.execute('UPDATE info SET husbando=? WHERE id=?', (husbando, id))
|
||||||
|
|
||||||
|
# --- points --- #
|
||||||
|
|
||||||
|
def add_user_gold(self, id: int, gold: int):
|
||||||
|
self.cursor.execute('UPDATE economy SET gold=gold + ? WHERE id=?', (gold, id))
|
||||||
|
|
||||||
|
def has_remove_user_gold(self, id: int, gold: int) -> bool:
|
||||||
|
self.cursor.execute('UPDATE economy SET gold=gold - ? WHERE id=? AND gold >= ?', (gold, id, gold))
|
||||||
|
return self.cursor.rowcount > 0
|
||||||
|
|
||||||
|
def get_user_items(self, id: int) -> Dict[str, int]:
|
||||||
|
self.cursor.execute('SELECT * FROM economy WHERE id=?', (id,))
|
||||||
|
result = self.cursor.fetchone()
|
||||||
|
return {
|
||||||
|
'gold': result[1],
|
||||||
|
'extra vote': result[2],
|
||||||
|
'color': result[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_leaderboard(self) -> Dict[str, int]:
|
||||||
|
self.cursor.execute('SELECT id, gold FROM economy ORDER BY gold DESC LIMIT 10')
|
||||||
|
return {user[0]: user[1] for user in self.cursor.fetchall()}
|
||||||
|
|
||||||
|
def set_user_extra_vote(self, id: int, count: int):
|
||||||
|
self.cursor.execute('UPDATE economy SET extra_vote=extra_vote + ? WHERE id=? AND extra_vote > 0', (count, id))
|
||||||
|
return self.cursor.rowcount > 0
|
||||||
|
|
||||||
|
def set_user_color_count(self, id: int, count: int):
|
||||||
|
self.cursor.execute('UPDATE economy SET color=color + ? WHERE id=?', (count, id))
|
||||||
|
|
||||||
|
def set_user_color(self, id: int, color: str):
|
||||||
|
self.cursor.execute('UPDATE info SET color=? WHERE id=?', (color, id))
|
||||||
|
|
||||||
|
# --- family --- #
|
||||||
|
|
||||||
|
def add_user_parent(self, user: int, parent_id: int):
|
||||||
|
self.cursor.execute('INSERT INTO family (id, parent) SELECT ?, ? WHERE NOT EXISTS(SELECT * FROM family WHERE id=? AND parent=?)', (user, parent_id, user, parent_id))
|
||||||
|
|
||||||
|
def remove_user_parent(self, user: int, parent_id: int):
|
||||||
|
self.cursor.execute('DELETE FROM family WHERE id=? AND parent=?', (user, parent_id))
|
||||||
|
|
||||||
|
def get_user_parents(self, user: int) -> List[int]:
|
||||||
|
parents = []
|
||||||
|
self.cursor.execute('SELECT parent FROM family WHERE id=?', (user,))
|
||||||
|
for parent in self.cursor.fetchall:
|
||||||
|
parents.append(parent[0])
|
||||||
|
|
||||||
|
return parents
|
||||||
|
|
||||||
|
def get_user_children(self, user: int) -> List[int]:
|
||||||
|
children = []
|
||||||
|
self.cursor.execute('SELECT id FROM family WHERE parent=?', (user,))
|
||||||
|
for parent in self.cursor.fetchall:
|
||||||
|
children.append(parent[0])
|
||||||
|
|
||||||
|
return children
|
295
kaizenbot/flags.py
Normal file
295
kaizenbot/flags.py
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
from copy import copy
|
||||||
|
from discord.ext import commands
|
||||||
|
from enum import Enum
|
||||||
|
import shlex
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from .utils import Embeds, role_names
|
||||||
|
|
||||||
|
|
||||||
|
class AlternativeStoreType:
|
||||||
|
|
||||||
|
class _StoreType:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Bool(_StoreType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Int(_StoreType):
|
||||||
|
def __init__(self, min: int = float('-inf'), max: int = float('inf')):
|
||||||
|
if min > max:
|
||||||
|
raise ValueError('min must be higher than max')
|
||||||
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
|
||||||
|
class Value(_StoreType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
store_bool = Bool()
|
||||||
|
store_int = Int()
|
||||||
|
store_value = Value()
|
||||||
|
|
||||||
|
|
||||||
|
class StoreType(Enum):
|
||||||
|
store_bool = 'store_bool'
|
||||||
|
store_value = 'store_value'
|
||||||
|
|
||||||
|
|
||||||
|
class _Parsed:
|
||||||
|
def __init__(self):
|
||||||
|
self.normal_args = ()
|
||||||
|
self.ctx: commands.Context = None
|
||||||
|
|
||||||
|
|
||||||
|
class FlagErrors:
|
||||||
|
|
||||||
|
class _FlagError(Exception):
|
||||||
|
|
||||||
|
def __init__(self, message: str = None, flag: str = None):
|
||||||
|
self.flag = flag
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
class DoubleFlagError(_FlagError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class FlagParseError(_FlagError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class FlagPositionError(_FlagError):
|
||||||
|
|
||||||
|
def __init__(self, message: str = None, flag: str = None):
|
||||||
|
super().__init__(message, flag)
|
||||||
|
|
||||||
|
class FlagStoreError(_FlagError):
|
||||||
|
|
||||||
|
def __init__(self, message: str = None, flag: str = None, store_type: StoreType = None, value=None):
|
||||||
|
self.store_type = store_type
|
||||||
|
self.value = value
|
||||||
|
super().__init__(message, flag)
|
||||||
|
|
||||||
|
class RoleNotAllowedError(_FlagError):
|
||||||
|
|
||||||
|
def __init__(self, message: str = None, flag: str = None, role: str = None):
|
||||||
|
self.role = role
|
||||||
|
super().__init__(message, flag)
|
||||||
|
|
||||||
|
class QuotationError(Exception):
|
||||||
|
|
||||||
|
def __init__(self, message: str = None):
|
||||||
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class _ParsedFlag:
|
||||||
|
|
||||||
|
def __init__(self, flag: str, value):
|
||||||
|
self.flag = flag
|
||||||
|
self._value = value
|
||||||
|
|
||||||
|
def __new__(cls, flag: str, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return self._value + other
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return True if self._value else False
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return self._value >= other
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return self._value < other
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.flag, self._value))
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self._value <= other
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._value)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self._value < other
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self._value
|
||||||
|
|
||||||
|
|
||||||
|
class Flag:
|
||||||
|
|
||||||
|
def __init__(self, raise_errors=True,
|
||||||
|
double_flag_error='The flag `{flag}` was already given',
|
||||||
|
flag_position_error='Flags must be called at the end of a command',
|
||||||
|
quotation_error='At least one quotation mark is missing (`"` or `\'`)'):
|
||||||
|
self.double_flag_error = double_flag_error
|
||||||
|
self.raise_errors = raise_errors
|
||||||
|
self.flag_position_error = flag_position_error
|
||||||
|
self.quotation_error = quotation_error
|
||||||
|
self._flags = {}
|
||||||
|
|
||||||
|
def add_flag(self, *flags,
|
||||||
|
store_type: typing.Union[StoreType, typing.Tuple[StoreType, typing.Any]],
|
||||||
|
parser: typing.Callable = None,
|
||||||
|
allowed_roles: typing.Union[typing.List[str], typing.Tuple[str]] = [],
|
||||||
|
disallowed_roles: typing.Union[typing.List[str], typing.Tuple[str], typing.Dict[str, str]] = [],
|
||||||
|
not_allowed_role_message='User has no allowed role for flag {flag}',
|
||||||
|
wrong_store_type_message=None,
|
||||||
|
help: str = '', show_help=True):
|
||||||
|
if store_type == StoreType.store_bool and parser is not None:
|
||||||
|
raise FlagErrors.FlagParseError('The flag parser cannot be set if the store type is \'store_bool\'')
|
||||||
|
|
||||||
|
if store_type == StoreType.store_bool:
|
||||||
|
default_value = False
|
||||||
|
else:
|
||||||
|
default_value = None
|
||||||
|
|
||||||
|
for allowed in allowed_roles:
|
||||||
|
if allowed in disallowed_roles:
|
||||||
|
raise ValueError(f'Role `{allowed}` cannot be allowed and disallowed at the same time')
|
||||||
|
|
||||||
|
flag_information = {'store_type': store_type[0] if isinstance(store_type, tuple) else store_type, 'default': store_type[1] if isinstance(store_type, tuple) else default_value,
|
||||||
|
'parser': parser,
|
||||||
|
'allowed_roles': allowed_roles, 'disallowed_roles': disallowed_roles,
|
||||||
|
'not_allowed_role_message': not_allowed_role_message,
|
||||||
|
'wrong_store_type_message': wrong_store_type_message,
|
||||||
|
'help': help, 'show_help': show_help}
|
||||||
|
|
||||||
|
for flag in flags:
|
||||||
|
flag = str(flag)
|
||||||
|
self._flags[flag] = flag_information
|
||||||
|
|
||||||
|
def flags(self) -> typing.Dict[str, typing.Any]:
|
||||||
|
return copy(self._flags)
|
||||||
|
|
||||||
|
async def parse(self, args: str, ctx: commands.Context):
|
||||||
|
# (re)sets the attributes every time
|
||||||
|
parsed = _Parsed()
|
||||||
|
|
||||||
|
try:
|
||||||
|
shlex_args = shlex.split(args)
|
||||||
|
except ValueError as error:
|
||||||
|
if str(error) == 'No closing quotation':
|
||||||
|
if self.raise_errors:
|
||||||
|
raise FlagErrors.QuotationError(self.quotation_error)
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=self.quotation_error))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise error
|
||||||
|
|
||||||
|
for flag, information in self._flags.items():
|
||||||
|
parsed.__setattr__(flag[2:], information['default'])
|
||||||
|
|
||||||
|
flag_indexed = False
|
||||||
|
normal_args = []
|
||||||
|
parsed_flags = []
|
||||||
|
roles = role_names(ctx.author)
|
||||||
|
|
||||||
|
for i, arg in enumerate(shlex_args):
|
||||||
|
arg = str(arg).replace('"', '').replace("'", '')
|
||||||
|
if '=' in arg:
|
||||||
|
arg, value = arg.split('=', 1)
|
||||||
|
else:
|
||||||
|
value = None
|
||||||
|
|
||||||
|
if arg in self._flags:
|
||||||
|
if arg in parsed_flags:
|
||||||
|
if self.raise_errors:
|
||||||
|
raise FlagErrors.DoubleFlagError(self.double_flag_error.format(flag=arg))
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=self.double_flag_error.format(flag=arg)))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
parsed_flags.append(arg)
|
||||||
|
|
||||||
|
if not flag_indexed:
|
||||||
|
flag_indexed = True
|
||||||
|
flag = self._flags[arg]
|
||||||
|
|
||||||
|
# --- #
|
||||||
|
allowed_roles = flag['allowed_roles']
|
||||||
|
if allowed_roles:
|
||||||
|
if not any(allowed in roles for allowed in allowed_roles):
|
||||||
|
error = flag['not_allowed_role_message'].format(flag=arg)
|
||||||
|
if self.raise_errors:
|
||||||
|
raise FlagErrors.RoleNotAllowedError(error)
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=error))
|
||||||
|
return
|
||||||
|
disallowed_roles = flag['disallowed_roles']
|
||||||
|
if disallowed_roles:
|
||||||
|
for disallowed in disallowed_roles:
|
||||||
|
if disallowed in roles:
|
||||||
|
error = disallowed_roles[disallowed] if isinstance(disallowed_roles, dict) else 'The role `{role}` is not allowed to use the {flag} flag'
|
||||||
|
error = error.format(role=disallowed, flag=arg)
|
||||||
|
if self.raise_errors:
|
||||||
|
raise FlagErrors.RoleNotAllowedError(message=error, flag=arg, role=disallowed)
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=error))
|
||||||
|
return
|
||||||
|
|
||||||
|
store_type = flag['store_type']
|
||||||
|
arg_without_prefix = arg[2:]
|
||||||
|
if store_type == StoreType.store_bool:
|
||||||
|
error = flag['wrong_store_type_message'] if flag['wrong_store_type_message'] else 'Flag `{flag}` must not contain a value'
|
||||||
|
error = error.format(flag=arg)
|
||||||
|
if value:
|
||||||
|
if self.raise_errors:
|
||||||
|
raise FlagErrors.FlagStoreError(message=error, flag=arg, store_type=store_type, value=value)
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=error))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
parsed.__setattr__(arg_without_prefix, _ParsedFlag(flag, True))
|
||||||
|
elif store_type == StoreType.store_value:
|
||||||
|
error = flag['wrong_store_type_message'] if flag['wrong_store_type_message'] else 'Flag `{flag}` must not contain a value'
|
||||||
|
error = error.format(flag=arg)
|
||||||
|
if not value:
|
||||||
|
if self.raise_errors:
|
||||||
|
raise FlagErrors.FlagStoreError(message=error, flag=arg, store_type=store_type)
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=error))
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
if parser := flag['parser']:
|
||||||
|
value_parsed = await parser(ctx, arg, value)
|
||||||
|
if isinstance(value_parsed, bool):
|
||||||
|
if value_parsed:
|
||||||
|
parsed.__setattr__(arg_without_prefix, _ParsedFlag(flag, True))
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
parsed.__setattr__(arg_without_prefix, _ParsedFlag(flag, value_parsed))
|
||||||
|
else:
|
||||||
|
parsed.__setattr__(arg_without_prefix, _ParsedFlag(flag, value))
|
||||||
|
|
||||||
|
elif flag_indexed:
|
||||||
|
if self.raise_errors:
|
||||||
|
raise FlagErrors.FlagPositionError(message=self.flag_position_error)
|
||||||
|
else:
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=self.flag_position_error))
|
||||||
|
return
|
||||||
|
|
||||||
|
else:
|
||||||
|
normal_args.append(arg)
|
||||||
|
|
||||||
|
parsed.normal_args = tuple(normal_args)
|
||||||
|
parsed.ctx = ctx
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
def get_flags(command: commands.Command) -> typing.Union[Flag, None]:
|
||||||
|
flags = command.__original_kwargs__.get('flags', None)
|
||||||
|
if isinstance(flags, str):
|
||||||
|
try:
|
||||||
|
return command.cog.__getattribute__(flags)
|
||||||
|
except AttributeError:
|
||||||
|
raise AttributeError(f'The flag `{flags}` does not exist')
|
||||||
|
elif isinstance(flags, Flag):
|
||||||
|
return flags
|
||||||
|
else:
|
||||||
|
return None
|
13
kaizenbot/user.py
Normal file
13
kaizenbot/user.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
|
||||||
|
def __init__(self, id: int, name: str, tag: str, joined: datetime.datetime, accepted_rules_date: datetime.datetime = None, warning_time: datetime.datetime = None, join_message = None):
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.tag = tag
|
||||||
|
self.joined = joined
|
||||||
|
self.accepted_rules_date = accepted_rules_date
|
||||||
|
self.warning_time = warning_time
|
||||||
|
self.join_message = join_message
|
476
kaizenbot/utils.py
Normal file
476
kaizenbot/utils.py
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
import asyncio
|
||||||
|
import random
|
||||||
|
import typing
|
||||||
|
from pathlib import Path
|
||||||
|
from threading import Timer as _Timer
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import menus
|
||||||
|
|
||||||
|
from . import logger
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncTimer:
|
||||||
|
|
||||||
|
def __init__(self, start: float, callback, *args):
|
||||||
|
self._callback = callback
|
||||||
|
self._args = args
|
||||||
|
self._start = start
|
||||||
|
self._task = asyncio.ensure_future(self._job())
|
||||||
|
|
||||||
|
async def _job(self):
|
||||||
|
await asyncio.sleep(self._start)
|
||||||
|
await self._callback(*self._args)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self._task.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncIntervalTimer(AsyncTimer):
|
||||||
|
|
||||||
|
def __init__(self, first_start: float, interval: float, callback, *args):
|
||||||
|
super().__init__(first_start, callback, *args)
|
||||||
|
self._interval = interval
|
||||||
|
|
||||||
|
async def _job(self):
|
||||||
|
await super()._job()
|
||||||
|
while True:
|
||||||
|
await asyncio.sleep(self._interval)
|
||||||
|
await self._callback(*self._args)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self._task.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
class IntervalTimer:
|
||||||
|
def __init__(self, first_start: float, interval: float, func, *args):
|
||||||
|
self.first_start = first_start
|
||||||
|
self.interval = interval
|
||||||
|
self.handlerFunction = func
|
||||||
|
self.args = args
|
||||||
|
self.running = False
|
||||||
|
self.timer = _Timer(self.interval, self.run, args)
|
||||||
|
|
||||||
|
def run(self, *args):
|
||||||
|
sleep(self.first_start)
|
||||||
|
self.handlerFunction(*args)
|
||||||
|
while self.running:
|
||||||
|
sleep(self.interval)
|
||||||
|
self.handlerFunction(*args)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self.running = True
|
||||||
|
self.timer.start()
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.running = False
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Embeds:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def send_kaizen_infos(channel):
|
||||||
|
file = discord.File(Path.cwd().joinpath('assets', 'kaizen-round.png'))
|
||||||
|
|
||||||
|
embed = discord.Embed(title='**Kaizen**', description='Folge Kaizen auf den folgenden Kanälen, um nichts mehr zu verpassen!', color=discord.Color(0xff0000))
|
||||||
|
embed.set_thumbnail(url='attachment://kaizen-round.png')
|
||||||
|
embed.add_field(name='**🎥Youtube Hauptkanal**', value='Abonniere Kaizen auf __**[Youtube](https://www.youtube.com/c/KaizenAnime)**__ um kein Anime Video mehr zu verpassen!', inline=False)
|
||||||
|
embed.add_field(name='**📑Youtube Toplisten-Kanal**', value='Abonniere Kaizen\'s __**[Toplisten-Kanal](https://www.youtube.com/channel/UCoijG8JqKb1rRZofx5b-LCw)**__ um kein Toplisten-Video mehr zu verpassen!', inline=False)
|
||||||
|
embed.add_field(name='**📯Youtube Stream-Clips & mehr**', value='Abonniere Kaizen\'s __**[Youtube Kanal](https://www.youtube.com/channel/UCodeTj8SJ-5HhJgC_Elr1Dw)**__ für Stream-Clips & mehr!', inline=False)
|
||||||
|
embed.add_field(name='**📲Twitch**', value='Folge Kaizen auf __**[Twitch](https://www.twitch.tv/kaizenanime)**__ und verpasse keinen Stream mehr! '
|
||||||
|
'Subbe Kaizen um eine exklusive Rolle auf dem Discord Server zu bekommen!', inline=False)
|
||||||
|
embed.add_field(name='**📢Twitter**', value='Folge Kaizen auf __**[Twitter](https://twitter.com/Kaizen_Anime)**__ um aktuelle Informationen zu bekommen und in Videos / Streams mitzuwirken!', inline=False)
|
||||||
|
embed.add_field(name='**📷Instagram**', value='Folge Kaizen auf __**[Instagram](https://www.instagram.com/kaizen.animeyt/)**__!', inline=False)
|
||||||
|
await channel.send(embed=embed, file=file)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def error_embed(title: typing.Union[str, None] = None, description: typing.Union[str, None] = None) -> discord.Embed:
|
||||||
|
embed = discord.Embed(color=discord.Color(0xff0000))
|
||||||
|
if title:
|
||||||
|
embed.title = title
|
||||||
|
if description:
|
||||||
|
embed.description = description
|
||||||
|
return embed
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def warn_embed(title: typing.Union[str, None] = None, description: typing.Union[str, None] = None) -> discord.Embed:
|
||||||
|
embed = discord.Embed(color=discord.Color(0xff9055))
|
||||||
|
if title:
|
||||||
|
embed.title = title
|
||||||
|
if description:
|
||||||
|
embed.description = description
|
||||||
|
return embed
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def success_embed(title: typing.Union[str, None] = None, description: typing.Union[str, None] = None) -> discord.Embed:
|
||||||
|
embed = discord.Embed(color=discord.Color(0x00ff00))
|
||||||
|
if title:
|
||||||
|
embed.title = title
|
||||||
|
if description:
|
||||||
|
embed.description = description
|
||||||
|
return embed
|
||||||
|
|
||||||
|
|
||||||
|
class MenuListPageSource(menus.ListPageSource):
|
||||||
|
|
||||||
|
def __init__(self, data):
|
||||||
|
super().__init__(data, per_page=1)
|
||||||
|
|
||||||
|
async def format_page(self, menu, embeds):
|
||||||
|
return embeds
|
||||||
|
|
||||||
|
|
||||||
|
def random_sequence_not_in_string(string: str):
|
||||||
|
sequence = '+'
|
||||||
|
while sequence in string:
|
||||||
|
choice = random.choice('+*~-:%&')
|
||||||
|
sequence = choice + sequence + choice
|
||||||
|
|
||||||
|
return sequence
|
||||||
|
|
||||||
|
|
||||||
|
def role_names(member: discord.Member) -> typing.List[str]:
|
||||||
|
return [role.name for role in member.roles]
|
||||||
|
|
||||||
|
|
||||||
|
# ADDED AFTERWARDS: I've stol- copied the following code from a tweepy (https://github.com/tweepy/tweepy) PR or gist (from where exactly I do not know anymore lul)
|
||||||
|
# at the time when they didn't support async actions
|
||||||
|
|
||||||
|
# Tweepy
|
||||||
|
# Copyright 2009-2021 Joshua Roesslein
|
||||||
|
# See LICENSE for details.
|
||||||
|
|
||||||
|
import json
|
||||||
|
from math import inf
|
||||||
|
from platform import python_version
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from oauthlib.oauth1 import Client as OAuthClient
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
|
import tweepy
|
||||||
|
from tweepy.error import TweepError
|
||||||
|
from tweepy.models import Status
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncStream:
|
||||||
|
"""Stream realtime Tweets asynchronously
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
consumer_key: :class:`str`
|
||||||
|
Consumer key
|
||||||
|
consumer_secret: :class:`str`
|
||||||
|
Consuemr secret
|
||||||
|
access_token: :class:`str`
|
||||||
|
Access token
|
||||||
|
access_token_secret: :class:`str`
|
||||||
|
Access token secret
|
||||||
|
max_retries: Optional[:class:`int`]
|
||||||
|
Number of times to attempt to (re)connect the stream.
|
||||||
|
Defaults to infinite.
|
||||||
|
proxy: Optional[:class:`str`]
|
||||||
|
Proxy URL
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, consumer_key, consumer_secret, access_token,
|
||||||
|
access_token_secret, max_retries=inf, proxy=None):
|
||||||
|
self.consumer_key = consumer_key
|
||||||
|
self.consumer_secret = consumer_secret
|
||||||
|
self.access_token = access_token
|
||||||
|
self.access_token_secret = access_token_secret
|
||||||
|
self.max_retries = max_retries
|
||||||
|
self.proxy = proxy
|
||||||
|
|
||||||
|
self.session = None
|
||||||
|
self.task = None
|
||||||
|
self.user_agent = (
|
||||||
|
f"Python/{python_version()} "
|
||||||
|
f"aiohttp/{aiohttp.__version__} "
|
||||||
|
f"Tweepy/{tweepy.__version__}"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _connect(self, method, endpoint, params={}, headers=None,
|
||||||
|
body=None):
|
||||||
|
error_count = 0
|
||||||
|
# https://developer.twitter.com/en/docs/twitter-api/v1/tweets/filter-realtime/guides/connecting
|
||||||
|
stall_timeout = 90
|
||||||
|
network_error_wait = network_error_wait_step = 0.25
|
||||||
|
network_error_wait_max = 16
|
||||||
|
http_error_wait = http_error_wait_start = 5
|
||||||
|
http_error_wait_max = 320
|
||||||
|
http_420_error_wait_start = 60
|
||||||
|
|
||||||
|
oauth_client = OAuthClient(self.consumer_key, self.consumer_secret,
|
||||||
|
self.access_token, self.access_token_secret)
|
||||||
|
|
||||||
|
if self.session is None or self.session.closed:
|
||||||
|
self.session = aiohttp.ClientSession(
|
||||||
|
headers={"User-Agent": self.user_agent},
|
||||||
|
timeout=aiohttp.ClientTimeout(sock_read=stall_timeout)
|
||||||
|
)
|
||||||
|
|
||||||
|
url = f"https://stream.twitter.com/1.1/{endpoint}.json"
|
||||||
|
url = str(URL(url).with_query(sorted(params.items())))
|
||||||
|
|
||||||
|
try:
|
||||||
|
while error_count <= self.max_retries:
|
||||||
|
request_url, request_headers, request_body = oauth_client.sign(
|
||||||
|
url, method, body, headers
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
async with self.session.request(
|
||||||
|
method, request_url, headers=request_headers,
|
||||||
|
data=request_body, proxy=self.proxy
|
||||||
|
) as resp:
|
||||||
|
if resp.status == 200:
|
||||||
|
error_count = 0
|
||||||
|
http_error_wait = http_error_wait_start
|
||||||
|
network_error_wait = network_error_wait_step
|
||||||
|
|
||||||
|
await self.on_connect()
|
||||||
|
|
||||||
|
async for line in resp.content:
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
await self.on_data(line)
|
||||||
|
else:
|
||||||
|
await self.on_keep_alive()
|
||||||
|
|
||||||
|
await self.on_closed(resp)
|
||||||
|
else:
|
||||||
|
await self.on_request_error(resp.status)
|
||||||
|
|
||||||
|
error_count += 1
|
||||||
|
|
||||||
|
if resp.status == 420:
|
||||||
|
if http_error_wait < http_420_error_wait_start:
|
||||||
|
http_error_wait = http_420_error_wait_start
|
||||||
|
|
||||||
|
await asyncio.sleep(http_error_wait)
|
||||||
|
|
||||||
|
http_error_wait *= 2
|
||||||
|
if resp.status != 420:
|
||||||
|
if http_error_wait > http_error_wait_max:
|
||||||
|
http_error_wait = http_error_wait_max
|
||||||
|
except (aiohttp.ClientConnectionError,
|
||||||
|
aiohttp.ClientPayloadError) as e:
|
||||||
|
await self.on_connection_error()
|
||||||
|
|
||||||
|
await asyncio.sleep(network_error_wait)
|
||||||
|
|
||||||
|
network_error_wait += network_error_wait_step
|
||||||
|
if network_error_wait > network_error_wait_max:
|
||||||
|
network_error_wait = network_error_wait_max
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
await self.on_exception(e)
|
||||||
|
finally:
|
||||||
|
await self.session.close()
|
||||||
|
await self.on_disconnect()
|
||||||
|
|
||||||
|
async def filter(self, follow=None, track=None, locations=None,
|
||||||
|
stall_warnings=False):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
Filter realtime Tweets
|
||||||
|
https://developer.twitter.com/en/docs/twitter-api/v1/tweets/filter-realtime/api-reference/post-statuses-filter
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
follow: Optional[List[Union[:class:`int`, :class:`str`]]]
|
||||||
|
A list of user IDs, indicating the users to return statuses for in
|
||||||
|
the stream. See https://developer.twitter.com/en/docs/twitter-api/v1/tweets/filter-realtime/guides/basic-stream-parameters
|
||||||
|
for more information.
|
||||||
|
track: Optional[List[:class:`str`]]
|
||||||
|
Keywords to track. Phrases of keywords are specified by a list. See
|
||||||
|
https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/basic-stream-parameters
|
||||||
|
for more information.
|
||||||
|
locations: Optional[List[:class:`float`]]
|
||||||
|
Specifies a set of bounding boxes to track. See
|
||||||
|
https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/basic-stream-parameters
|
||||||
|
for more information.
|
||||||
|
stall_warnings: Optional[:class:`bool`]
|
||||||
|
Specifies whether stall warnings should be delivered. See
|
||||||
|
https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/basic-stream-parameters
|
||||||
|
for more information. Def
|
||||||
|
logger = logging.getLogger('kaizen')aults to False.
|
||||||
|
Returns :class:`asyncio.Task`
|
||||||
|
"""
|
||||||
|
if self.task is not None and not self.task.done():
|
||||||
|
raise TweepError("Stream is already connected")
|
||||||
|
|
||||||
|
endpoint = "statuses/filter"
|
||||||
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||||||
|
|
||||||
|
body = {}
|
||||||
|
if follow is not None:
|
||||||
|
body["follow"] = ','.join(map(str, follow))
|
||||||
|
if track is not None:
|
||||||
|
body["track"] = ','.join(map(str, track))
|
||||||
|
if locations is not None:
|
||||||
|
if len(locations) % 4:
|
||||||
|
raise TweepError(
|
||||||
|
"Number of location coordinates should be a multiple of 4"
|
||||||
|
)
|
||||||
|
body["locations"] = ','.join(
|
||||||
|
f"{location:.4f}" for location in locations
|
||||||
|
)
|
||||||
|
if stall_warnings:
|
||||||
|
body["stall_warnings"] = "true"
|
||||||
|
|
||||||
|
self.task = asyncio.create_task(
|
||||||
|
self._connect("POST", endpoint, headers=headers, body=body or None)
|
||||||
|
)
|
||||||
|
return self.task
|
||||||
|
|
||||||
|
async def sample(self, stall_warnings=False):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
Sample realtime Tweets
|
||||||
|
https://developer.twitter.com/en/docs/twitter-api/v1/tweets/sample-realtime/api-reference/get-statuses-sample
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
stall_warnings: Optional[:class:`bool`]
|
||||||
|
Specifies whether stall warnings should be delivered. See
|
||||||
|
https://developer.twitter.com/en/docs/tweets/filter-realtime/guides/basic-stream-parameters
|
||||||
|
for more information. Defaults to False.
|
||||||
|
Returns :class:`asyncio.Task`
|
||||||
|
"""
|
||||||
|
if self.task is not None and not self.task.done():
|
||||||
|
raise TweepError("Stream is already connected")
|
||||||
|
|
||||||
|
endpoint = "statuses/sample"
|
||||||
|
|
||||||
|
params = {}
|
||||||
|
if stall_warnings:
|
||||||
|
params["stall_warnings"] = "true"
|
||||||
|
|
||||||
|
self.task = asyncio.create_task(
|
||||||
|
self._connect("GET", endpoint, params=params)
|
||||||
|
)
|
||||||
|
return self.task
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
"""Disconnect the stream"""
|
||||||
|
if self.task is not None:
|
||||||
|
self.task.cancel()
|
||||||
|
|
||||||
|
async def on_closed(self, resp):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when the stream has been closed by Twitter.
|
||||||
|
"""
|
||||||
|
logger.error("Stream connection closed by Twitter")
|
||||||
|
|
||||||
|
async def on_connect(self):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called after successfully connecting to the streaming API.
|
||||||
|
"""
|
||||||
|
# logger.info("Stream connected")
|
||||||
|
|
||||||
|
async def on_connection_error(self):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when the stream connection errors or times out.
|
||||||
|
"""
|
||||||
|
# logger.error("Stream connection has errored or timed out")
|
||||||
|
|
||||||
|
async def on_disconnect(self):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when the stream has disconnected.
|
||||||
|
"""
|
||||||
|
# logger.info("Stream disconnected")
|
||||||
|
|
||||||
|
async def on_exception(self, exception):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when an unhandled exception occurs.
|
||||||
|
"""
|
||||||
|
logger.exception("Stream encountered an exception")
|
||||||
|
|
||||||
|
async def on_keep_alive(self):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a keep-alive message is received.
|
||||||
|
"""
|
||||||
|
#logger.debug("Received keep-alive message")
|
||||||
|
|
||||||
|
async def on_request_error(self, status_code):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a non-200 HTTP status code is encountered.
|
||||||
|
"""
|
||||||
|
# logger.error("Stream encountered HTTP Error: %d", status_code)
|
||||||
|
|
||||||
|
async def on_data(self, raw_data):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when raw data is received from the stream.
|
||||||
|
This method handles sending the data to other methods, depending on the
|
||||||
|
message type.
|
||||||
|
https://developer.twitter.com/en/docs/twitter-api/v1/tweets/filter-realtime/guides/streaming-message-types
|
||||||
|
"""
|
||||||
|
data = json.loads(raw_data)
|
||||||
|
|
||||||
|
if "in_reply_to_status_id" in data:
|
||||||
|
status = Status.parse(None, data)
|
||||||
|
return await self.on_status(status)
|
||||||
|
if "delete" in data:
|
||||||
|
delete = data["delete"]["status"]
|
||||||
|
return await self.on_delete(delete["id"], delete["user_id"])
|
||||||
|
if "disconnect" in data:
|
||||||
|
return await self.on_disconnect_message(data["disconnect"])
|
||||||
|
if "limit" in data:
|
||||||
|
return await self.on_limit(data["limit"]["track"])
|
||||||
|
if "scrub_geo" in data:
|
||||||
|
return await self.on_scrub_geo(data["scrub_geo"])
|
||||||
|
if "status_withheld" in data:
|
||||||
|
return await self.on_status_withheld(data["status_withheld"])
|
||||||
|
if "user_withheld" in data:
|
||||||
|
return await self.on_user_withheld(data["user_withheld"])
|
||||||
|
if "warning" in data:
|
||||||
|
return await self.on_warning(data["warning"])
|
||||||
|
|
||||||
|
logger.warning("Received unknown message type: %s", raw_data)
|
||||||
|
|
||||||
|
async def on_status(self, status):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a status is received.
|
||||||
|
"""
|
||||||
|
# logger.debug("Received status: %d", status.id)
|
||||||
|
|
||||||
|
async def on_delete(self, status_id, user_id):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a status deletion notice is received.
|
||||||
|
"""
|
||||||
|
# logger.debug("Received status deletion notice: %d", status_id)
|
||||||
|
|
||||||
|
async def on_disconnect_message(self, message):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a disconnect message is received.
|
||||||
|
"""
|
||||||
|
# logger.warning("Received disconnect message: %s", message)
|
||||||
|
|
||||||
|
async def on_limit(self, track):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a limit notice is received.
|
||||||
|
"""
|
||||||
|
# logger.debug("Received limit notice: %d", track)
|
||||||
|
|
||||||
|
async def on_scrub_geo(self, notice):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a location deletion notice is received.
|
||||||
|
"""
|
||||||
|
# logger.debug("Received location deletion notice: %s", notice)
|
||||||
|
|
||||||
|
async def on_status_withheld(self, notice):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a status withheld content notice is received.
|
||||||
|
"""
|
||||||
|
# logger.debug("Received status withheld content notice: %s", notice)
|
||||||
|
|
||||||
|
async def on_user_withheld(self, notice):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a user withheld content notice is received.
|
||||||
|
"""
|
||||||
|
# logger.debug("Received user withheld content notice: %s", notice)
|
||||||
|
|
||||||
|
async def on_warning(self, notice):
|
||||||
|
"""This method is a coroutine.
|
||||||
|
This is called when a stall warning message is received.
|
||||||
|
"""
|
||||||
|
# logger.warning("Received stall warning: %s", notice)
|
444
main.py
Normal file
444
main.py
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
#!/usr/local/bin/python3.9
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from copy import copy
|
||||||
|
import datetime
|
||||||
|
import discord
|
||||||
|
from discord import errors
|
||||||
|
from discord.ext import commands
|
||||||
|
from discord.ext.commands import errors as ext_errors
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import tweepy
|
||||||
|
from twitchAPI.twitch import Twitch
|
||||||
|
|
||||||
|
from kaizenbot import glob, logger
|
||||||
|
from kaizenbot.database import Database
|
||||||
|
from kaizenbot.user import User
|
||||||
|
from kaizenbot.utils import AsyncIntervalTimer, AsyncStream, IntervalTimer, Embeds
|
||||||
|
|
||||||
|
import kaizenbot.commands as cogs
|
||||||
|
|
||||||
|
|
||||||
|
class Bot(commands.Bot):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
intents = discord.Intents(messages=True, members=True, guilds=True, reactions=True)
|
||||||
|
super().__init__(command_prefix='$', case_insensitive=True, intents=intents)
|
||||||
|
self.support = Support(self)
|
||||||
|
|
||||||
|
# ADDED AFTERWARDS: hardcoded password, very secure...
|
||||||
|
self.database = Database('kaizen', '9y*"xF(BxLZ!HpgKn_')
|
||||||
|
self.image_id = self.database.get_image_id()
|
||||||
|
self.message_id = self.database.get_message_id()
|
||||||
|
self.all_users = self.database.get_all_users()
|
||||||
|
self.normal_users = []
|
||||||
|
self.error_users = []
|
||||||
|
|
||||||
|
for cog in [cogs.Economy, cogs.Mod, cogs.Help, cogs.Info, cogs.Kaizen, cogs.Vote, cogs.NSFW]:
|
||||||
|
loaded_cog = cog(self)
|
||||||
|
if cog == cogs.Economy:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
try:
|
||||||
|
reset_economy = IntervalTimer((datetime.datetime(year=now.year, month=now.month, day=now.day + 1)), 60*60*24, lambda: loaded_cog.reset())
|
||||||
|
except ValueError:
|
||||||
|
reset_economy = IntervalTimer((datetime.datetime(year=now.year, month=now.month + 1, day=1)), 60 * 60 * 24, lambda: loaded_cog.reset())
|
||||||
|
reset_economy.start()
|
||||||
|
self.add_cog(loaded_cog)
|
||||||
|
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_ready(self):
|
||||||
|
logger.info('Logged in')
|
||||||
|
if not hasattr(self, 'guild'):
|
||||||
|
if sys.argv[1] == 'test':
|
||||||
|
self.guild: discord.Guild = self.get_guild(768846874517438535)
|
||||||
|
self.welcome_channel = self.guild.get_channel(769292296985903184)
|
||||||
|
self.info_channel = self.welcome_channel
|
||||||
|
self.twitter_channel = self.welcome_channel
|
||||||
|
self.kaizenianer = self.guild.get_role(802222058116612136)
|
||||||
|
self.twitter_abonnenten = self.kaizenianer
|
||||||
|
self.mod = self.guild.get_role(803296051444056115)
|
||||||
|
|
||||||
|
twitter_id = 1301397827378176007
|
||||||
|
elif sys.argv[1] == 'run':
|
||||||
|
self.guild: discord.Guild = self.get_guild(796132539269382154)
|
||||||
|
self.welcome_channel = self.guild.get_channel(796876279537336423)
|
||||||
|
self.info_channel = self.guild.get_channel(803403162056523786)
|
||||||
|
self.twitter_channel = self.guild.get_channel(797555712943194163)
|
||||||
|
self.kaizenianer = self.guild.get_role(796870178939994132)
|
||||||
|
self.twitter_abonnenten = self.guild.get_role(831963234352234578)
|
||||||
|
self.mod = self.guild.get_role(796864065741258773)
|
||||||
|
|
||||||
|
twitter_id = 982950038610632705
|
||||||
|
else:
|
||||||
|
exit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
if now.hour < 12:
|
||||||
|
first_call = datetime.datetime(year=now.year, month=now.month, day=now.day, hour=12)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
first_call = datetime.datetime(year=now.year, month=now.month, day=now.day + 1, hour=12)
|
||||||
|
except ValueError:
|
||||||
|
first_call = datetime.datetime(year=now.year, month=now.month + 1, day=1, hour=12)
|
||||||
|
|
||||||
|
glob['bot'] = self
|
||||||
|
|
||||||
|
daily_kaizen_infos = AsyncIntervalTimer((first_call - now).seconds, 60*60*24, Embeds.send_kaizen_infos, [self.info_channel])
|
||||||
|
glob['timers'].append(daily_kaizen_infos)
|
||||||
|
recheck_users = AsyncIntervalTimer(60*60*24, 60*60*24, self.support.recheck_user_tags_and_names)
|
||||||
|
glob['timers'].append(recheck_users)
|
||||||
|
|
||||||
|
database_ping = IntervalTimer(0, 60 * 60, lambda: self.database.cursor.execute('SELECT * FROM stuff WHERE k=?', ('0',)))
|
||||||
|
database_ping.start()
|
||||||
|
glob['timers'].append(database_ping)
|
||||||
|
logger.debug('Started database pinger')
|
||||||
|
transcript_cleaner = IntervalTimer(0, 60 * 5, self.support.transcript_cleaner)
|
||||||
|
transcript_cleaner.start()
|
||||||
|
glob['timers'].append(transcript_cleaner)
|
||||||
|
logger.debug('Started transcript cleaner')
|
||||||
|
|
||||||
|
twitch_stream_status = AsyncIntervalTimer(0, 60 * 5, self.support.twitch_stream_status)
|
||||||
|
glob['timers'].append(twitch_stream_status)
|
||||||
|
logger.debug("Started twitch stream status listener")
|
||||||
|
|
||||||
|
# ADDED AFTERWARDS: 2 twitter listeners, why not
|
||||||
|
|
||||||
|
# twitter listener
|
||||||
|
stream = AsyncStream('...', '...',
|
||||||
|
'...', '...')
|
||||||
|
stream.on_status = self.support.twitter(twitter_id)
|
||||||
|
await stream.filter(follow=[str(twitter_id)])
|
||||||
|
|
||||||
|
auth = tweepy.OAuthHandler('...', '...')
|
||||||
|
auth.set_access_token('...', '...')
|
||||||
|
|
||||||
|
glob['twitter'] = tweepy.API(auth)
|
||||||
|
|
||||||
|
logger.debug('Started twitter listener')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
await self.support.on_startup()
|
||||||
|
|
||||||
|
self.loop = asyncio.get_event_loop()
|
||||||
|
self.loop.run_until_complete(await asyncio.gather([await self.support.check_normal_users()], return_exceptions=True))
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def help_command(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_guild_join(self, guild: discord.Guild):
|
||||||
|
if guild.id != self.guild.id:
|
||||||
|
await guild.leave()
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_connect(self):
|
||||||
|
logger.info('Connected to discord')
|
||||||
|
# self.loop = asyncio.get_event_loop()
|
||||||
|
# self.loop.run_until_complete(await self.support.check_normal_users())
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_disconnect(self):
|
||||||
|
logger.info('Disconnected from discord')
|
||||||
|
# self.loop.close()
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_command_error(self, ctx: commands.Context, error: ext_errors.CommandError):
|
||||||
|
if isinstance(error, ext_errors.CommandNotFound):
|
||||||
|
logger.info(error)
|
||||||
|
#elif isinstance(error, ext_errors.UnexpectedQuoteError):
|
||||||
|
# await ctx.send(embed=Embeds.error_embed('Please use `\'` as quotation mark for flags'))
|
||||||
|
elif isinstance(error, ext_errors.CommandOnCooldown):
|
||||||
|
cooldown = divmod(error.retry_after, 60)
|
||||||
|
await ctx.send(embed=Embeds.error_embed(description=f'Dieser Befehl kann erst wieder in {round(cooldown[0])} Minuten und {round(cooldown[1])} Sekunden genutzt werden'))
|
||||||
|
elif isinstance(error, ext_errors.MissingRequiredArgument):
|
||||||
|
await cogs.Help(self).show_help(ctx, ctx.command)
|
||||||
|
elif isinstance(error, ext_errors.MissingRole):
|
||||||
|
logger.info(f'{str(ctx.author)} tried to run `{ctx.command}` for which he has no authorization')
|
||||||
|
else:
|
||||||
|
logger.error(''.join(traceback.format_exception(etype=type(error), value=error, tb=error.__traceback__)))
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
|
||||||
|
if not payload.member.bot and payload.message_id == self.message_id:
|
||||||
|
user = self.all_users[payload.member.id]
|
||||||
|
if str(payload.emoji) == '✅':
|
||||||
|
if not user.accepted_rules_date:
|
||||||
|
await self.support.user_accepted_rules(user)
|
||||||
|
await payload.member.add_roles(self.kaizenianer)
|
||||||
|
else:
|
||||||
|
await payload.member.remove_roles(self.kaizenianer)
|
||||||
|
# `self.support.reset_user(user)` is not called here, because it gets called in `self.on_member_update(...)`
|
||||||
|
message = await self.welcome_channel.fetch_message(payload.message_id)
|
||||||
|
await message.remove_reaction(payload.emoji, payload.member)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_member_join(self, member: discord.Member):
|
||||||
|
if not member.bot:
|
||||||
|
join_message = await self.welcome_channel.send(f'Hey <@{member.id}>, Willkommen auf dem Offiziellen Discord-Server von Kaizen! **Kaizen Anime!**\n'
|
||||||
|
'Bitte lese die Regeln und warte 10 Mintuten, damit Du auf sie reacten kannst, um ein Kaizenianer zu werden! Diese findest Du, wenn Du nach oben scrollst, '
|
||||||
|
'oder indem Du zur angepinnten Nachricht springst.')
|
||||||
|
user = self.database.add_user(member, join_message.id)
|
||||||
|
self.all_users[user.id] = user
|
||||||
|
self.normal_users.append(user)
|
||||||
|
logger.info(f'User {str(member)} joined')
|
||||||
|
await asyncio.sleep(60*10 + 15)
|
||||||
|
if user.accepted_rules_date is None:
|
||||||
|
await member.send(f'Hey <@{member.id}>, Willkommen auf dem Offiziellen Discord-Server von Kaizen! **Kaizen Anime!**\n'
|
||||||
|
'Bitte lese und reacte auf die Regeln, um ein Kaizenianer zu werden! Diese findest Du, wenn Du im __**#willkommen**__ Kanal nach oben scrollst, oder indem Du zur dort angepinnten Nachricht springst.')
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_member_remove(self, member: discord.Member):
|
||||||
|
if not member.bot:
|
||||||
|
user = self.all_users[member.id]
|
||||||
|
|
||||||
|
self.database.remove_user(user.id)
|
||||||
|
await self.support.check_and_remove_normal_user(user)
|
||||||
|
del user
|
||||||
|
logger.info(f'User {str(member)} left')
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_member_update(self, before: discord.Member, after: discord.Member):
|
||||||
|
try:
|
||||||
|
user = self.all_users[before.id]
|
||||||
|
except KeyError: # this method also gets called if a new user is joined
|
||||||
|
return
|
||||||
|
if hash((str(before), before.display_name)) != hash((str(after), after.display_name)):
|
||||||
|
self.database.change_user_infos(user, after.display_name, str(after))
|
||||||
|
logger.info(f'User changed from {str(before)}, {before.display_name} to {str(after)}, {after.display_name}')
|
||||||
|
|
||||||
|
if before.roles != after.roles:
|
||||||
|
if user.accepted_rules_date and self.kaizenianer in before.roles and self.kaizenianer not in after.roles:
|
||||||
|
self.support.reset_user(user)
|
||||||
|
elif not user.accepted_rules_date and self.kaizenianer not in before.roles and self.kaizenianer in after.roles:
|
||||||
|
await self.support.user_accepted_rules(user)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_message(self, message: discord.Message):
|
||||||
|
glob['transcript'][datetime.datetime.now()] = f'[{datetime.datetime.now().strftime("%Y-%d-%m %H:%M:%S")}] - {message.author}: {message.content}' \
|
||||||
|
f'{" | Attachments: " + ", ".join([attachments.url for attachments in message.attachments]) if message.attachments else ""}'
|
||||||
|
|
||||||
|
await super().on_message(message)
|
||||||
|
|
||||||
|
|
||||||
|
class Support:
|
||||||
|
|
||||||
|
def __init__(self, bot: Bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
self.streams = False
|
||||||
|
self.title = ""
|
||||||
|
|
||||||
|
self.twitch = Twitch('...', '...')
|
||||||
|
self.twitch.authenticate_app([])
|
||||||
|
|
||||||
|
async def on_startup(self):
|
||||||
|
# the rules embed to accept to get the kaizenianer role
|
||||||
|
embed = discord.Embed(description='Hey, bitte lese & akzeptiere unsere Regeln!\n\n'
|
||||||
|
'Willkommen auf dem Offiziellen Discord-Server von KaizenAnime. Bitte lest Euch die Regen einmal durch.\n\n'
|
||||||
|
'-Keine Beleidigungen\n'
|
||||||
|
'-Kein Rassismus\n'
|
||||||
|
'-Freundlich bleiben\n'
|
||||||
|
'-Themen bitte nur in die dafür vorgesehenen Kanäle posten\n'
|
||||||
|
'-Sprachkanäle bitte mit Respekt betreten\n'
|
||||||
|
'-Kein Spam oder Eigenwerbung\n'
|
||||||
|
'-Religiöse und Politische Themen sind nur in Sprachkanälen erlaubt\n'
|
||||||
|
'-Behandle alle mit Respekt. Belästigung, Hexenjagd, Sexismus, Rassismus oder Volksverhetzung werden absolut NICHT toleriert\n'
|
||||||
|
'\n'
|
||||||
|
'Die Regeln können jederzeit (mit Vorankündigung) geändert werden',
|
||||||
|
color=000000)
|
||||||
|
|
||||||
|
if not self.bot.image_id:
|
||||||
|
image = await self.bot.welcome_channel.send(file=discord.File(Path.cwd().joinpath('assets', 'rules.png')))
|
||||||
|
self.bot.database.set_image_id(image.id)
|
||||||
|
self.bot.image_id = image.id
|
||||||
|
|
||||||
|
if self.bot.message_id:
|
||||||
|
message = await self.bot.welcome_channel.fetch_message(self.bot.message_id)
|
||||||
|
await message.delete()
|
||||||
|
self.bot.database.set_message_id(None)
|
||||||
|
self.bot.message_id = None
|
||||||
|
|
||||||
|
# checks if the posted message if there are differences between `embed` content and the posted content
|
||||||
|
if self.bot.message_id:
|
||||||
|
message = await self.bot.welcome_channel.fetch_message(self.bot.message_id)
|
||||||
|
message_embed = message.embeds[0]
|
||||||
|
# if differences are there, the posted message will be edited
|
||||||
|
if hash((embed.title, embed.description, embed.color)) != hash((message_embed.title, message_embed.description, message_embed.color)):
|
||||||
|
await message.edit(embed=embed)
|
||||||
|
logger.info('Edited rules message')
|
||||||
|
# if the message doesn't exists, it gets posted
|
||||||
|
else:
|
||||||
|
message = await self.bot.welcome_channel.send(embed=embed)
|
||||||
|
await message.add_reaction('✅')
|
||||||
|
self.bot.database.set_message_id(message.id)
|
||||||
|
self.bot.message_id = message.id
|
||||||
|
|
||||||
|
member_ids = []
|
||||||
|
for member in self.bot.guild.members:
|
||||||
|
member_ids.append(member.id)
|
||||||
|
if not member.bot:
|
||||||
|
new = False
|
||||||
|
if member.id in self.bot.all_users:
|
||||||
|
user = self.bot.all_users[member.id]
|
||||||
|
else:
|
||||||
|
user = self.bot.database.add_user(member, None)
|
||||||
|
self.bot.all_users[member.id] = user
|
||||||
|
new = True
|
||||||
|
logger.info(f'User {user.tag} joined while I was offline')
|
||||||
|
|
||||||
|
if user.accepted_rules_date is None:
|
||||||
|
if self.bot.kaizenianer in member.roles:
|
||||||
|
self.bot.database.add_user_accepted_rules(user, datetime.datetime.now())
|
||||||
|
await self.check_and_remove_normal_user(user)
|
||||||
|
logger.info(f'Added kaizenianer {user.tag}')
|
||||||
|
else:
|
||||||
|
self.bot.normal_users.append(user)
|
||||||
|
if new:
|
||||||
|
join_message = await self.bot.welcome_channel.send(f'Hey <@{member.id}>, Willkommen auf dem Offiziellen Discord-Server von Kaizen! **Kaizen Anime!**\n'
|
||||||
|
f'Bitte lese und reacte auf die Regeln, um ein Kaizenianer zu werden! '
|
||||||
|
f'Diese findest Du, wenn Du nach oben scrollst, oder indem Du zur angepinnten Nachricht springst.')
|
||||||
|
await member.send(f'Hey <@{member.id}>, Willkommen auf dem Offiziellen Discord-Server von Kaizen! **Kaizen Anime!**\n'
|
||||||
|
f'Bitte lese und reacte auf die Regeln, um ein Kaizenianer zu werden! '
|
||||||
|
f'Diese findest Du, wenn Du im __**#willkommen**__ Kanal nach oben scrollst, oder indem Du zur dort angepinnten Nachricht springst.')
|
||||||
|
user.join_message = join_message.id
|
||||||
|
elif self.bot.kaizenianer not in member.roles:
|
||||||
|
self.bot.database.reset_user(user)
|
||||||
|
|
||||||
|
for user in copy(self.bot.all_users).values():
|
||||||
|
if user.id not in member_ids:
|
||||||
|
tag = user.tag
|
||||||
|
self.bot.database.remove_user(user.id)
|
||||||
|
del user
|
||||||
|
logger.info(f'User {tag} left while I was offline')
|
||||||
|
|
||||||
|
try:
|
||||||
|
reacted_users = message.reactions[0].users() # the ✅ reaction / emoji
|
||||||
|
async for reacted_user in reacted_users:
|
||||||
|
if not reacted_user.bot:
|
||||||
|
user = self.bot.all_users[reacted_user.id]
|
||||||
|
if not user.accepted_rules_date:
|
||||||
|
await reacted_user.add_roles(self.bot.kaizenianer)
|
||||||
|
await self.user_accepted_rules(user)
|
||||||
|
else:
|
||||||
|
self.reset_user(user)
|
||||||
|
await message.remove_reaction(message.reactions[0], user)
|
||||||
|
except IndexError: # gets thrown if the embed message was new created with this bot runtime
|
||||||
|
pass
|
||||||
|
|
||||||
|
bot.database.sync()
|
||||||
|
|
||||||
|
def transcript_cleaner(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
for timestamp in copy(glob['transcript']).keys():
|
||||||
|
if timestamp + datetime.timedelta(hours=1) < now:
|
||||||
|
del glob['transcript'][timestamp]
|
||||||
|
|
||||||
|
async def recheck_user_tags_and_names(self):
|
||||||
|
for member in self.bot.guild.members:
|
||||||
|
user = self.bot.all_users[member.id]
|
||||||
|
tag = str(member)
|
||||||
|
name = member.display_name
|
||||||
|
if tag != user.tag or name != user.name:
|
||||||
|
info = f'User changed from {user.tag}, {user.name} to {tag}, {name}'
|
||||||
|
self.bot.database.change_user_infos(user, name, tag)
|
||||||
|
logger.info(info)
|
||||||
|
|
||||||
|
async def check_normal_users(self):
|
||||||
|
temp_error_users = {}
|
||||||
|
while True:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
for user in self.bot.normal_users:
|
||||||
|
try:
|
||||||
|
if user.joined + datetime.timedelta(days=1) < now and user.warning_time is None:
|
||||||
|
member = self.bot.guild.get_member(user.id)
|
||||||
|
await member.send('Hey, bitte reacte auf unsere Server-Regeln, da dir sonst wegen Verweigerung ein Kick aus unserem Server (**Kaizen Anime**) bevorstehen wird!\n\n'
|
||||||
|
'Mit freundlichen Grüßen,\n'
|
||||||
|
'das _Kaizen Server Team_')
|
||||||
|
self.bot.database.set_user_warning(user, now)
|
||||||
|
logger.info(f'Warned user {user.tag}, because he didn\'t accepted the rules since he joined')
|
||||||
|
elif user.warning_time and user.warning_time + datetime.timedelta(days=1, hours=12) < now:
|
||||||
|
await self.bot.guild.get_member(user.id).kick(reason='Wegen nicht reacten auf unsere Kaizen Anime Server-Regeln trotz Verwarnung.\n\n'
|
||||||
|
'MfG das Kaizen Server Team')
|
||||||
|
# the user won't removed from the database here, because `self.on_member_remove(...)` is called
|
||||||
|
logger.info(f'Kicked user {user.tag} because he didn\'t accepted the rules')
|
||||||
|
except Exception as error:
|
||||||
|
if isinstance(error, errors.Forbidden):
|
||||||
|
if user not in self.bot.error_users:
|
||||||
|
if user in temp_error_users:
|
||||||
|
temp_error_users[user] += 1
|
||||||
|
else:
|
||||||
|
temp_error_users[user] = 1
|
||||||
|
|
||||||
|
if temp_error_users[user] >= 5:
|
||||||
|
logger.warning(f'User {user.tag} caused to many error. I hide error log messages for him from now on')
|
||||||
|
self.bot.error_users.append(user)
|
||||||
|
del temp_error_users[user]
|
||||||
|
continue
|
||||||
|
logger.warning(f'Unexpected exception was thrown while checking all normal users: {"".join(traceback.format_exception(etype=type(error), value=error, tb=error.__traceback__))}')
|
||||||
|
await asyncio.sleep(60)
|
||||||
|
|
||||||
|
async def check_and_remove_normal_user(self, user: User):
|
||||||
|
if user in self.bot.normal_users:
|
||||||
|
self.bot.normal_users.remove(user)
|
||||||
|
if user in self.bot.error_users:
|
||||||
|
self.bot.error_users.remove(user)
|
||||||
|
|
||||||
|
if user.join_message:
|
||||||
|
try:
|
||||||
|
message = await self.bot.welcome_channel.fetch_message(user.join_message)
|
||||||
|
await message.delete()
|
||||||
|
except discord.errors.NotFound:
|
||||||
|
logger.info(f'Failed to find message {user.join_message}')
|
||||||
|
user.join_message = None
|
||||||
|
|
||||||
|
def reset_user(self, user: User):
|
||||||
|
self.bot.database.reset_user(user)
|
||||||
|
self.bot.normal_users.append(user)
|
||||||
|
if user in self.bot.error_users:
|
||||||
|
self.bot.error_users.remove(user)
|
||||||
|
logger.info(f'Reset kaizenianer {user.tag}')
|
||||||
|
|
||||||
|
async def user_accepted_rules(self, user: User):
|
||||||
|
await self.check_and_remove_normal_user(user)
|
||||||
|
self.bot.database.add_user_accepted_rules(user, datetime.datetime.now())
|
||||||
|
if user in self.bot.error_users:
|
||||||
|
self.bot.error_users.remove(user)
|
||||||
|
logger.info(f'Added kaizenianer {user.tag}')
|
||||||
|
|
||||||
|
def twitter(self, id: int):
|
||||||
|
async def on_status(status: tweepy.Status):
|
||||||
|
# checks if the status author is the given id, if the tweet is not a retweet and if the tweet is not a reply
|
||||||
|
if id == status.author.id and not hasattr(status, 'retweeted_status') and status.in_reply_to_status_id is None:
|
||||||
|
msg = f'Hey <@&{self.bot.kaizenianer.id}>, **Kaizen!** hat einen neuen Tweet gepostet!\nhttps://twitter.com/twitter/statuses/{status.id}'
|
||||||
|
await self.bot.twitter_channel.send(msg)
|
||||||
|
return on_status
|
||||||
|
|
||||||
|
async def twitch_stream_status(self):
|
||||||
|
if data := self.twitch.get_streams(user_login=['kaizenanime'])['data']:
|
||||||
|
if not self.streams:
|
||||||
|
await self.bot.change_presence(activity=discord.Streaming(name=data[0]['title'], url='https://www.twitch.tv/kaizenanime'))
|
||||||
|
self.streams = True
|
||||||
|
logger.info('Kaizen started streaming')
|
||||||
|
elif self.title != data[0]['title']:
|
||||||
|
self.title = data[0]['title']
|
||||||
|
await self.bot.change_presence(activity=discord.Streaming(name=data[0]['title'], url='https://www.twitch.tv/kaizenanime'))
|
||||||
|
else:
|
||||||
|
if self.streams:
|
||||||
|
await self.bot.change_presence(activity=None)
|
||||||
|
self.streams = False
|
||||||
|
self.title = ''
|
||||||
|
logger.info('Kaizen stopped streaming')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
bot = Bot()
|
||||||
|
if sys.argv[1] == 'test':
|
||||||
|
bot.run('...')
|
||||||
|
elif sys.argv[1] == 'run':
|
||||||
|
bot.run('...')
|
Reference in New Issue
Block a user