""" 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 import tiktoken from discord.ext import commands from spotipy.oauth2 import SpotifyClientCredentials import yt_dlp 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 = 15 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" SYSTEM_GPT_SETTINGS = "/home/mtuszowski/conjurer/system_gpt_settings.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" SYSTEM_GPT_SETTINGS = "/home/pi/Conjurer/system_gpt_settings.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" SYSTEM_GPT_SETTINGS = "system_gpt_settings.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(SYSTEM_GPT_SETTINGS, "r+", encoding=ENCODING) as temp_settings_file: # First we load existing data into a dict. GPT_SETTINGS = json.load(temp_settings_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"] cyclic_words = data["cyclic_words"] 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. kondziu_mentioned = False for mention in message.mentions: if mention == client.user: kondziu_mentioned = True if kondziu_mentioned or "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 def num_tokens_from_string(message, model): """ The function takes a string message and a model as input and returns the number of tokens in the message according to the given model. :param message: A string containing the message or text from which you want to count the number of tokens :param model: The model parameter refers to a language model or tokenizer that can be used to tokenize the input string. It could be a pre-trained model or a custom tokenizer """ tokens_per_message = 3 tokens_per_name = 1 chat_gpt_encoding = tiktoken.encoding_for_model(model) num_tokens = 0 num_tokens += tokens_per_message for keys, values in message.items(): num_tokens += len(chat_gpt_encoding.encode(values)) if keys == "role": num_tokens += tokens_per_name num_tokens += 3 # every reply is primed with <|start|>assistant<|message|> return num_tokens 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 check(): """Funkcja sprawdzająca czy grać następny kawałek.""" last_spontaneous_call = datetime.now() while True: tdelta = datetime.now() - last_spontaneous_call tdelta = tdelta.total_seconds() if tdelta > 34000: channel = client.get_channel(1062047571557744721) last_spontaneous_call = datetime.now() with channel.typing(): message = get_random_cyclic_message() await channel.send(message) # 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) async def get_random_cyclic_message(): """ The function `get_random_cyclic_message` returns a random cyclic message from a list of cyclic words. :return: a random cyclic message from the list `cyclic_words`. """ messnum = random.randint(0, len(cyclic_words)-1) return cyclic_words[messnum][0] # trunk-ignore(pylint/R0914) # trunk-ignore(pylint/R0913) # trunk-ignore(pylint/R0915) 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 = [] history.append(GPT_SETTINGS[0]) chat_gpt_config_request_size = num_tokens_from_string(GPT_SETTINGS[0], "gpt-4") for (slowo, reakcja) in word_reactions.items(): if not reakcja[3]: content = "Kiedy słyszysz " + slowo + " to reagujesz lub dzieje się to " + reakcja[0] temp = {"role": "system", "content": content} chat_gpt_config_request_size += num_tokens_from_string(temp, "gpt-4") history.append(temp) final_prompt = username + ":" + prompt logger.info("Rozmiar zapytania przed dodaniem historii %s", chat_gpt_config_request_size) if music: algorithm = "gpt-3.5-turbo-16k" table = message_table_muzyka token_amount = 10700 else: table = MESSAGE_TABLE algorithm = "gpt-4" token_amount = 7000 prompt_gpt_request_size = num_tokens_from_string({"role": "user", "content": final_prompt}, "gpt-4") temptable = [] for i in reversed(table): temp_token = num_tokens_from_string(i, "gpt-4") logger.info("Rozmiar zapytania %s prompt %s temp %s", chat_gpt_config_request_size, prompt_gpt_request_size, temp_token) if chat_gpt_config_request_size < token_amount + prompt_gpt_request_size + temp_token: temptable.insert(1, i) chat_gpt_config_request_size += temp_token history.extend(temptable) temp = {"role": "user", "content": final_prompt} history.append(temp) logger.info("Rozmiar zapytania po wyslaniu %s", chat_gpt_config_request_size) response = await openai.ChatCompletion.acreate( model=algorithm, messages=history ) logger.info("Historia wysłana:") logger.info(history) await asyncio.sleep(15) result = "" logger.debug("Odpowiedzi") logger.info(response) 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, }, ] 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 = await remove_characters(f_iter, 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 remove_characters(string, character): """ The `remove_characters` function removes all occurrences of a specified character from a given string. :param string: The string parameter is the input string from which characters will be removed :param character: The character parameter is the character that you want to remove from the string :return: a new string where all occurrences of the specified character have been removed. """ return string.replace(character, "") 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.\n" 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) # trunk-ignore(pylint/R0915) 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: logger.info("Zagraj muzyke mego ludu: wywolane") 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]) logger.info("Zagraj muzyke mego ludu:zebrano statystyki") key_words = [] for stat in sorted_stats: key_words.append(stat[0]) logger.info(sorted_stats[:MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE]) logger.info(key_words[:MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE]) logger.info(sorted_stats[-MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE:]) logger.info(key_words[-MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE:]) final_key_words = key_words[:MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE] final_key_words.extend(key_words[-MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE:]) 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=final_key_words) logger.info("Zagraj muzyke mego ludu:wygenerowano plejliste") logger.info(file) 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.\n" 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=15, 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: async with ctx.typing(): # if ':' in ile_historii: # _, _, ile_historii = ile_historii.partition(':') # if ':' in ile_slow_kluczowych: # _, _, ile_slow_kluczowych = ile_slow_kluczowych.partition(':') # if ':' in jak_dluga_plejlista: # _, _, jak_dluga_plejlista = jak_dluga_plejlista.partition(':') try: global MUZYKA_MOJEGO_LUDU_HISTORIA # pylint: disable=global-statement global MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE # pylint: disable=global-statement global MUZYKA_MOJEGO_LUDU_PLAJLISTA # pylint: disable=global-statement logger.info("Długość historii wyszukiwania: %s\n Ilość słów kluczowych: %s\n Długość plejlisty do wygenerowanie: %s", MUZYKA_MOJEGO_LUDU_HISTORIA, MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE, MUZYKA_MOJEGO_LUDU_PLAJLISTA) ctx.send(f"Dotychczasowe wartośći:\n Długość historii wyszukiwania: {MUZYKA_MOJEGO_LUDU_HISTORIA}\n Ilość słów kluczowych: {MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE}\n Długość plejlisty do wygenerowanie:{MUZYKA_MOJEGO_LUDU_PLAJLISTA}") MUZYKA_MOJEGO_LUDU_HISTORIA = ile_historii MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE = ile_slow_kluczowych MUZYKA_MOJEGO_LUDU_PLAJLISTA = jak_dluga_plejlista logger.info("Długość historii wyszukiwania: %s\n Ilość słów kluczowych: %s\n Długość plejlisty do wygenerowanie: %s", MUZYKA_MOJEGO_LUDU_HISTORIA, MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE, MUZYKA_MOJEGO_LUDU_PLAJLISTA) ctx.send(f"Zmienione wartośći:\n Długość historii wyszukiwania: {MUZYKA_MOJEGO_LUDU_HISTORIA}\n Ilość słów kluczowych: {MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE}\n Długość plejlisty do wygenerowanie:{MUZYKA_MOJEGO_LUDU_PLAJLISTA}") except discord.ext.commands.errors.BadArgument as exce: ctx.send("Spierdalaj") logger.info(exce) # *================================== Run # trunk-ignore(mypy/arg-type) client.run(TOKEN)