Files
unbelievaselfbot/bot/client.py

112 lines
4.2 KiB
Python

"""
client.py
Stores the primary client class, which accesses the Discord API and processes messages automatically.
"""
import ctypes
import logging
from typing import Optional
import discord
from discord.ext.tasks import loop
from bot import constants, parsers, timings, helpers
from bot.blackjack import Card, Blackjack
from bot.constants import PlayOptions
logger = logging.getLogger(__file__)
logger.setLevel(constants.LOGGING_LEVEL)
class UnbelievaClient(discord.Client):
def __init__(self, bot_id: int, channel_id: int, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.last_message = -1
# References
self.bot_id, self.channel_id = bot_id, channel_id
self.channel: Optional[discord.TextChannel] = None
self.tasks = {
'$work': timings.Cooldown(5 * 60 + 2),
'$slut': timings.Cooldown(13 * 60 + 2),
'$crime': timings.Cooldown(20 * 60 + 2),
'$dep all': timings.Cooldown(30 * 60)
}
self.command_cooldown = timings.Cooldown(6.5)
self.money = 0
self.last_deposit = -1
self.last_user_deposit = -1
self.current_blackjack: Optional[discord.Message] = None
async def on_ready(self):
await self.wait_until_ready()
self.channel: discord.TextChannel = self.get_channel(self.channel_id)
self.check_task_available.start()
ctypes.windll.kernel32.SetConsoleTitleW(f"#{self.channel.name}/{self.channel.guild.name}")
logger.info(f'Connected to #{self.channel.name} in {self.channel.guild.name}')
async def on_message(self, message: discord.Message):
# Ignore messages in other channels or sent by myself
if message.channel != self.channel or message.author == self.user:
return
if message.author.id == self.bot_id and len(message.embeds) > 0:
embed = message.embeds[0]
is_self = helpers.embed_author_matches(embed, self.user)
if is_self:
if parsers.TaskCooldownMessage.check_valid(message):
tcm = parsers.TaskCooldownMessage(message)
logger.debug(f'"{tcm.duration_unparsed}" => {tcm.duration}s')
logger.debug(f'Changed {tcm.task_type} to wait {tcm.duration + 2}s instead.')
self.tasks[tcm.task_type].change_expiration(tcm.available_at)
# Handling earnings
if parsers.TaskResponse.check_valid(message):
tr = parsers.TaskResponse(message)
self.money += tr.change
logger.log(logging.INFO if is_self else logging.DEBUG, tr.log_message(embed.author.name))
# Handling for blackjack
if embed.description.startswith('Type `hit` to draw another card'):
options = self.parse_options(embed.description)
my_cards = Card.parse_cards(embed.fields[0])[1]
dealer_card = Card.parse_cards(embed.fields[1])[1][0]
choice = Blackjack.choose(options, my_cards, dealer_card)
logger.info(f'Predicted best choice for Blackjack: {choice}')
def parse_options(self, options_str: str) -> PlayOptions:
"""
Return a tuple of booleans describing what the player can do.
Tuple Options: [hit, stand, double_down, split]
"""
options = [f'`{sub}`' in options_str for sub in ['hit', 'stand', 'double down', 'split']]
# noinspection PyProtectedMember
return PlayOptions._make(options)
@loop(seconds=1)
async def check_task_available(self):
"""Loop to run tasks as soon as they are available."""
await self.wait_until_ready()
for task, task_cooldown in self.tasks.items():
# Task is ready to be ran again
if task_cooldown.ready:
# Ensure the cooldown between commands has ran.
await self.command_cooldown.sleep()
# Ready to execute the task.
logger.debug(f'Executing {task} task.')
await self.channel.send(task)
# Activate the cooldowns
task_cooldown.hit()
self.command_cooldown.hit()