This repository has been archived on 2023-07-12. You can view files and clone it, but cannot push or open issues or pull requests.
kaizen-bot-old/main.py

445 lines
23 KiB
Python
Raw Permalink Normal View History

2022-04-20 23:41:25 +02:00
#!/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('...')