mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-14 21:38:38 +00:00
211c45891e
fix fax fx Fin part fx 121 Temp fix as aa fx f5 fx4 fix fx1 Work on deployment
1658 lines
67 KiB
Python
1658 lines
67 KiB
Python
"""
|
|
Module of a python bot named Conjurer - used to work on BDSM discord servers.
|
|
"""
|
|
# This Python file uses the following encoding: utf-8
|
|
# trunk-ignore-all(flake8/E501)
|
|
# trunk-ignore-all(bandit/B311)
|
|
# trunk-ignore-all(mypy/index)
|
|
# pylint: disable=line-too-long
|
|
# pylint: disable=too-many-lines
|
|
import asyncio
|
|
import io
|
|
import json
|
|
import logging
|
|
import netrc
|
|
|
|
# *=========================================== Standard Library Imports
|
|
import os
|
|
import random
|
|
import re
|
|
import shutil
|
|
import sys
|
|
from datetime import datetime
|
|
from logging import handlers
|
|
from pathlib import Path, PurePath
|
|
from platform import uname
|
|
from sys import platform
|
|
from typing import List, Optional, TypedDict
|
|
|
|
# *==============Imported libraries
|
|
import discord
|
|
import eyed3
|
|
import numpy as np
|
|
import openai
|
|
import pdf2image
|
|
import PyPDF2
|
|
import requests
|
|
import spotipy
|
|
from discord.ext import commands
|
|
from spotipy.oauth2 import SpotifyClientCredentials
|
|
|
|
from spotify_dl import youtube as youtube_download
|
|
|
|
from spotify_dl import spotify
|
|
|
|
Music_Config = TypedDict(
|
|
"Music_Config",
|
|
{
|
|
"playing": bool,
|
|
"ctx": Optional[str],
|
|
"queue": List[str],
|
|
"requestor": List[str],
|
|
},
|
|
)
|
|
|
|
# *=========================================== Predefines
|
|
formatter = logging.Formatter(
|
|
"%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
MASTER_TIMEOUT = datetime.now()
|
|
INITIAL_TIME_WAIT = 5
|
|
MUZYKA: Music_Config = {"playing": False,
|
|
"ctx": None, "queue": [], "requestor": []}
|
|
|
|
LOGFILE = ""
|
|
NETRC_FILE = ""
|
|
MUSIC_FOLDER = ""
|
|
MEMORY_FIVE_SIARA = ""
|
|
MEMORY_FIVE_MUZYKA = ""
|
|
SETTINGS_FILE = ""
|
|
ENCODING = ""
|
|
GRAPHICS_PATH = ""
|
|
MUZYKA_MOJEGO_LUDU_HISTORIA = 1500
|
|
MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE = 30
|
|
MUZYKA_MOJEGO_LUDU_PLAJLISTA = 30
|
|
|
|
|
|
# *=========================================== Platform Specific Predefines
|
|
|
|
if platform in ("linux", "linux2"):
|
|
SEPARATOR_FILE_PATH = "/"
|
|
if "microsoft-standard" in uname().release:
|
|
LOGFILE = "/home/mtuszowski/conjurer/discord.log"
|
|
MEMORY_FIVE_SIARA = "/home/mtuszowski/conjurer/pamiec.json"
|
|
MEMORY_FIVE_MUZYKA = "/home/mtuszowski/conjurer/pamiec_muzyki.json"
|
|
MUSIC_FOLDER = "/mnt/g/Muzyka/"
|
|
SETTINGS_FILE = "/home/mtuszowski/conjurer/settings.json"
|
|
NETRC_FILE = "/home/mtuszowski/.netrc"
|
|
LOGSTORE = "/home/mtuszowski/conjurer/logs/"
|
|
ACCIDENT_LOG = "/home/mtuszowski/conjurer/accident_log.json"
|
|
ENCODING = "utf-8"
|
|
GRAPHICS_PATH = "/home/mtuszowski/conjurer/Conjurer_graphics/"
|
|
DIR_PATH_SADOX = "/mnt/c/Users/mtusz/OneDrive/Dokumenty/Fansadox"
|
|
|
|
else:
|
|
LOGFILE = "/home/pi/Conjurer/discord.log"
|
|
MEMORY_FIVE_SIARA = "/home/pi/Conjurer/pamiec.json"
|
|
MEMORY_FIVE_MUZYKA = "/home/pi/Conjurer/pamiec_muzyki.json"
|
|
MUSIC_FOLDER = "/home/pi/RetroPie/mp3/"
|
|
SETTINGS_FILE = "/home/pi/Conjurer/settings.json"
|
|
NETRC_FILE = "/home/pi/.netrc"
|
|
LOGSTORE = "/home/pi/RetroPie/logs/"
|
|
ACCIDENT_LOG = "/home/pi/Conjurer/accident_log.json"
|
|
ENCODING = "utf-8"
|
|
GRAPHICS_PATH = "/home/pi/RetroPie/Conjurer_graphics/"
|
|
DIR_PATH_SADOX = "/home/pi/RetroPie/Fansadox/"
|
|
|
|
|
|
elif platform == "win32":
|
|
LOGFILE = "discord.log"
|
|
MEMORY_FIVE_SIARA = "pamiec.json"
|
|
MEMORY_FIVE_MUZYKA = "pamiec_muzyki.json"
|
|
MUSIC_FOLDER = "G:\\Muzyka\\"
|
|
SETTINGS_FILE = "settings.json"
|
|
NETRC_FILE = "C:\\Users\\mtusz\\.netrc"
|
|
LOGSTORE = "C:\\Users\\mtusz\\OneDrive\\Pulpit\\Conjurer\\logs\\"
|
|
ACCIDENT_LOG = "accident_log.json"
|
|
ENCODING = "utf-8"
|
|
DIR_PATH_SADOX = "C:\\Users\\mtusz\\OneDrive\\Dokumenty\\Fansadox\\"
|
|
SEPARATOR_FILE_PATH = "\\"
|
|
|
|
|
|
# *=========================================== Keys
|
|
netrc_mod = netrc.netrc(NETRC_FILE)
|
|
REMOTE_HOST_NAME = "discord"
|
|
authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME)
|
|
TOKEN = authTokens[2]
|
|
|
|
REMOTE_HOST_NAME = "openai"
|
|
authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME)
|
|
openai.api_key = authTokens[2]
|
|
|
|
REMOTE_HOST_NAME = "spotipy"
|
|
authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME)
|
|
spotify_ctrl = spotipy.Spotify(
|
|
client_credentials_manager=SpotifyClientCredentials(
|
|
client_id=authTokens[0],
|
|
client_secret=authTokens[2],
|
|
)
|
|
)
|
|
|
|
# *=========================================== Initializations
|
|
intents = discord.Intents.default()
|
|
intents.message_content = True
|
|
intents.typing = True
|
|
intents.presences = True
|
|
intents.members = True
|
|
intents.messages = True
|
|
intents.voice_states = True
|
|
logger = logging.getLogger("discord")
|
|
logger.setLevel(logging.DEBUG)
|
|
handler = handlers.RotatingFileHandler(
|
|
filename=LOGFILE,
|
|
encoding=ENCODING,
|
|
mode="a",
|
|
maxBytes=6 * 1024 * 1024,
|
|
backupCount=6,
|
|
)
|
|
handler.setFormatter(formatter)
|
|
logger.addHandler(handler)
|
|
random.seed()
|
|
logger.debug(platform)
|
|
logger.info("Playlist generation")
|
|
|
|
# *=========================================== Load Data files
|
|
music_file_list = []
|
|
for mp3_item in Path.glob(Path(MUSIC_FOLDER), "**/*.mp3"):
|
|
temp_music_file = mp3_item.as_posix()
|
|
if platform == "win32":
|
|
temp_music_file = temp_music_file.replace("/", "\\")
|
|
music_file_list.append(temp_music_file)
|
|
with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as temp_memory_file:
|
|
# First we load existing data into a dict.
|
|
MESSAGE_TABLE = json.load(temp_memory_file)
|
|
with open(MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING) as temp_music_memory_file:
|
|
# First we load existing data into a dict.
|
|
message_table_muzyka = json.load(temp_music_memory_file)
|
|
with open(SETTINGS_FILE, "r", encoding=ENCODING) as f_settings_file:
|
|
data = json.load(f_settings_file)
|
|
word_reactions = data["word_reactions"]
|
|
historia_fabryczki = data["fabryczka"]
|
|
for key in word_reactions:
|
|
word_reactions[key][2] = datetime.now()
|
|
|
|
# *=========================================== Create Client
|
|
client = commands.Bot(intents=intents, command_prefix="$")
|
|
|
|
# *=========================================== Define Events
|
|
|
|
|
|
@client.event
|
|
async def on_ready():
|
|
"""Metoda wywoływana przy połączeniu do serwera."""
|
|
logger.debug("%s has connected to Discord!", client.user)
|
|
await client.change_presence(activity=discord.Game(name="Axe Throwing Darts"))
|
|
await client.tree.sync()
|
|
await check()
|
|
|
|
|
|
@client.event
|
|
# trunk-ignore(pylint/R0915)
|
|
# trunk-ignore(pylint/R0914)
|
|
# trunk-ignore(pylint/R0912)
|
|
async def on_message(message):
|
|
|
|
"""
|
|
Handle incoming messages in a Discord server, perform various
|
|
checks and actions based on the content and context of the message, and respond accordingly.
|
|
|
|
:param message: The message object that is received when a user sends a message in a Discord server
|
|
or DM. The code is checking various conditions and performing actions based on the content of the
|
|
message and the context in which it was sent. It also includes TODOs for future improvements
|
|
:return: The function `on_message` is being returned.
|
|
"""
|
|
vykidailo = False
|
|
channel = None
|
|
if message.author == client.user:
|
|
return
|
|
if isinstance(message.author, discord.Member):
|
|
for role in message.author.roles:
|
|
if role.name == "Vykidailo":
|
|
vykidailo = True
|
|
|
|
if ("Conjurer Śpij Słodko Aniołku" in message.content) and vykidailo:
|
|
sys.exit()
|
|
# kanal bez bota
|
|
if message.channel.id == 1095985579147141202:
|
|
return
|
|
# wentylacja
|
|
if message.channel.id == 1083804024173764739:
|
|
return
|
|
# legendy
|
|
if message.channel.id == 1084448332841230388:
|
|
return
|
|
# interrogation booth
|
|
if message.channel.id == 1111625221171052595:
|
|
return
|
|
if isinstance(message.channel, discord.DMChannel):
|
|
channel = client.get_channel(1064888712565100614)
|
|
await channel.send("Słyszałem ja żem że: " + message.content)
|
|
else:
|
|
channel = message.channel
|
|
await client.process_commands(message)
|
|
|
|
# TODO: wpiac jego reakcje we framework chatu GPT
|
|
message.content = message.content.lower()
|
|
|
|
tdelta = datetime.now() - MASTER_TIMEOUT
|
|
tdelta = tdelta.total_seconds()
|
|
if "opowiedz o fabryczce" in message.content:
|
|
await message.reply(historia_fabryczki)
|
|
if "opowiedz mi o fabryczce" in message.content:
|
|
await message.reply(historia_fabryczki)
|
|
|
|
if tdelta > INITIAL_TIME_WAIT:
|
|
for word in word_reactions:
|
|
if re.search(r"\b" + word + r"\b", message.content):
|
|
tdelta = datetime.now() - word_reactions[word][2]
|
|
tdelta = tdelta.total_seconds()
|
|
security_clearance = word_reactions[word][4]
|
|
reaction = word_reactions[word][3]
|
|
if tdelta > word_reactions[word][1]:
|
|
# TODO: to zrobic reactiony
|
|
logger.info("Ping z procedury reakcji")
|
|
if reaction:
|
|
emoji = client.get_emoji(word_reactions[word][0])
|
|
await message.add_reaction(emoji)
|
|
elif security_clearance and vykidailo:
|
|
await message.reply(word_reactions[word][0])
|
|
elif not security_clearance:
|
|
await message.reply(word_reactions[word][0])
|
|
word_reactions[word][2] = datetime.now()
|
|
|
|
# TODO: drobne literówki, mentiony, spacja przed dwukropkiem. napraw.
|
|
|
|
if "conjurer:" in message.content:
|
|
async with channel.typing():
|
|
logger.debug("Procedura chatu")
|
|
|
|
message.content = message.content.replace("conjurer: ", "")
|
|
if message.author.nick:
|
|
username = message.author.nick
|
|
else:
|
|
username = message.author.name
|
|
vykidailo = False
|
|
bartender = False
|
|
if kondziu_mentioned:
|
|
prompt = message.clean_content
|
|
else:
|
|
prompt = message.content
|
|
for role in message.author.roles:
|
|
if role.name == "Vykidailo":
|
|
vykidailo = True
|
|
if role.name == "Bartender":
|
|
bartender = True
|
|
global MESSAGE_TABLE # pylint: disable=global-statement
|
|
|
|
result, MESSAGE_TABLE = await handle_response(
|
|
prompt, vykidailo, bartender, MESSAGE_TABLE, username, False
|
|
)
|
|
await message.reply(result)
|
|
if "imaginuje sobie:" in message.content:
|
|
async with channel.typing():
|
|
logger.info("Poczatek procedury obrazkowej")
|
|
message.content = message.content.replace("imaginuje sobie: ", "")
|
|
logger.debug("Wywolanie obrazka: %s", message.content)
|
|
response = openai.Image.create(
|
|
prompt=message.content, n=1, size="1024x1024"
|
|
)
|
|
image_url = response["data"][0]["url"]
|
|
logger.debug("Wynikowy obrazek pod url: %s", image_url)
|
|
response = requests.get(image_url, timeout=360)
|
|
|
|
temp_file_name = message.content + ".png"
|
|
temp_file_name = GRAPHICS_PATH + message.content + ".png"
|
|
|
|
with open(temp_file_name, "wb") as dalle_file:
|
|
dalle_file.write(response.content)
|
|
logger.info("Koniec procedury obrazkowej.")
|
|
fnord = discord.File(
|
|
temp_file_name, spoiler=False, description=message.content
|
|
)
|
|
await message.reply(file=fnord)
|
|
|
|
|
|
# *=========================================== Define Functions
|
|
|
|
|
|
async def connect(ctx, arg=None):
|
|
"""
|
|
Connect the bot to a voice channel if it is not already connected.
|
|
|
|
:param ctx: ctx is short for context and refers to the context in which the command was invoked. It
|
|
contains information about the message, the channel, the server, and the user who invoked the
|
|
command
|
|
:param arg: The `arg` parameter is an optional argument that can be passed to the `connect`
|
|
function. It is not used in the code snippet provided, but it could potentially be used to specify a
|
|
specific voice channel to connect to
|
|
:return: If the voice client is already connected, the function will return without doing anything.
|
|
If the function successfully connects to the voice channel, it will return a voice channel
|
|
connection object. If it is not possible to connect to the voice channel, the function will log an
|
|
error message and return nothing.
|
|
"""
|
|
if ctx and arg:
|
|
logger.info("Ctx and arg defined for connect")
|
|
voice_channel = client.get_channel(1060349757349974066)
|
|
|
|
if client.voice_clients:
|
|
voice_client = client.voice_clients[0]
|
|
if voice_client.is_connected():
|
|
return
|
|
voice_channel_connection = await voice_channel.connect()
|
|
if not voice_channel_connection:
|
|
logger.error("Not possible to connect to voice")
|
|
logger.info("Connecting to voice")
|
|
|
|
|
|
async def disconnect(ctx):
|
|
"""
|
|
Asynchronous Python function that disconnects the voice client if it is connected and
|
|
logs a message if the context is defined.
|
|
|
|
:param ctx: ctx is short for context and refers to the context in which a command is being executed.
|
|
It contains information about the message, the user who sent the message, the channel the message
|
|
was sent in, and more. In this case, the `disconnect` function is likely being called as a command
|
|
in
|
|
"""
|
|
if ctx:
|
|
logger.info("Ctx defined for disconnect")
|
|
if client.voice_clients:
|
|
voice_client = client.voice_clients[0]
|
|
if voice_client.is_connected():
|
|
await voice_client.disconnect()
|
|
|
|
|
|
# trunk-ignore(pylint/R0915)
|
|
# trunk-ignore(pylint/R0912)
|
|
async def play(ctx, zamawial=None, arg=None):
|
|
"""
|
|
Play a music file, retrieve metadata about the file, and send a
|
|
message to a Discord channel with information about the song being played.
|
|
|
|
:param ctx: The "ctx" parameter is a context object that contains information about the current
|
|
Discord command invocation, such as the message, the channel, and the user who invoked the command.
|
|
It is passed to the function automatically by the Discord.py library
|
|
:param zamawial: The parameter `zamawial` is a variable that stores the user who requested the song
|
|
to be played. It is an optional parameter and can be None if no user requested the song
|
|
:param arg: The `arg` parameter is an optional argument that can be passed to the `play` function.
|
|
It is not used in the code provided, so its purpose is unclear without additional context
|
|
"""
|
|
# TODO: Uzupełnianie metadanych. Jeśli ich nie ma sprawdzamy nazwę (w sposób inteligentny - czyli jeśli składa się z samych cyfr i/lub słowa track to sprawdzić album w sensie folder)
|
|
# TODO: Jeśli w metadanych jest tylko "audiotrack" i "album" też po
|
|
# folderze. Jeśli są niekompletne uzupełnić dalej.
|
|
logger.info("Play procedure")
|
|
if arg:
|
|
logger.info("Arg defined for play")
|
|
|
|
file_to_play = None
|
|
voice_client = client.voice_clients[0]
|
|
if not client.voice_clients:
|
|
voice_client = client.voice_clients[0]
|
|
await connect(ctx)
|
|
if MUZYKA["queue"]:
|
|
file_to_play = MUZYKA["queue"].pop()
|
|
zamawial = MUZYKA["requestor"].pop()
|
|
else:
|
|
index = random.randint(0, len(music_file_list) - 1)
|
|
file_to_play = music_file_list[index]
|
|
|
|
logger.debug(file_to_play)
|
|
metadata = eyed3.load(file_to_play)
|
|
query = None
|
|
temp = file_to_play.split(SEPARATOR_FILE_PATH)[-1]
|
|
kawalek = f"Zagram teraz kawalek {temp} z prywatnej kolekcji Hammera."
|
|
if metadata:
|
|
if metadata.tag:
|
|
if metadata.tag.title:
|
|
query = f"Opowiedz mi o kawałku {metadata.tag.title}"
|
|
kawalek = f"Zagram teraz kawalek {metadata.tag.title}"
|
|
if metadata.tag.artist:
|
|
query += f" wykonawcy {metadata.tag.artist}"
|
|
kawalek += f" wykonawcy {metadata.tag.artist}"
|
|
if metadata.tag.album:
|
|
query += f" z albumu {metadata.tag.album}"
|
|
kawalek += f" z albumu {metadata.tag.album}"
|
|
kawalek += " z prywatnej kolekcji Hammera."
|
|
if zamawial:
|
|
kawalek += f"Na specjalne życzenie <@{zamawial.id}>."
|
|
await ctx.send(kawalek)
|
|
|
|
voice_client.play(
|
|
discord.PCMVolumeTransformer(
|
|
discord.FFmpegPCMAudio(source=file_to_play), volume=0.3
|
|
)
|
|
) # NIE DOTYKAC POKRĘTŁA GŁOŚNOŚCI!!!!
|
|
if query:
|
|
logger.debug("Zapytanie do openai")
|
|
logger.debug(query)
|
|
vykidailo = False
|
|
bartender = False
|
|
for role in ctx.message.author.roles:
|
|
if role.name == "Vykidailo":
|
|
vykidailo = True
|
|
if role.name == "Bartender":
|
|
bartender = True
|
|
if ctx.message.author.nick:
|
|
username = ctx.message.author.nick
|
|
else:
|
|
username = ctx.message.author.name
|
|
global MESSAGE_TABLE # pylint: disable=global-statement
|
|
result, MESSAGE_TABLE = await handle_response(
|
|
query, vykidailo, bartender, message_table_muzyka, username, True
|
|
)
|
|
logger.debug("Obecna historia czatu: %s", message_table_muzyka)
|
|
await ctx.send(result)
|
|
|
|
|
|
async def archive_channel(channel_no):
|
|
channel = client.get_channel(channel_no)
|
|
messages = [message async for message in channel.history(limit=None)]
|
|
path_newfile = (
|
|
f"{LOGSTORE}channeldump_{channel_no}_{datetime.now()}.json"
|
|
)
|
|
new_data = []
|
|
for message in messages:
|
|
pass
|
|
with open(path_newfile, "x", encoding=ENCODING) as new_file:
|
|
new_file.seek(0)
|
|
json.dump(new_data, new_file, indent=4)
|
|
|
|
|
|
async def check():
|
|
"""Funkcja sprawdzająca czy grać następny kawałek."""
|
|
while True:
|
|
# TODO: Tu dołożyć skanowanie rozmiaru pliku logowania oraz plików pamięci i przenoszenie ich na większy dysk.
|
|
# trunk-ignore(codespell/misspelled)
|
|
# TODO: dlaczego sie tu wypierdala
|
|
if client.voice_clients:
|
|
voice_client = client.voice_clients[0]
|
|
if voice_client.is_connected():
|
|
while MUZYKA["playing"] and voice_client.is_connected():
|
|
if (
|
|
voice_client
|
|
and not voice_client.is_playing()
|
|
and not voice_client.is_paused()
|
|
):
|
|
await play(MUZYKA["ctx"], MUZYKA["requestor"])
|
|
await asyncio.sleep(0.1)
|
|
if os.path.getsize(LOGFILE) > 600000:
|
|
shutil.copyfile(LOGFILE, f"{LOGSTORE}discord{datetime.now()}")
|
|
logger.info("Log rollover")
|
|
handler.doRollover()
|
|
|
|
if os.path.getsize(MEMORY_FIVE_MUZYKA) > 300000:
|
|
channel = client.get_channel(1062047571557744721)
|
|
await channel.send(
|
|
"*Conjurer porządkuje bar, wypala szklanki do czysta miotaczem płomieni ze swojej zbroi i ogólnie wygląda na zajętego....*"
|
|
)
|
|
with channel.typing():
|
|
path_newfile = (
|
|
f"{LOGSTORE}{MEMORY_FIVE_MUZYKA[:-4]}{datetime.now()}.json"
|
|
)
|
|
with open(
|
|
MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING
|
|
) as file_music_memory:
|
|
with open(path_newfile, "x", encoding=ENCODING) as new_file:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(file_music_memory)
|
|
new_data = []
|
|
new_data.append(file_data[0])
|
|
new_data.extend(file_data[:-20])
|
|
file_music_memory.seek(0)
|
|
# convert back to json.
|
|
new_file.seek(0)
|
|
json.dump(new_data, file_music_memory, indent=4)
|
|
json.dump(file_data, new_file, indent=4)
|
|
await channel.send(
|
|
"*Przeciąga się za barem* No dobra - porobione to można dalej pi... *Zauważa spojrzenie Hammera* ...sać powieści o naszym wspaniałym barze SZEFIE!"
|
|
)
|
|
if os.path.getsize(MEMORY_FIVE_SIARA) > 300000:
|
|
path_newfile = "{LOGSTORE}{MEMORY_FIVE_SIARA[:-4]}{datetime.now()}.json"
|
|
await channel.send(
|
|
"*Conjurer porządkuje bar, ścina lekkim laserem pulsacyjnym powierzchnie baru o grubości kilku mikronów i ogólnie wygląda na zajętego....*"
|
|
)
|
|
with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as file:
|
|
with open(path_newfile, "x", encoding=ENCODING) as new_file:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(file)
|
|
new_data = []
|
|
new_data.append(file_data[0])
|
|
new_data.extend(file_data[:-20])
|
|
file.seek(0)
|
|
new_file.seek(0)
|
|
# convert back to json.
|
|
json.dump(new_data, file, indent=4)
|
|
json.dump(file_data, new_file, indent=4)
|
|
await channel.send(
|
|
"*Przeciąga się za barem* No dobra - porobione to można dalej pi... *Zauważa spojrzenie Hammera* eprzyć o głupotach z klientami... Szefie... *Bierze 'ukradkowy' łyk z piersiówki*"
|
|
)
|
|
await asyncio.sleep(60)
|
|
|
|
|
|
# trunk-ignore(pylint/R0913)
|
|
async def handle_response(prompt, vykidailo, bartender, history, username, music):
|
|
"""
|
|
Handle responses by appending them to a history, use OpenAI to
|
|
generate a response, and then append the generated response to the history.
|
|
|
|
:param prompt: The prompt for the OpenAI chatbot to generate a response to
|
|
:param vykidailo: It is a boolean variable that indicates whether the user invoking the function is
|
|
an administrator or not
|
|
:param bartender: The bartender parameter is a boolean value indicating whether the user making the
|
|
request is a bartender or not
|
|
:param history: A list containing the conversation history between the user and the assistant
|
|
:param username: The username of the user who initiated the conversation
|
|
:param music: The "music" parameter is a boolean value that indicates whether the conversation is
|
|
related to music or not. If it is True, the conversation history will be stored in a different file
|
|
and the response will be generated using a different model
|
|
:return: The function `handle_response` returns a tuple containing the `result` and `MESSAGE_TABLE`.
|
|
"""
|
|
# TODO: Wykrywać "Pracuję nad odpowiedzią i tego typu rzeczy"
|
|
logger.info("Wywolanie procedury openai z promptem: %s", prompt)
|
|
temp = {"role": "user", "content": username + ":" + prompt}
|
|
if vykidailo or bartender:
|
|
logger.info("Administrator coś chciał")
|
|
history.append(temp)
|
|
if music:
|
|
with open(MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING) as file_music_memory:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(file_music_memory)
|
|
# Join new_data with file_data inside emp_details
|
|
file_data.append(temp)
|
|
file_music_memory.seek(0)
|
|
# convert back to json.
|
|
json.dump(file_data, file_music_memory, indent=4)
|
|
else:
|
|
with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as file_memory:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(file_memory)
|
|
# Join new_data with file_data inside emp_details
|
|
file_data.append(temp)
|
|
file_memory.seek(0)
|
|
# convert back to json.
|
|
json.dump(file_data, file_memory, indent=4)
|
|
history = []
|
|
# * append bo pierwszy index
|
|
# * extend bo 20 ostatnich
|
|
if music:
|
|
history.append(message_table_muzyka[0])
|
|
history.extend(message_table_muzyka[-15:])
|
|
else:
|
|
history.append(MESSAGE_TABLE[0])
|
|
history.extend(MESSAGE_TABLE[-15:])
|
|
logger.info("Historia wysłana:")
|
|
logger.debug(history)
|
|
response = await openai.ChatCompletion.acreate(
|
|
model="gpt-4", messages=history
|
|
)
|
|
await asyncio.sleep(10)
|
|
result = ""
|
|
logger.debug("Odpowiedzi")
|
|
logger.debug(response.choices)
|
|
for choice in response.choices:
|
|
result += choice.message.content
|
|
logger.info("Sformatowane odpowiedzi")
|
|
logger.info(result)
|
|
temp = {"role": "assistant", "content": result}
|
|
history.append(temp)
|
|
if music:
|
|
with open(MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING) as file:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(file)
|
|
# Join new_data with file_data inside emp_details
|
|
file_data.append(temp)
|
|
# Sets file's current position at offset.
|
|
file.seek(0)
|
|
# convert back to json.
|
|
json.dump(file_data, file, indent=4)
|
|
else:
|
|
with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as file:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(file)
|
|
# Join new_data with file_data inside emp_details
|
|
file_data.append(temp)
|
|
# Sets file's current position at offset.
|
|
file.seek(0)
|
|
# convert back to json.
|
|
json.dump(file_data, file, indent=4)
|
|
return result, MESSAGE_TABLE
|
|
|
|
|
|
# trunk-ignore(pylint/R0914)
|
|
# trunk-ignore(pylint/R0915)
|
|
async def get_file(ctx, source, link):
|
|
"""
|
|
Take in three parameters: "ctx" (context),
|
|
"source" (a string representing the source of the file), and "link" (a string representing the link
|
|
to the file). Execute it in asynchronouse way. And get file from spotify or youtube.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which a command is being executed, including information such as the
|
|
message, the channel, the server, and the user who invoked the command
|
|
:param source: The source parameter is likely a string that represents the source of the file that
|
|
the user wants to retrieve. This could be a website, a cloud storage service, or any other location
|
|
where the file is stored
|
|
:param link: The "link" parameter is likely a string that represents a URL or file path to the
|
|
location of the file that the function is trying to retrieve
|
|
"""
|
|
item_id = None
|
|
item_type = None
|
|
file_list = {}
|
|
if source == "Spotify":
|
|
dir_path = None
|
|
item_type, item_id = spotify.parse_spotify_url(link)
|
|
if item_id:
|
|
file_list = spotify.fetch_tracks(spotify_ctrl, item_type, link)
|
|
directory_name = spotify.get_item_name(
|
|
spotify_ctrl, item_type, item_id)
|
|
url_data = {"urls": []}
|
|
url_dict = {}
|
|
url_dict["save_path"] = Path(
|
|
PurePath.joinpath(Path(MUSIC_FOLDER), Path(directory_name))
|
|
)
|
|
url_dict["save_path"].mkdir(parents=True, exist_ok=True)
|
|
url_dict["songs"] = file_list
|
|
url_data["urls"].append(url_dict.copy())
|
|
file_name_f = youtube_download.default_filename
|
|
youtube_download.download_songs(
|
|
songs=url_data,
|
|
output_dir=MUSIC_FOLDER,
|
|
format_str="bestaudio/best",
|
|
skip_mp3=False,
|
|
keep_playlist_order=False,
|
|
no_overwrites=True,
|
|
remove_trailing_tracks="no",
|
|
use_sponsorblock="yes",
|
|
file_name_f=file_name_f,
|
|
multi_core=0,
|
|
proxy="",
|
|
)
|
|
dir_path = (url_dict["save_path"].resolve()).as_posix()
|
|
if platform == "win32":
|
|
dir_path = dir_path.replace("/", "\\")
|
|
return dir_path, file_list
|
|
|
|
await ctx.send(
|
|
"No se jaja chyba robisz, Ty byś spotifaja nie poznawszy jakby z krzaków wyskoczył i w dupę kopnął. Plejlista, kawałek albo album prosze."
|
|
)
|
|
logger.error("Wrong link provided spotify: %s", link)
|
|
elif source == "Youtube":
|
|
link_ok = False
|
|
pat = r"^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$"
|
|
if re.match(pat, link) and re.match(".*youtube.*", link):
|
|
link_ok = True
|
|
|
|
if link_ok:
|
|
dir_path = ""
|
|
file_list = []
|
|
query = link
|
|
sponsorblock_postprocessor = [
|
|
{
|
|
"key": "SponsorBlock",
|
|
"categories": ["skip_non_music_sections"],
|
|
},
|
|
{
|
|
"key": "ModifyChapters",
|
|
"remove_sponsor_segments": ["music_offtopic"],
|
|
"force_keyframes": True,
|
|
},
|
|
]
|
|
#TODO: Make sponsorblock work
|
|
sponsorblock_postprocessor = []
|
|
if platform == "win32":
|
|
dir_path = "G:\\Muzyka\\Youtube"
|
|
else:
|
|
dir_path = "/home/pi/RetroPie/mp3/Youtube"
|
|
ydl_opts = {
|
|
"proxy": "",
|
|
"default_search": "ytsearch",
|
|
"format": "bestaudio/best",
|
|
"postprocessors": sponsorblock_postprocessor,
|
|
"noplaylist": True,
|
|
"no_color": False,
|
|
"paths": {"home": dir_path},
|
|
}
|
|
mp3_postprocess_opts = {
|
|
"key": "FFmpegExtractAudio",
|
|
"preferredcodec": "mp3",
|
|
"preferredquality": "192",
|
|
}
|
|
ydl_opts["postprocessors"].append(mp3_postprocess_opts.copy())
|
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
try:
|
|
ydl.download([query])
|
|
except Exception as exce: # pylint: disable=broad-exception-caught
|
|
logger.error(exce)
|
|
logger.info(
|
|
"Failed to download %s, make sure yt_dlp is up to date", link
|
|
)
|
|
extract = re.search("v=(...........)[&,$]*", link)
|
|
ident = extract.group(1)
|
|
for item in Path.glob(Path(dir_path), "**/*.mp3"):
|
|
file = item.as_posix()
|
|
if platform == "win32":
|
|
file = file.replace("/", "\\")
|
|
if re.match(".*" + ident + ".*", file):
|
|
file_list.append(file)
|
|
return dir_path, file_list
|
|
await ctx.send(
|
|
"No se jaja chyba robisz, Ty byś jutuba nie poznawszy jakby z krzaków wyskoczył i w dupę kopnął. Jutuba to nie tuba którą sobie można gdzieś wsadzić."
|
|
)
|
|
logger.error("Wrong link provided youtube: %s", link)
|
|
return "", None
|
|
|
|
|
|
# define an asynchronous generator
|
|
async def async_iterator_generator(range_of_iterable):
|
|
"""
|
|
Generate asynchronouse iterator.
|
|
This is an incomplete function definition for an asynchronous iterator generator that takes a range
|
|
of iterable as input.
|
|
|
|
:param range_of_iterable: The parameter `range_of_iterable` is likely a range or iterable object
|
|
that the async iterator generator will iterate over asynchronously. It could be a list, tuple, set,
|
|
or any other iterable object. The generator will yield each item in the iterable object
|
|
asynchronously, allowing other code to run in between
|
|
"""
|
|
# normal loop
|
|
for i in range_of_iterable:
|
|
# pylint: disable=pointless-string-statement
|
|
# yield the result
|
|
yield i
|
|
"""
|
|
Alternatywna wersja przerobienia fora na asynchroniczny.
|
|
# traverse the iterable of awaitables
|
|
for item in coros:
|
|
# await and get the result from the awaitable
|
|
result = await item
|
|
# report the results
|
|
print(result)
|
|
"""
|
|
|
|
|
|
async def get_stats(ctx, history_limit):
|
|
"""
|
|
The `get_stats` function retrieves the message history of a specific channel and counts the
|
|
frequency of each word in the messages.
|
|
|
|
:param ctx: The `ctx` parameter is an object that represents the context of the command being
|
|
executed. It contains information such as the message, the author, the server, and other relevant
|
|
details
|
|
:param history_limit: The `history_limit` parameter is the maximum number of messages to retrieve
|
|
from the channel history. It determines how far back in time the statistics will be calculated
|
|
:return: The function `get_stats` returns a dictionary `stats` that contains the frequency count of
|
|
words found in the messages from the specified channel.
|
|
"""
|
|
channel_id = 1062047367337095268
|
|
async with ctx.typing():
|
|
stats = {}
|
|
channel = client.get_channel(channel_id)
|
|
messages = [message async for message in channel.history(limit=history_limit)]
|
|
# traverse the iterable of awaitables
|
|
for message in messages:
|
|
# await and get the result from the awaitable
|
|
result = message.content
|
|
words = result.split()
|
|
for word in words:
|
|
if word in stats:
|
|
stats[word] += 1
|
|
else:
|
|
stats[word] = 1
|
|
for name, how_many in stats.items():
|
|
yield name, how_many
|
|
|
|
|
|
# trunk-ignore(pylint/R0914)
|
|
# trunk-ignore(pylint/R0915)
|
|
# trunk-ignore(pylint/R0912)
|
|
async def wyszukaj(ctx, how_many=0, slowa_kluczowe=None):
|
|
"""
|
|
Take in a context object and an optional integer parameter "how_many" and perform search
|
|
operation on music library.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command
|
|
:param how_many: how_many is a parameter that specifies the number of search results to be returned.
|
|
It is an optional parameter with a default value of 0, which means that if no value is provided for
|
|
how_many, the function will return all search results, defaults to 0 (optional)
|
|
"""
|
|
# TODO: Potem dorobić wyszukiwania po kawałkach słów
|
|
# i sieć neuronową z możliwością wyrażenia opinii o dopasowaniu.
|
|
if slowa_kluczowe:
|
|
word_list = slowa_kluczowe
|
|
else:
|
|
word_list = ctx.message.content.split()
|
|
logger.info("Wyszukuje")
|
|
search_weight = []
|
|
for _ in range(len(music_file_list)):
|
|
search_weight.append((0, ""))
|
|
|
|
time_start = datetime.now()
|
|
if int(how_many) > 0:
|
|
skip_start = 2
|
|
else:
|
|
skip_start = 1
|
|
async for word in async_iterator_generator(word_list[skip_start:]):
|
|
token_weight = len(word)
|
|
logger.info("Słowo kluczowe: %s", word)
|
|
itr = 0
|
|
async for file in async_iterator_generator(music_file_list):
|
|
file = file.split(SEPARATOR_FILE_PATH)
|
|
|
|
char_remove = [
|
|
".",
|
|
"^",
|
|
"$",
|
|
"*",
|
|
"+",
|
|
"?",
|
|
"{",
|
|
"}",
|
|
"[",
|
|
"]",
|
|
"\\",
|
|
"/",
|
|
"|",
|
|
"(",
|
|
")",
|
|
"!",
|
|
",",
|
|
"-",
|
|
":",
|
|
"mp3",
|
|
]
|
|
all_words = []
|
|
async for f_iter in async_iterator_generator(file):
|
|
async for char in async_iterator_generator(char_remove):
|
|
f_iter = f_iter.replace(char, "")
|
|
tmp = f_iter.split()
|
|
all_words.extend(tmp)
|
|
pingu = 1
|
|
pattern_len = len(all_words)
|
|
if platform == "win32":
|
|
skip = 2
|
|
else:
|
|
skip = 4
|
|
matched_times = 1
|
|
async for itm in async_iterator_generator(all_words[skip:]):
|
|
pingu += 1
|
|
pattern = ".*" + word + ".*"
|
|
if re.match(pattern, itm, re.IGNORECASE):
|
|
temp_weight = (
|
|
search_weight[itr][0]
|
|
+ (token_weight + (pingu**1.5) /
|
|
pattern_len) / matched_times
|
|
)
|
|
search_weight[itr] = temp_weight, music_file_list[itr]
|
|
matched_times += 1
|
|
itr += 1
|
|
|
|
logger.info("Stworzylem tablice wag zajęło mi to %s",
|
|
datetime.now() - time_start)
|
|
time_start = datetime.now()
|
|
item_to_search = await max_weight(search_weight)
|
|
itr = 0
|
|
if item_to_search == 0:
|
|
return None
|
|
not_found = True
|
|
return_list = []
|
|
if int(how_many) <= 0:
|
|
logger.info("Jeden plik do zagrania")
|
|
while not_found:
|
|
if search_weight[itr][0] == item_to_search:
|
|
return_list.append(search_weight[itr])
|
|
return return_list
|
|
itr += 1
|
|
else:
|
|
logger.info("Wiele plików do zagrania")
|
|
search_weight.sort(key=lambda x: x[0], reverse=True)
|
|
return_list.extend(search_weight[: int(how_many)])
|
|
return return_list
|
|
|
|
|
|
async def max_weight(lista):
|
|
"""
|
|
Take a list of weights and return the maximum weight.
|
|
|
|
:param lista: It seems like the parameter `lista` is a list of items, possibly representing weights.
|
|
The function name `max_weight` suggests that the function is intended to find the maximum weight
|
|
from the list. However, without more context or information about the problem, it's difficult to say
|
|
for sure what the
|
|
"""
|
|
maximum_weight = 0
|
|
for iterator in lista:
|
|
if iterator[0] > maximum_weight:
|
|
maximum_weight = iterator[0]
|
|
return maximum_weight
|
|
|
|
|
|
# *=========================================== Define Commands
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="dej_co_ze_spotifaja",
|
|
description="Podaj link do spotify - sciagnie i doda muzyke",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def dej_co_ze_spotifaja(ctx):
|
|
"""
|
|
Get a playlist or a music number from Spotify.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. This parameter is required in all
|
|
Discord.py commands
|
|
"""
|
|
logger.info("Spotifaj")
|
|
await ctx.send("Dej mnie chwilkę")
|
|
async with ctx.typing():
|
|
# wyciagnij linka z kontekstu
|
|
content = ctx.message.content.split()
|
|
for item in content:
|
|
if re.match("http.*", item):
|
|
dir_path, files = await get_file(ctx, "Spotify", item)
|
|
if platform == "win32":
|
|
separator = "\\"
|
|
else:
|
|
separator = "/"
|
|
for file in files:
|
|
global MUZYKA # pylint: disable=global-variable-not-assigned
|
|
MUZYKA["queue"].insert(
|
|
0,
|
|
dir_path
|
|
+ separator
|
|
+ file["artist"]
|
|
+ " - "
|
|
+ file["name"]
|
|
+ ".mp3",
|
|
)
|
|
MUZYKA["requestor"].insert(0, ctx.author)
|
|
music_file_list.append(file)
|
|
await ctx.send(
|
|
f"Dodałem do listy {dir_path}{file['artist']} - + {file['name']}.mp3"
|
|
)
|
|
logger.info("Spotifaj udany")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="dej_co_z_jutuba",
|
|
description="Podaj link do youtube - sciagnie i doda muzyke",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def dej_co_z_jutuba(ctx):
|
|
"""
|
|
Download a music number from youtube.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. It allows the command to interact
|
|
with the Discord API and
|
|
"""
|
|
await ctx.send("Dej mnie chwilkę")
|
|
logger.info("Jutub")
|
|
async with ctx.typing():
|
|
# wyciagnij linka z kontekstu
|
|
content = ctx.message.content.split()
|
|
for item_yt in content:
|
|
if re.match("http.*", item_yt):
|
|
_, files = await get_file(ctx, "Youtube", item_yt)
|
|
if files:
|
|
for file_iter in files:
|
|
global MUZYKA # pylint: disable=global-variable-not-assigned
|
|
MUZYKA["queue"].insert(0, file_iter)
|
|
music_file_list.append(file_iter)
|
|
MUZYKA["requestor"].insert(0, ctx.author)
|
|
await ctx.send(f"Dodałem do listy {file_iter}")
|
|
logger.info("Jutub udany")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="update_banlist",
|
|
description="Jeśli nie wiesz jak użyć tej komendy to nawet nie próbuj",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
# trunk-ignore(pylint/R0914)
|
|
# trunk-ignore(pylint/R0912)
|
|
async def update_banlist(ctx):
|
|
"""
|
|
Update a banlist in a Discord guild from preprovided list in channel.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
contains information about the context in which the command was invoked, such as the message, the
|
|
channel, the server, and the user who invoked the command. This information can be used to perform
|
|
various actions, such
|
|
"""
|
|
async with ctx.channel.typing():
|
|
allowed = False
|
|
for role in ctx.author.roles:
|
|
if role.name == "Jarl":
|
|
allowed = True
|
|
|
|
if ctx.channel.id != 1102190666827702342:
|
|
# trunk-ignore(codespell/misspelled)
|
|
await ctx.send("Nie wydaje mnie sie")
|
|
elif not allowed:
|
|
await ctx.send("Idź bo Cię zdziele")
|
|
else:
|
|
tobancunter = 0
|
|
async with ctx.typing():
|
|
# 1. get list of users posted on channel for banning
|
|
channel = ctx.channel
|
|
messages = [message async for message in channel.history(limit=None)]
|
|
ids_to_ban = []
|
|
for message in messages:
|
|
lines = message.content.split("\n")
|
|
for line in lines:
|
|
match = re.search(
|
|
"\\d{18}",
|
|
line
|
|
# trunk-ignore(codespell/misspelled)
|
|
) # poprawilem backslash na podwojny bo linter sie czepial
|
|
if match:
|
|
ids_to_ban.append(match.group())
|
|
tobancunter += 1
|
|
# 2. get list of already banned users for all servers Conjurer
|
|
# has rights to
|
|
for guild in client.guilds:
|
|
bancunter = 0
|
|
# trunk-ignore(codespell/misspelled)
|
|
logger.info("Serwer: %s", guild)
|
|
current_banlist = []
|
|
permission = True
|
|
try:
|
|
async for entry in guild.bans(limit=None):
|
|
current_banlist.append(entry.user.id)
|
|
bancunter += 1
|
|
except BaseException: # pylint: disable=broad-exception-caught
|
|
permission = False
|
|
if permission:
|
|
# 3. compare lists ban only users on list 2 and not on
|
|
# list 1
|
|
cunter_counter = 0
|
|
bad_id_cunter_counter = 0
|
|
ban_list = np.setdiff1d(ids_to_ban, current_banlist)
|
|
for ident in ban_list:
|
|
try:
|
|
tmp_user = await client.fetch_user(int(ident))
|
|
await guild.ban(
|
|
tmp_user,
|
|
reason="Automatyczna lista banów",
|
|
delete_message_seconds=0,
|
|
)
|
|
cunter_counter += 1
|
|
except discord.NotFound:
|
|
logger.info(
|
|
"Znaleziono uzyszkodnika ktory juz nie ma konta"
|
|
)
|
|
bad_id_cunter_counter += 1
|
|
# 4. return message success/failure - how many banned
|
|
await ctx.send(
|
|
"Obecnie na serwerze {guild} jest {bancunter} zbanowanych użytkowników. Znaleziono na tym kanale {tobancunter} id użytkowników do zbanowania. Zbanowano {cunter_counter} użytkowników. Na liście jest {bad_id_cunter_counter} użytkowników którzy skasowali już konto"
|
|
)
|
|
else:
|
|
await ctx.send(
|
|
f"Nie mam na serwerze {guild} uprawnień do banowania. :()"
|
|
)
|
|
await ctx.send("Zrobione tak czy inaczej")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
nsfw=True,
|
|
name="get_image_sadox",
|
|
description="Wyświetla losową stronę z losowego komiksu FanSadox. Bardzo NSFW.",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def get_image_sadox(ctx):
|
|
"""
|
|
Take in a context parameter and retrieve an image related from fansadox collection.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. In this case, it is likely being used
|
|
to determine
|
|
"""
|
|
logger.info("Get sadox")
|
|
channel = ctx.message.channel
|
|
async with channel.typing():
|
|
# select random file
|
|
res = []
|
|
# Iterate directory
|
|
for path in os.listdir(DIR_PATH_SADOX):
|
|
# check if current path is a file
|
|
if os.path.isfile(os.path.join(DIR_PATH_SADOX, path)):
|
|
res.append(path)
|
|
filename = res[random.randrange(0, len(res) - 1)]
|
|
# select random page
|
|
file = open(DIR_PATH_SADOX + filename, "rb")
|
|
readpdf = PyPDF2.PdfReader(file)
|
|
totalpages = len(readpdf.pages)
|
|
page = random.randrange(1, totalpages)
|
|
# convert page to image
|
|
image = pdf2image.convert_from_path(
|
|
DIR_PATH_SADOX + filename, first_page=page, last_page=page
|
|
)
|
|
byte_io_stream = io.BytesIO()
|
|
image[0].save(byte_io_stream, "JPEG")
|
|
byte_io_stream.seek(0)
|
|
byte_io_stream.name = "image.jpg"
|
|
file = discord.File(byte_io_stream)
|
|
await ctx.send(file=file)
|
|
logger.info("Get sadox completed")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="graj_muzyko",
|
|
description="Włącza muzykę na kanale #nocna-zmiana.",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def graj_muzyko(ctx):
|
|
"""
|
|
Async function named "graj_muzyko" starts music playback on channel "Nocna Zmiana".
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. This parameter is required in all
|
|
Discord.py commands
|
|
"""
|
|
|
|
logger.info("Press play on tape")
|
|
async with ctx.typing():
|
|
global MUZYKA # pylint: disable=global-variable-not-assigned
|
|
MUZYKA["playing"] = True
|
|
MUZYKA["ctx"] = ctx
|
|
await connect(ctx=ctx)
|
|
await play(ctx=ctx)
|
|
logger.info("Press play on tape completed")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="cisza",
|
|
description="Wyłącza muzykę i czyści plejliste.",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def cisza(ctx):
|
|
"""
|
|
Stop playback of the music and clear the queue.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. This parameter is required in all
|
|
Discord.py commands
|
|
"""
|
|
logger.info("Stop")
|
|
global MUZYKA # pylint: disable=global-variable-not-assigned
|
|
MUZYKA["playing"] = False
|
|
MUZYKA["ctx"] = None
|
|
voice_client = client.voice_clients[0]
|
|
if voice_client.is_connected():
|
|
await disconnect(ctx=ctx)
|
|
while MUZYKA["queue"]:
|
|
MUZYKA["queue"].pop()
|
|
MUZYKA["requestor"].pop()
|
|
logger.info("Stop completed")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="dalej",
|
|
description="Przerzuca na następny kawałek",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def dalej(ctx):
|
|
"""
|
|
Play next track in queue.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which a command is being executed, including information such as the
|
|
message, the channel, the server, and the user who invoked the command. The context object is used
|
|
to access and manipulate this
|
|
"""
|
|
if ctx:
|
|
logger.info("Ctx defined for dalej")
|
|
logger.info("Next")
|
|
voice_client = client.voice_clients[0]
|
|
if voice_client.is_connected():
|
|
voice_client.stop()
|
|
await play(ctx=ctx)
|
|
logger.info("Next completed")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="daj_mi_chwile",
|
|
description="Pauzuje",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def daj_mi_chwile(ctx):
|
|
"""
|
|
Pause music playback.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. This parameter is required in most
|
|
Discord.py commands and is
|
|
"""
|
|
if ctx:
|
|
logger.info("Ctx defined for pause")
|
|
|
|
logger.info("Pause")
|
|
voice_client = client.voice_clients[0]
|
|
if voice_client.is_connected():
|
|
voice_client.pause()
|
|
logger.info("Pause completed")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="graj_dalej",
|
|
description="Odpauzowuje",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def graj_dalej(ctx):
|
|
"""
|
|
Unpause music and continue playback.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. This parameter is required in all
|
|
Discord.py commands
|
|
"""
|
|
if ctx:
|
|
logger.info("Ctx defined for graj_dalej")
|
|
logger.info("Unpause")
|
|
voice_client = client.voice_clients[0]
|
|
if voice_client.is_paused():
|
|
voice_client.resume()
|
|
logger.info("Unpause completed")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="zagraj_mi_kawalek",
|
|
description="Wyszukuje w bibliotece muzycznej Hammera kawałek który ma zostać zagrany",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def zagraj_mi_kawalek(ctx):
|
|
"""
|
|
Play a song or music piece in response to a command
|
|
triggered by a user in a Discord chat context.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
contains information about the context in which the command was invoked, such as the message, the
|
|
channel, the server, and the user who invoked the command. This information can be used to perform
|
|
various actions, such
|
|
"""
|
|
async with ctx.typing():
|
|
search_time_glob = datetime.now()
|
|
logger.info("Zaczynam szukać timestamp %s", datetime.now())
|
|
file = await wyszukaj(ctx=ctx)
|
|
logger.info(
|
|
"Koniec szukania(timestamp %s zajęło %s",
|
|
datetime.now(),
|
|
datetime.now() - search_time_glob,
|
|
)
|
|
|
|
if file:
|
|
for plik in file:
|
|
global MUZYKA # pylint: disable=global-variable-not-assigned
|
|
MUZYKA["queue"].insert(0, plik[1])
|
|
MUZYKA["requestor"].insert(0, ctx.author)
|
|
metadata = eyed3.load(plik[1])
|
|
reply = "Dodałem do playlisty {plik[1]} z prywatnej kolekcji Hammera."
|
|
if metadata.tag.title:
|
|
reply = f"Dodałem do playlisty kawalek {metadata.tag.title}"
|
|
if metadata.tag.artist:
|
|
reply += f" wykonawcy {metadata.tag.artist}"
|
|
if metadata.tag.album:
|
|
reply += f" z albumu {metadata.tag.album}"
|
|
reply += " z prywatnej kolekcji Hammera i na Twoją specjalną rpośbę."
|
|
await ctx.send(reply)
|
|
else:
|
|
await ctx.send(
|
|
"Obawiam się że nie mogę nic znaleźć u siebie. Spróbuj komendy '$dej_co_ze_spotifaja' jeśli masz link"
|
|
)
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="zrob_mi_plejliste",
|
|
description="Generuje playliste - param1 - dlugosc, potem slowa wyszukiwania",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def zrob_mi_plejliste(ctx):
|
|
"""
|
|
Generate a playlist in queue. First word in this command shall be int defining length of the playlist.
|
|
Rest of the line are search terms.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. This parameter is required in all
|
|
Discord.py commands
|
|
"""
|
|
reply = "Wygenerowana plejlista:\n"
|
|
async with ctx.typing():
|
|
logger.info("Zaczynam szukać timestamp %s", datetime.now())
|
|
search_time_glob = datetime.now()
|
|
|
|
dlugosc_playlisty = ctx.message.content.split()[1]
|
|
file = await wyszukaj(ctx=ctx, how_many=dlugosc_playlisty)
|
|
logger.info(
|
|
"Koniec szukania(timestamp %s zajęło %s",
|
|
datetime.now(),
|
|
datetime.now() - search_time_glob,
|
|
)
|
|
index = 1
|
|
if file:
|
|
for plik in file:
|
|
global MUZYKA # pylint: disable=global-variable-not-assigned
|
|
MUZYKA["queue"].insert(0, plik[1])
|
|
MUZYKA["requestor"].insert(0, ctx.author)
|
|
metadata = eyed3.load(plik[1])
|
|
if metadata and metadata.tag:
|
|
if metadata.tag.title:
|
|
reply += f"{index} {metadata.tag.title}"
|
|
else:
|
|
reply += f"{index}. {plik[1]} z prywatnej kolekcji Hammera."
|
|
|
|
if metadata.tag.artist:
|
|
reply += f" wykonawcy {metadata.tag.artist}"
|
|
if metadata.tag.album:
|
|
reply += f" z albumu {metadata.tag.album}"
|
|
if metadata.tag.title:
|
|
reply += " z prywatnej kolekcji Hammera i na Twoją specjalną rpośbę.\n"
|
|
else:
|
|
reply += f"{index}. {plik[1]} z prywatnej kolekcji Hammera."
|
|
|
|
index += 1
|
|
if len(reply) > 1800:
|
|
await ctx.send(reply)
|
|
reply = ""
|
|
else:
|
|
await ctx.send(
|
|
"Obawiam się że nie mogę nic znaleźć u siebie. Spróbuj komendy '$dej_co_ze_spotifaja' jeśli masz link"
|
|
)
|
|
await ctx.send(reply)
|
|
logger.info("Plejlista zrobiona")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="przytul", description="Przytul kogoś - daj mention po komendzie :)"
|
|
)
|
|
async def przytul(ctx, arg: Optional[discord.Member] = None):
|
|
"""
|
|
Generate a text about hugging mentioned user.
|
|
|
|
:param ctx: ctx stands for "context" and is a required parameter in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command
|
|
:param arg: arg is a parameter of the function "przytul" that expects a Discord member object. The
|
|
parameter is optional, meaning that if no member object is provided, it will default to None
|
|
:type arg: Optional[discord.Member]
|
|
"""
|
|
async with ctx.typing():
|
|
nieprzytulac = False
|
|
for mention in ctx.message.mentions:
|
|
for role in mention.roles:
|
|
if role.name == "NIEPRZYTULAĆ!":
|
|
nieprzytulac = True
|
|
|
|
if arg and nieprzytulac:
|
|
await ctx.send(
|
|
f"Żebym ja Ciebie nie przytulił {ctx.message.author.mention}"
|
|
)
|
|
elif arg:
|
|
await ctx.send(
|
|
# trunk-ignore(codespell/misspelled)
|
|
f"Już dobrze.... Już dobrze... Ojej.. Biedactwo... :( *W ułamku sekundy {arg.mention} znajduje sie w duszącym uścisku. Żebra trzeszczą - kilka pęka. Pacnięcia po plecach grożą odbiciem nerek, a głaskanie po głowie powoduje wstrząs mózgu*"
|
|
)
|
|
else:
|
|
await ctx.send(
|
|
"Kogo mam przytulić? *Wyłamuje kostki i przeciąga się - jego 200 kilowa sylwetka złożona z samych mięśni świadczy o tym że jest gotowy*"
|
|
)
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(name="fabryczka", description="Historia fabryczki")
|
|
async def fabryczka(ctx):
|
|
"""
|
|
Send a general description of "fabryczkagate" to channel.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. The context object provides a way to
|
|
interact with the Discord
|
|
"""
|
|
await ctx.send(historia_fabryczki)
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="chata_hammera",
|
|
description="Czas od ostatniego incydentu w AbsinthHammerTimeSpaceContinuum",
|
|
)
|
|
async def chata_hammera(ctx):
|
|
"""
|
|
Send measured time from last incident in Hammer Fortress to the chat.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
contains information about the context in which the command was invoked, such as the message, the
|
|
channel, the server, and the user who invoked the command. This information can be used to perform
|
|
various actions, such
|
|
"""
|
|
with open(ACCIDENT_LOG, "r+", encoding=ENCODING) as new_file:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(new_file)
|
|
opis = file_data[-1][0]
|
|
czas = datetime.strptime(file_data[-1][1], "%Y-%m-%d %H:%M:%S.%f")
|
|
await ctx.send(
|
|
f"Komenda mierząca incydenty w Eldritch AbsinthHammerTimeSpaceContinuum.\n Od ostatniego incydentu w chacie Hammera minęło {datetime.now() - czas}. Incydent to: {opis}"
|
|
)
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="historia_incydentow_u_hammera",
|
|
description="Wyswietla liste incydentów które miały miejsce w AbsinthHammerTimeSpaceContinuum.",
|
|
)
|
|
async def historia_incydentow_u_hammera(ctx):
|
|
"""
|
|
Send a list of incidents in Hammer Fortress to the chat.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
contains information about the context in which the command was invoked, such as the message, the
|
|
channel, the server, and the user who invoked the command. This information can be used to perform
|
|
various actions, such
|
|
"""
|
|
with open(ACCIDENT_LOG, "r+", encoding=ENCODING) as new_file_accidents:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(new_file_accidents)
|
|
return_data = "Komenda mierząca incydenty w Eldritch AbsinthHammerTimeSpaceContinuum.\n Historia zarejestrownych incydentów u Hammera:\n"
|
|
index = 1
|
|
for iter_data in file_data:
|
|
opis = iter_data[0]
|
|
czas = datetime.strptime(iter_data[1], "%Y-%m-%d %H:%M:%S.%f")
|
|
add_data = f"{index}. Opis: {opis} Data: {czas}"
|
|
return_data = return_data + add_data + "\n"
|
|
index += 1
|
|
await ctx.send(return_data)
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="reset_the_clock",
|
|
description="Resetowanie zegara - dostępne wyłącznie dla Hammera",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def reset_the_clock(ctx):
|
|
"""
|
|
Reset the clock on "accidents" in Hammer Fortress adding a new one.
|
|
|
|
:param ctx: ctx stands for "context" and is a parameter commonly used in Discord.py commands. It
|
|
represents the context in which the command was invoked, including information such as the message,
|
|
the channel, the server, and the user who invoked the command. This parameter is required in all
|
|
Discord.py commands
|
|
"""
|
|
hammer = False
|
|
for role in ctx.message.author.roles:
|
|
if role.name == "Bartender":
|
|
hammer = True
|
|
if hammer:
|
|
with open(ACCIDENT_LOG, "r+", encoding=ENCODING) as new_file_accidents:
|
|
# First we load existing data into a dict.
|
|
file_data = json.load(new_file_accidents)
|
|
czas = datetime.strptime(file_data[-1][1], "%Y-%m-%d %H:%M:%S.%f")
|
|
opis = ctx.message.content[16:]
|
|
accident = accident = [opis, f"{datetime.now()}"]
|
|
file_data.append(accident)
|
|
new_file_accidents.seek(0)
|
|
# convert back to json.
|
|
json.dump(file_data, new_file_accidents, indent=4)
|
|
await ctx.send(
|
|
f"Komenda mierząca incydenty w Eldritch AbsinthHammerTimeSpaceContinuum.\n RESET THE CLOCK. Od ostatniego incydentu w chacie Hammera minęło {czas}. No ale teraz się odpierdoliło: {opis}"
|
|
)
|
|
else:
|
|
await ctx.send(
|
|
f"Komenda mierząca incydenty w Eldritch AbsinthHammerTimeSpaceContinuum.\n Gdzie z łapami do zegara? {client.get_user(346956223645614080).mention} coś albo się komuś stało albo zaraz się stanie jak będzie zegar tykał...."
|
|
)
|
|
|
|
|
|
# @client.hybrid_command()
|
|
async def get_image_stable_diffusion(ctx):
|
|
"""
|
|
Connect to remote stable diffusion and generate an image. Retrieve the image.
|
|
|
|
:param ctx: ctx is short for context and is typically used in Discord.py to represent the context in
|
|
which a command is being executed. It contains information such as the message, the channel, the
|
|
author, and other relevant data that can be used to perform actions or respond to the user. In this
|
|
case, it
|
|
"""
|
|
if ctx:
|
|
pass
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="zagraj_muzyke_mego_ludu",
|
|
description="Gra muzyke wyszukana na bazie tematow konwersacji na kanale NZ",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
# trunk-ignore(pylint/R0912)
|
|
async def zagraj_muzyke_mego_ludu(ctx):
|
|
"""
|
|
Play completeley random playlist based on messaging history.
|
|
|
|
:param ctx: ctx stands for "context" and is typically used in Discord.py commands to represent the
|
|
context in which the command was invoked. This includes information such as the message, the
|
|
channel, the author, and other relevant details. In this case, it is being passed as a parameter to
|
|
the `get_image
|
|
"""
|
|
stats = {}
|
|
if ctx:
|
|
stat_iter = get_stats(ctx, MUZYKA_MOJEGO_LUDU_HISTORIA)
|
|
async for name, how_many in stat_iter:
|
|
if len(name) > 3:
|
|
stats[name] = how_many
|
|
|
|
sorted_stats = sorted(stats.items(), key=lambda x: x[1])
|
|
key_words = []
|
|
for stat in sorted_stats:
|
|
key_words.append(stat[0])
|
|
logger.info(sorted_stats[:30])
|
|
logger.info(key_words[:30])
|
|
reply = "Wygenerowana plejlista:\n"
|
|
async with ctx.typing():
|
|
logger.info("Zaczynam szukać timestamp %s", datetime.now())
|
|
search_time_glob = datetime.now()
|
|
file = await wyszukaj(ctx=ctx, how_many=MUZYKA_MOJEGO_LUDU_PLAJLISTA, slowa_kluczowe=key_words[:MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE])
|
|
logger.info(
|
|
"Koniec szukania(timestamp %s zajęło %s",
|
|
datetime.now(),
|
|
datetime.now() - search_time_glob,
|
|
)
|
|
index = 1
|
|
if file:
|
|
for plik in file:
|
|
global MUZYKA # pylint: disable=global-variable-not-assigned
|
|
MUZYKA["queue"].insert(0, plik[1])
|
|
MUZYKA["requestor"].insert(0, ctx.author)
|
|
metadata = eyed3.load(plik[1])
|
|
if metadata and metadata.tag:
|
|
if metadata.tag.title:
|
|
reply += f"{index} {metadata.tag.title}"
|
|
else:
|
|
reply += f"{index}. {plik[1]} z prywatnej kolekcji Hammera."
|
|
|
|
if metadata.tag.artist:
|
|
reply += f" wykonawcy {metadata.tag.artist}"
|
|
if metadata.tag.album:
|
|
reply += f" z albumu {metadata.tag.album}"
|
|
if metadata.tag.title:
|
|
reply += " z prywatnej kolekcji Hammera i na Twoją specjalną rpośbę.\n"
|
|
else:
|
|
reply += f"{index}. {plik[1]} z prywatnej kolekcji Hammera."
|
|
|
|
index += 1
|
|
if len(reply) > 1800:
|
|
await ctx.send(reply)
|
|
reply = ""
|
|
else:
|
|
await ctx.send(
|
|
"Obawiam się że nie mogę nic znaleźć u siebie. Spróbuj komendy '$dej_co_ze_spotifaja' jeśli masz link"
|
|
)
|
|
await ctx.send(reply)
|
|
logger.info("Plejlista zrobiona")
|
|
|
|
|
|
# trunk-ignore(mypy/arg-type)
|
|
@client.hybrid_command(
|
|
name="parametry_muzyki_mego_ludu",
|
|
description="Konfiguruje komende zagraj muzyke mojego ludu",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def parametry_muzyki_mego_ludu(ctx, ile_historii=1500, ile_slow_kluczowych=30, jak_dluga_plejlista=30):
|
|
"""
|
|
The function `parametry_muzyki_mego_ludu` sets global variables for the number of history entries,
|
|
number of keywords, and length of playlist for a music application.
|
|
|
|
:param ctx: The `ctx` parameter is typically used in Discord.py, a Python library for creating
|
|
Discord bots. It represents the context of the command being executed, including information about
|
|
the message, the server, and the user who invoked the command
|
|
:param ile_historii: The parameter "ile_historii" represents the number of history items for the
|
|
music playlist, defaults to 1500 (optional)
|
|
:param ile_slow_kluczowych: The parameter "ile_slow_kluczowych" represents the number of keywords or
|
|
key phrases related to music that you want to include in your analysis or search, defaults to 30
|
|
(optional)
|
|
:param jak_dluga_plejlista: The parameter "jak_dluga_plejlista" determines the length of the
|
|
playlist. It specifies how many songs should be included in the playlist, defaults to 30 (optional)
|
|
"""
|
|
|
|
if ctx:
|
|
global MUZYKA_MOJEGO_LUDU_HISTORIA # pylint: disable=global-statement
|
|
MUZYKA_MOJEGO_LUDU_HISTORIA = ile_historii
|
|
global MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE # pylint: disable=global-statement
|
|
MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE = ile_slow_kluczowych
|
|
global MUZYKA_MOJEGO_LUDU_PLAJLISTA # pylint: disable=global-statement
|
|
MUZYKA_MOJEGO_LUDU_PLAJLISTA = jak_dluga_plejlista
|
|
|
|
|
|
# *================================== Run
|
|
# trunk-ignore(mypy/arg-type)
|
|
client.run(TOKEN)
|