diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3c8b1b3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.linting.enabled": true, + "python.linting.pylintEnabled": true +} \ No newline at end of file diff --git a/bot.py b/bot.py index c19fa5c..3b3f386 100644 --- a/bot.py +++ b/bot.py @@ -1,74 +1,122 @@ +""" +Module of a python bot named Conjurer - used to work on BDSM discord servers. +""" # This Python file uses the following encoding: utf-8 -# *=========================================== Imports -import os -import discord -import json -import re -import netrc -from discord.ext import commands -import logging -import requests -from datetime import datetime -from sys import platform -import openai -import numpy as np -import pdf2image -import io -import PyPDF2 -import random +# 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 -from pathlib import PurePath -from pathlib import Path +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, TypedDict, Union + +# *==============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 + +import yt_dlp from spotify_dl import spotify from spotify_dl import youtube as youtube_download -import yt_dlp -from spotipy.oauth2 import SpotifyClientCredentials -import logging.handlers as handlers -import shutil + +Movie = TypedDict("Movie", {"name": str, "year": int}) +movie: Movie = {"name": "Blade Runner", "year": 1982} + +Music_Config = TypedDict( + "Music_Config", + { + "playing": bool, + "ctx": Union[str, None], + "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 = {"playing": False, "ctx": None, "queue": [], "requestor": []} +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 = "" # *=========================================== Platform Specific Predefines -if platform == "linux" or platform == "linux2": - 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" +if platform in ("linux", "linux2"): + if "microsoft-standard" in uname().release: + 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/" + 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/" elif platform == "win32": - logfile = "discord.log" - memory_five_siara = "pamiec.json" - memory_five_muzyka = "pamiec_muzyki.json" - # music_folder = 'G:\\Muzyka\\segars\\segars\\segars_&_friends_-_shanties_of_oldtime_sailing_ships\\' - 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" - + 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 = "cp1250" # *=========================================== Keys -netrc = netrc.netrc(netrc_file) -remoteHostName = "discord" -authTokens = netrc.authenticators(remoteHostName) +netrc_mod = netrc.netrc(NETRC_FILE) +REMOTE_HOST_NAME = "discord" +authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME) TOKEN = authTokens[2] -remoteHostName = "openai" -authTokens = netrc.authenticators(remoteHostName) +REMOTE_HOST_NAME = "openai" +authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME) openai.api_key = authTokens[2] -remoteHostName = "spotipy" -authTokens = netrc.authenticators(remoteHostName) +REMOTE_HOST_NAME = "spotipy" +authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME) spotify_ctrl = spotipy.Spotify( client_credentials_manager=SpotifyClientCredentials( client_id=authTokens[0], @@ -87,8 +135,8 @@ intents.voice_states = True logger = logging.getLogger("discord") logger.setLevel(logging.DEBUG) handler = handlers.RotatingFileHandler( - filename=logfile, - encoding="utf-8", + filename=LOGFILE, + encoding=ENCODING, mode="a", maxBytes=6 * 1024 * 1024, backupCount=6, @@ -99,22 +147,21 @@ random.seed() logger.debug(platform) logger.info("Playlist generation") - # *=========================================== Load Data files music_file_list = [] -for item in Path.glob(Path(music_folder), "**/*.mp3"): - file = item.as_posix() +for mp3_item in Path.glob(Path(MUSIC_FOLDER), "**/*.mp3"): + temp_music_file = mp3_item.as_posix() if platform == "win32": - file = file.replace("/", "\\") - music_file_list.append(file) -with open(memory_five_siara, "r+") as file: + 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(file) -with open(memory_five_muzyka, "r+") as file: + 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(file) -f = open(settings_file) -data = json.load(f) + 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: @@ -128,16 +175,31 @@ client = commands.Bot(intents=intents, command_prefix="$") @client.event async def on_ready(): - logger.debug(f"{client.user} has connected to Discrod!") + """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 tree.sync(guild=discord.Object(id=Your guild id)) + # await tree.sync(guild=discord.Object(id=Your guild id)) 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): @@ -146,7 +208,7 @@ async def on_message(message): vykidailo = True if ("Conjurer Åšpij SÅ‚odko AnioÅ‚ku" in message.content) and vykidailo: - exit() + sys.exit() # kanal bez bota if message.channel.id == 1095985579147141202: return @@ -181,6 +243,7 @@ async def on_message(message): 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 @@ -188,7 +251,9 @@ async def on_message(message): if reaction: emoji = client.get_emoji(word_reactions[word][0]) await message.add_reaction(emoji) - else: + 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() @@ -212,36 +277,32 @@ async def on_message(message): vykidailo = True if role.name == "Bartender": bartender = True - global message_table - result, message_table = await handle_response( - prompt, vykidailo, bartender, message_table, username, False + 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: {}".format(message.content)) + 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: {}".format(image_url)) - response = requests.get(image_url) - file = response.content + logger.debug("Wynikowy obrazek pod url: %s", image_url) + response = requests.get(image_url, timeout=360) - file_name = message.content + ".png" - if platform == "linux" or platform == "linux2": - file_name = ( - "/home/pi/RetroPie/Conjurer_graphics" + message.content + ".png" - ) - elif platform == "win32": - file_name = message.content + ".png" + temp_file_name = message.content + ".png" + temp_file_name = GRAPHICS_PATH + message.content + ".png" - with open(file_name, "wb") as f: - f.write(response.content) + with open(temp_file_name, "wb") as dalle_file: + dalle_file.write(response.content) logger.info("Koniec procedury obrazkowej.") - fnord = discord.File(file_name, spoiler=False, description=message.content) + fnord = discord.File( + temp_file_name, spoiler=False, description=message.content + ) await message.reply(file=fnord) @@ -249,34 +310,80 @@ async def on_message(message): 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: - voiceClient = client.voice_clients[0] - if voiceClient.is_connected(): + voice_client = client.voice_clients[0] + if voice_client.is_connected(): return - vc = await voice_channel.connect() + 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: - voiceClient = client.voice_clients[0] - if voiceClient.is_connected(): - await voiceClient.disconnect() + voice_client = client.voice_clients[0] + if voice_client.is_connected(): + await voice_client.disconnect() +# trunk-ignore(pylint/R0915) +# trunk-ignore(pylint/R0914) +# trunk-ignore(pylint/R0912) async def play(ctx, zamawial=None, arg=None): - # 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") + """ + 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") - voiceClient = client.voice_clients[0] file_to_play = None + voice_client = client.voice_clients[0] if not client.voice_clients: - voiceClient = client.voice_clients[0] - if not voiceClient.is_connected(): - await connect(ctx) - global MUZYKA + voice_client = client.voice_clients[0] + await connect(ctx) if MUZYKA["queue"]: file_to_play = MUZYKA["queue"].pop() zamawial = MUZYKA["requestor"].pop() @@ -287,29 +394,30 @@ async def play(ctx, zamawial=None, arg=None): logger.debug(file_to_play) metadata = eyed3.load(file_to_play) query = None - if platform == "linux" or platform == "linux2": + separator = None + if platform in ("linux", "linux2"): separator = "/" elif platform == "win32": separator = "\\" temp = file_to_play.split(separator)[-1] - kawalek = "Zagram teraz kawalek {} z prywatnej kolekcji Hammera.".format(temp) + kawalek = f"Zagram teraz kawalek {temp} z prywatnej kolekcji Hammera." if metadata: if metadata.tag: if metadata.tag.title: - query = "Opowiedz mi o kawaÅ‚ku {}".format(metadata.tag.title) - kawalek = "Zagram teraz kawalek {}".format(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 += " wykonawcy {}".format(metadata.tag.artist) - kawalek += " wykonawcy {}".format(metadata.tag.artist) + query += f" wykonawcy {metadata.tag.artist}" + kawalek += f" wykonawcy {metadata.tag.artist}" if metadata.tag.album: - query += " z albumu {}".format(metadata.tag.album) - kawalek += " z albumu {}".format(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 += "Na specjalne życzenie <@{}>.".format(zamawial.id) + kawalek += f"Na specjalne życzenie <@{zamawial.id}>." await ctx.send(kawalek) - voiceClient.play( + voice_client.play( discord.PCMVolumeTransformer( discord.FFmpegPCMAudio(source=file_to_play), volume=0.3 ) @@ -328,69 +436,70 @@ async def play(ctx, zamawial=None, arg=None): username = ctx.message.author.nick else: username = ctx.message.author.name - global message_table - result, message_table = await handle_response( + 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:{}".format(message_table_muzyka)) + 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.""" while True: # TODO: Tu doÅ‚ożyć skanowanie rozmiaru pliku logowania oraz plików pamiÄ™ci i przenoszenie ich na wiÄ™kszy dysk. # TODO: Dodać typing i dać znać że wykonuje operacje porzÄ…dkowania Baru (zamiata, ukÅ‚ada szklanki itp.) + # trunk-ignore(codespell/misspelled) # TODO: dlaczego sie tu wypierdala if client.voice_clients: - voiceClient = client.voice_clients[0] - if voiceClient.is_connected(): - global MUZYKA - while MUZYKA["playing"] and voiceClient.is_connected(): + voice_client = client.voice_clients[0] + if voice_client.is_connected(): + while MUZYKA["playing"] and voice_client.is_connected(): if ( - voiceClient - and not voiceClient.is_playing() - and not voiceClient.is_paused() + 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, "{}discord{}.log".format(logstore, datetime.now())) + 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: + 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 = "{}{}{}.json".format( - logstore, memory_five_muzyka[:-4], datetime.now() + path_newfile = ( + f"{LOGSTORE}{MEMORY_FIVE_MUZYKA[:-4]}{datetime.now()}.json" ) - with open(memory_five_muzyka, "r+") as file: - with open(path_newfile, "x") as new_file: + 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) + file_data = json.load(file_music_memory) new_data = [] new_data.append(file_data[0]) new_data.extend(file_data[:-20]) - file.seek(0) + file_music_memory.seek(0) # convert back to json. new_file.seek(0) - json.dump(new_data, file, indent=4) + 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 = "{}{}{}.json".format( - logstore, memory_five_siara[:-4], datetime.now() - ) + 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+") as file: - with open(path_newfile, "x") as new_file: + 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 = [] @@ -407,30 +516,50 @@ async def check(): await asyncio.sleep(60) -async def handle_response(prompt, vykidailo, bartender, history, username, music): - # TODO: Wykrywać "PracujÄ™ nad odpowiedziÄ… i tego typu rzeczy" - logger.info("Wywolanie procedury openai z promptem: {}".format(prompt)) - temp = {"role": "user", "content": username + ":" + prompt} +# 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+") as file: + 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) + file_data = json.load(file_music_memory) # Join new_data with file_data inside emp_details file_data.append(temp) - file.seek(0) + file_music_memory.seek(0) # convert back to json. - json.dump(file_data, file, indent=4) + json.dump(file_data, file_music_memory, indent=4) else: - with open(memory_five_siara, "r+") as file: + with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as file_memory: # First we load existing data into a dict. - file_data = json.load(file) + file_data = json.load(file_memory) # Join new_data with file_data inside emp_details file_data.append(temp) - file.seek(0) + file_memory.seek(0) # convert back to json. - json.dump(file_data, file, indent=4) + json.dump(file_data, file_memory, indent=4) history = [] # * append bo pierwszy index # * extend bo 20 ostatnich @@ -438,8 +567,8 @@ async def handle_response(prompt, vykidailo, bartender, history, username, music history.append(message_table_muzyka[0]) history.extend(message_table_muzyka[-20:]) else: - history.append(message_table[0]) - history.extend(message_table[-20:]) + history.append(MESSAGE_TABLE[0]) + history.extend(MESSAGE_TABLE[-20:]) logger.info("Historia wysÅ‚ana:") logger.debug(history) response = await openai.ChatCompletion.acreate( @@ -456,7 +585,7 @@ async def handle_response(prompt, vykidailo, bartender, history, username, music temp = {"role": "assistant", "content": result} history.append(temp) if music: - with open(memory_five_muzyka, "r+") as file: + 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 @@ -466,7 +595,7 @@ async def handle_response(prompt, vykidailo, bartender, history, username, music # convert back to json. json.dump(file_data, file, indent=4) else: - with open(memory_five_siara, "r+") as file: + 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 @@ -475,10 +604,26 @@ async def handle_response(prompt, vykidailo, bartender, history, username, music file.seek(0) # convert back to json. json.dump(file_data, file, indent=4) - return result, message_table + 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 = {} @@ -491,7 +636,7 @@ async def get_file(ctx, source, link): url_data = {"urls": []} url_dict = {} url_dict["save_path"] = Path( - PurePath.joinpath(Path(music_folder), Path(directory_name)) + PurePath.joinpath(Path(MUSIC_FOLDER), Path(directory_name)) ) url_dict["save_path"].mkdir(parents=True, exist_ok=True) url_dict["songs"] = file_list @@ -499,7 +644,7 @@ async def get_file(ctx, source, link): file_name_f = youtube_download.default_filename youtube_download.download_songs( songs=url_data, - output_dir=music_folder, + output_dir=MUSIC_FOLDER, format_str="bestaudio/best", skip_mp3=False, keep_playlist_order=False, @@ -514,11 +659,11 @@ async def get_file(ctx, source, link): if platform == "win32": dir_path = dir_path.replace("/", "\\") return dir_path, file_list - else: - 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: {}".format(link)) + + 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()@:%_\+.~#?&\/=]*)$" @@ -541,7 +686,7 @@ async def get_file(ctx, source, link): }, ] if platform == "win32": - dir_path = "G:\\Muzyka\Youtube" + dir_path = "G:\\Muzyka\\Youtube" else: dir_path = "/home/pi/RetroPie/mp3/Youtube" ydl_opts = { @@ -562,50 +707,76 @@ async def get_file(ctx, source, link): with yt_dlp.YoutubeDL(ydl_opts) as ydl: try: ydl.download([query]) - except Exception as e: # skipcq: PYL-W0703 - logger.error(e) + except Exception as exce: # pylint: disable=broad-exception-caught + logger.error(exce) logger.info( - f"Failed to download {link}, make sure yt_dlp is up to date" + "Failed to download %s, make sure yt_dlp is up to date", link ) extract = re.search("v=(...........)[&,$]*", link) - id = extract.group(1) + 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(".*" + id + ".*", file): + if re.match(".*" + ident + ".*", file): file_list.append(file) return dir_path, file_list - else: - 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: {}".format(link)) - return "", None - + 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): +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: + 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) + ''' -''' 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) -''' +# trunk-ignore(pylint/R0914) +# trunk-ignore(pylint/R0915) +# trunk-ignore(pylint/R0912) async def wyszukaj(ctx, how_many=0): - # TODO: Potem dorobić wyszukiwania po kawaÅ‚kach słów i sieć neuronowÄ… z możliwoÅ›ciÄ… wyrażenia opinii o dopasowaniu. + """ + 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. word_list = ctx.message.content.split() logger.info("Wyszukuje") - global music_file_list search_weight = [] - for i in range(len(music_file_list)): + for _ in range(len(music_file_list)): search_weight.append((0, "")) time_start = datetime.now() @@ -615,7 +786,7 @@ async def wyszukaj(ctx, how_many=0): skip_start = 1 for word in word_list[skip_start:]: token_weight = len(word) - logger.info("SÅ‚owo kluczowe: {}".format(word)) + logger.info("SÅ‚owo kluczowe: %s", word) itr = 0 for file in music_file_list: if platform == "win32": @@ -644,10 +815,10 @@ async def wyszukaj(ctx, how_many=0): "mp3", ] all_words = [] - for f in file: + for f_iter in file: for char in char_remove: - f = f.replace(char, "") - tmp = f.split() + f_iter = f_iter.replace(char, "") + tmp = f_iter.split() all_words.extend(tmp) pingu = 1 pattern_len = len(all_words) @@ -668,9 +839,7 @@ async def wyszukaj(ctx, how_many=0): matched_times += 1 itr += 1 - logger.info( - "Stworzylem tablice wag zajęło mi to {}".format(datetime.now() - time_start) - ) + 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 @@ -692,19 +861,40 @@ async def wyszukaj(ctx, how_many=0): return return_list -async def max_weight(list): - max_weight = 0 - for iter in list: - if iter[0] > max_weight: - max_weight = iter[0] - return max_weight +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 -#@client.command() -@client.hybrid_command(name = "dej_co_ze_spotifaja", description = "Podaj link do spotify - sciagnie i doda muzyke", guild=discord.Object(id=664789470779932693)) + +# 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(): @@ -718,7 +908,7 @@ async def dej_co_ze_spotifaja(ctx): else: separator = "/" for file in files: - global MUZYKA + global MUZYKA # pylint: disable=global-variable-not-assigned MUZYKA["queue"].insert( 0, dir_path @@ -731,46 +921,69 @@ async def dej_co_ze_spotifaja(ctx): MUZYKA["requestor"].insert(0, ctx.author) music_file_list.append(file) await ctx.send( - "DodaÅ‚em do listy {}".format( - dir_path + file["artist"] + " - " + file["name"] + ".mp3" - ) + f"DodaÅ‚em do listy {dir_path}{file['artist']} - + {file['name']}.mp3" ) logger.info("Spotifaj udany") -#@client.command() -@client.hybrid_command(name = "dej_co_z_jutuba", description = "Podaj link do youtube - sciagnie i doda muzyke",guild=discord.Object(id=664789470779932693)) + +# 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 in content: - if re.match("http.*", item): - dir_path, files = await get_file(ctx, "Youtube", item) - if platform == "win32": - separator = "\\" - else: - separator = "/" + for item_yt in content: + if re.match("http.*", item_yt): + _, files = await get_file(ctx, "Youtube", item_yt) if files: - for file in files: - global MUZYKA - MUZYKA["queue"].insert(0, file) - music_file_list.append(file) + 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("DodaÅ‚em do listy {}".format(file)) + await ctx.send(f"DodaÅ‚em do listy {file_iter}") logger.info("Jutub udany") -#@client.command() -@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(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): - async with channel.typing(): + """ + 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") @@ -784,30 +997,37 @@ async def update_banlist(ctx): for message in messages: lines = message.content.split("\n") for line in lines: - match = re.search("\d{18}", line) + 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 + # 2. get list of already banned users for all servers Conjurer + # has rights to for guild in client.guilds: bancunter = 0 - logger.info("Serwer:{}".format(guild)) + # 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: + 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 + # 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 id in ban_list: + for ident in ban_list: try: - tmp_user = await client.fetch_user(int(id)) + tmp_user = await client.fetch_user(int(ident)) await guild.ban( tmp_user, reason="Automatyczna lista banów", @@ -821,140 +1041,232 @@ async def update_banlist(ctx): bad_id_cunter_counter += 1 # 4. return message success/failure - how many banned await ctx.send( - "Obecnie na serwerze {} jest {} zbanowanych użytkowników. Znaleziono na tym kanale {} id użytkowników do zbanowania. Zbanowano {} użytkowników. Na liÅ›cie jest {} użytkowników którzy skasowali już konto".format( - guild, - bancunter, - tobancunter, - cunter_counter, - bad_id_cunter_counter, - ) + "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( - "Nie mam na serwerze {} uprawnieÅ„ do banowania. :()".format( - guild - ) + f"Nie mam na serwerze {guild} uprawnieÅ„ do banowania. :()" ) await ctx.send("Zrobione tak czy inaczej") -#@client.command() -@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)) + +# 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(): + dir_path_sadox = "" # select random file - if platform == "linux" or platform == "linux2": - dir_path = "/home/pi/RetroPie/Fansadox/" + if platform in ("linux", "linux2"): + dir_path_sadox = "/home/pi/RetroPie/Fansadox/" elif platform == "win32": - dir_path = "C:\\Users\\mtusz\\OneDrive\\Dokumenty\Fansadox\\" + dir_path_sadox = "C:\\Users\\mtusz\\OneDrive\\Dokumenty\\Fansadox\\" res = [] # Iterate directory - for path in os.listdir(dir_path): + for path in os.listdir(dir_path_sadox): # check if current path is a file - if os.path.isfile(os.path.join(dir_path, path)): + 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 + filename, "rb") + 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 + filename, first_page=page, last_page=page + dir_path_sadox + filename, first_page=page, last_page=page ) - b = io.BytesIO() - image[0].save(b, "JPEG") - b.seek(0) - b.name = "image.jpg" - file = discord.File(b) + 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") -#@client.command() -@client.hybrid_command(name = "graj_muzyko", description = "Włącza muzykÄ™ na kanale #nocna-zmiana.",guild=discord.Object(id=664789470779932693)) + +# 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 + 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") -#@client.command() -@client.hybrid_command(name = "cisza", description = "Wyłącza muzykÄ™ i czyÅ›ci plejliste.",guild=discord.Object(id=664789470779932693)) + +# 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 + global MUZYKA # pylint: disable=global-variable-not-assigned MUZYKA["playing"] = False MUZYKA["ctx"] = None - voiceClient = client.voice_clients[0] - if voiceClient.is_connected(): + 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") -#@client.command() -@client.hybrid_command(name = "dalej", description = "Przerzuca na nastÄ™pny kawaÅ‚ek",guild=discord.Object(id=664789470779932693)) + +# 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") - voiceClient = client.voice_clients[0] - if voiceClient.is_connected(): - voiceClient.stop() + voice_client = client.voice_clients[0] + if voice_client.is_connected(): + voice_client.stop() await play(ctx=ctx) logger.info("Next completed") -#@client.command() -@client.hybrid_command(name = "daj_mi_chwile", description = "Pauzuje",guild=discord.Object(id=664789470779932693)) + +# 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") - voiceClient = client.voice_clients[0] - if voiceClient.is_connected(): - voiceClient.pause() + voice_client = client.voice_clients[0] + if voice_client.is_connected(): + voice_client.pause() logger.info("Pause completed") -#@client.command() -@client.hybrid_command(name = "graj_dalej", description = "Odpauzowuje",guild=discord.Object(id=664789470779932693)) + +# 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") - voiceClient = client.voice_clients[0] - if voiceClient.is_paused(): - voiceClient.resume() + voice_client = client.voice_clients[0] + if voice_client.is_paused(): + voice_client.resume() logger.info("Unpause completed") -#@client.command() -@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)) +# 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 {}".format(datetime.now())) + logger.info("Zaczynam szukać timestamp %s", datetime.now()) file = await wyszukaj(ctx=ctx) logger.info( - "Koniec szukania(timestamp {} zajęło {}".format( - datetime.now(), datetime.now() - search_time_glob - ) + "Koniec szukania(timestamp %s zajęło %s", + datetime.now(), + datetime.now() - search_time_glob, ) + if file: for plik in file: - global MUZYKA + 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 {} z prywatnej kolekcji Hammera.".format( - plik[1] - ) + reply = "DodaÅ‚em do playlisty {plik[1]} z prywatnej kolekcji Hammera." if metadata.tag.title: - reply = "DodaÅ‚em do playlisty kawalek {}".format(metadata.tag.title) + reply = f"DodaÅ‚em do playlisty kawalek {metadata.tag.title}" if metadata.tag.artist: - reply += " wykonawcy {}".format(metadata.tag.artist) + reply += f" wykonawcy {metadata.tag.artist}" if metadata.tag.album: - reply += " z albumu {}".format(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: @@ -962,42 +1274,56 @@ async def zagraj_mi_kawalek(ctx): "Obawiam siÄ™ że nie mogÄ™ nic znaleźć u siebie. Spróbuj komendy '$dej_co_ze_spotifaja' jeÅ›li masz link" ) -#@client.command() -@client.hybrid_command(name = "zrob_mi_plejliste", description = "Generuje playliste - param1 - dlugosc, potem slowa wyszukiwania",guild=discord.Object(id=664789470779932693)) + +# 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 {}".format(datetime.now())) + logger.info("Zaczynam szukać timestamp %s", datetime.now()) search_time_glob = datetime.now() - dlugosc_playlisty = ctx.message.content.split()[0] + dlugosc_playlisty = ctx.message.content.split()[1] file = await wyszukaj(ctx=ctx, how_many=dlugosc_playlisty) logger.info( - "Koniec szukania(timestamp {} zajęło {}".format( - datetime.now(), datetime.now() - search_time_glob - ) + "Koniec szukania(timestamp %s zajęło %s", + datetime.now(), + datetime.now() - search_time_glob, ) index = 1 if file: for plik in file: - global MUZYKA + 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.tag.title: - reply += "{} {}".format(index, metadata.tag.title) + reply += f"{index} {metadata.tag.title}" else: - reply += "{}. {} z prywatnej kolekcji Hammera.".format(index, - plik[1] - ) + reply += "{index}. {plik[1]} z prywatnej kolekcji Hammera." if metadata.tag.artist: - reply += " wykonawcy {}".format(metadata.tag.artist) + reply += f" wykonawcy {metadata.tag.artist}" if metadata.tag.album: - reply += " z albumu {}".format(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" - index+=1 + reply += ( + " z prywatnej kolekcji Hammera i na TwojÄ… specjalnÄ… rpoÅ›bÄ™.\n" + ) + index += 1 if len(reply) > 1800: await ctx.send(reply) reply = "" @@ -1008,9 +1334,23 @@ async def zrob_mi_plejliste(ctx): await ctx.send(reply) logger.info("Plejlista zrobiona") -#@client.command() -@client.hybrid_command(name = "przytul", description = "Przytul kogoÅ› - daj mention po komendzie :)") -async def przytul(ctx, arg:discord.Member=None): + +# trunk-ignore(mypy/arg-type) +@client.hybrid_command( + name="przytul", description="Przytul kogoÅ› - daj mention po komendzie :)" +) +async def przytul(ctx, arg: Union[discord.Member, None] = 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 is being executed, including information such as the + message, the channel, the server, and the user who invoked the command + :param arg: The parameter "arg" is a variable that can accept either a discord.Member object or None + as its value. It is an optional parameter, which means that if no value is provided for it when the + function is called, it will default to None + :type arg: Union[discord.Member, None] + """ async with ctx.typing(): nieprzytulac = False for mention in ctx.message.mentions: @@ -1020,13 +1360,12 @@ async def przytul(ctx, arg:discord.Member=None): if arg and nieprzytulac: await ctx.send( - "Å»ebym ja Ciebie nie przytuliÅ‚ {}".format(ctx.message.author.mention) + f"Å»ebym ja Ciebie nie przytuliÅ‚ {ctx.message.author.mention}" ) elif arg: await ctx.send( - "Już dobrze.... Już dobrze... Ojej.. Biedactwo... :( *W uÅ‚amku sekundy {} 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*".format( - arg.mention - ) + # 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( @@ -1034,81 +1373,140 @@ async def przytul(ctx, arg:discord.Member=None): ) -#@client.command() -@client.hybrid_command(name = "fabryczka", description = "Historia fabryczki") +# 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) -#@client.command() -@client.hybrid_command(name = "chata_hammera", description = "Czas od ostatniego incydentu w AbsinthHammerTimeSpaceContinuum") + +# trunk-ignore(mypy/arg-type) +@client.hybrid_command( + name="chata_hammera", + description="Czas od ostatniego incydentu w AbsinthHammerTimeSpaceContinuum", +) async def chata_hammera(ctx): - with open(accident_log, "r+") as new_file: + """ + 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( - "Komenda mierzÄ…ca incydenty w Eldritch AbsinthHammerTimeSpaceContinuum.\n Od ostatniego incydentu w chacie Hammera minęło {}. Incydent to: {}".format( - datetime.now() - czas, opis - ) + f"Komenda mierzÄ…ca incydenty w Eldritch AbsinthHammerTimeSpaceContinuum.\n Od ostatniego incydentu w chacie Hammera minęło {datetime.now() - czas}. Incydent to: {opis}" ) -@client.hybrid_command(name = "historia_incydentow_u_hammera", description = "Wyswietla liste incydentów które miaÅ‚y miejsce w AbsinthHammerTimeSpaceContinuum.") -#@client.command() +# 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): - with open(accident_log, "r+") as new_file: + """ + 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) + 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 data in file_data: - opis = data[0] - czas = datetime.strptime(data[1], "%Y-%m-%d %H:%M:%S.%f") - add_data = "{}. Opis: {} Data: {}".format(index, opis, czas) + 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) -@client.hybrid_command(name = "reset_the_clock", description = "Resetowanie zegara - dostÄ™pne wyłącznie dla Hammera", guild=discord.Object(id=664789470779932693)) -#@client.command() +# 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+") as new_file: + 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) + 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, "{}".format(datetime.now())] + accident = accident = [opis, f"{datetime.now()}"] file_data.append(accident) - new_file.seek(0) + new_file_accidents.seek(0) # convert back to json. - json.dump(file_data, new_file, indent=4) + json.dump(file_data, new_file_accidents, indent=4) await ctx.send( - "Komenda mierzÄ…ca incydenty w Eldritch AbsinthHammerTimeSpaceContinuum.\n RESET THE CLOCK. Od ostatniego incydentu w chacie Hammera minęło {}. No ale teraz siÄ™ odpierdoliÅ‚o: {}".format( - czas, opis - ) + 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( - "Komenda mierzÄ…ca incydenty w Eldritch AbsinthHammerTimeSpaceContinuum.\n Gdzie z Å‚apami do zegara? {} coÅ› albo siÄ™ komuÅ› staÅ‚o albo zaraz siÄ™ stanie jak bÄ™dzie zegar tykaÅ‚....".format( - client.get_user(346956223645614080).mention - ) + 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.command() -async def get_image_stable_diffusion(ctx): - pass -#@client.command() -#@client.hybrid_command() +# @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 + + +# @client.hybrid_command() async def zagraj_muzyke_mego_ludu(ctx): - pass + """ + 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 + """ + + if ctx: + pass + # *================================== Run +# trunk-ignore(mypy/arg-type) client.run(TOKEN) diff --git a/settings.json b/settings.json index f53eccd..3f8fe96 100644 --- a/settings.json +++ b/settings.json @@ -5,310 +5,365 @@ 1103376650487484556, 10, 0, - true + true, + false ], "hammer": [ "Hammer? Chuj o z³otym serduszku? Popierdolony w idealnych proporcjach do czystego serduszka? Legendarny zbanowany admin Kinky Little Discord? Legendarny kochanek? \"Bardzo fajny sadysta\":tm: W³aœciciel \"Domu który czyni pijanym\"? \nNie znam. Nie s³ysza³em", 15.0, 0, + false, false ], "chujz³oty": [ "Eeeee.... Szefie... ktoœ ciê wo³a! Wskazuje na Hammera", 15.0, 0, + false, false ], "indie": [ "Indie? *Wyci¹ga rewolwerow¹ wyrzutniê taktycznych bomb j¹drowych* Gdzie??? *Zaczyna siê maniakalnie œmiaæ*", 15.0, 0, + false, false ], "hindus": [ "Hindus? Gdzie.... *Wyci¹ga spod lady ciê¿ki miotacz p³omieni i zaczyna siê maniakalnie œmiaæ*", 15.0, 0, + false, false ], "chuj": [ "Eeeee.... Szefie... ktoœ ciê wo³a! Wskazuje na Hammera", 15.0, 0, + false, false ], "Sowa": [ "Czy ja us³ysza³em Sowa? Jedne z lepszych cycków po tej stronie wewnêtrznej sfery. Ale ja tego nie mówi³em", 100000.0, 0, + false, false ], "je¿": [ "*Œwiat³o gaœnie i ca³y bar nagle zostaje przemalowany w s³oneczniki.* Ki dia... *Œwiat³o znowu gaœnie i pojawiaj¹ siê psychodeliczne animacje* Je¿u. Ogranicz. *Po kolejnym b³ysku wszystko jest anime chibi style* Swoj¹... *I wraca do normy* Kreatywnoœæ. *Wzdycha* I tak jedyna chemia jak¹ tu widzê to domestos... *Krêci g³ow¹*", 15.0, 0, + false, false ], "savil": [ "*Znik¹d zaczyna byæ s³ychaæ muzykê. Conjurer nuci* There must be some way out of here... Said the joker to the thief...", 15.0, 0, + false, false ], "zinnejbajki": [ "ZInnejBajki? Nie wcale nie ¿ywi siê krwi¹. To pog³oski. *Nieudolnie chowa za plecami plastikowy pakiet z du¿ym napisem AB RH+ do lodówki na drinki*", 15.0, 0, + false, false ], "Bluszcz": [ "Bluszcz? *widocznie blednie i cofa siê o krok, dwustukilowy Elemental robi siê nagle taaaaki malutki* Nieeee.... Wcaaaale siê jej nie boje.... *Szybko ucieka za bar polerowaæ nerwowo szklanki* W ogóle nie jest straszna *Ostatnie zdanie brzmi jak pisk*", 15.0, 0, + false, false ], "jaeger": [ "Jaeger? *W tle zaczyna graæ opening z Attack on the Titan* Na pó³nocy siedzi, na te no... Piero¿ki bezglutenowe poluje. Odwa¿ny ch³op. *kiwa g³ow¹ z szacunkiem jaki tylko ³owca mo¿e wobec innego ³owcy mieæ*", 15.0, 0, + false, false ], "lena": [ "Lena? Nie nie mia³em NIC wspólnego ze znikniêciem... A czekaj - o któr¹ z osób znikniêtych pytasz? *Zaczyna nuciæ* Boogie ³ugi, znikn¹ wrógi", 15.0, 0, + false, false ], "rafikowy": [ "Rafikowy? *wy³amuje kostki* Jak masz PROBLEM z tym super dzieciakiem - to masz problem ze mn¹. I reszt¹ Nocnej Zmiany. *wali piêœci¹ w stó³* Ka-pe-wu?", 15.0, 0, + false, false ], "kurwa": [ "Kurwa? Ale która... Ta mityczna czy Toæka?", 15.0, 0, + false, false ], "shila": [ "Shila? Instruktorka aerobiku. Takiego nago. Z elektrod¹ w ty³ku... A i robi takie s³odkie *roar*. No normalnie a¿ siê chcê odpowiedzieæ *kici, kici*", 15.0, 0, + false, false ], "mienco": [ "*Wyci¹ga kawa³ krwawi¹cego króliczego udka* Frecia, Frecia, Frecia... kici, kici, taœ taœ.... *Œwiat³o gaœnie na sekunddê i po tym jak siê zapala ca³y bar jest uwalony krwi¹* Za ka¿dym razem *wzdycha*", 15.0, 0, + false, false ], "tatarek": [ "Tatarek? Nie dramatyzuj....", 15.0, 0, + false, false ], "krowa": [ "Krówka? Nie dramatyzuj....", 15.0, 0, + false, false ], "krówka": [ "Krówka? Nie dramatyzuj....", 15.0, 0, + false, false ], "cielak": [ "Krówka? Nie dramatyzuj....", 15.0, 0, + false, false ], "cielaczek": [ "Krówka? Nie dramatyzuj....", 15.0, 0, + false, false ], "jessenia": [ "Jessenia? Jak przysz³a tu w ró¿owej pid¿amie z uszkami to nawet nie mrugn¹³em okiem. Te oczy.... No ale jakiœ debil J¹ prowokowa³ mówi¹c \"Zdominuj mnie\" Akurat mia³a dobry humor wiêc prze¿y³ - ale po 115.0 sekundach by³ ju¿ na \"Tak Pani, przepraszam ¿e zaj¹³em czas Pani\" Respect dla kobitki", 15.0, 0, + false, false ], "enterix": [ "Enterix? Chodz¹ca encyklopedia sprzêtu BDSM... Jeœli On o czymœ nie s³ysza³ to to nie istnieje.", 15.0, 0, + false, false ], "aswerus": [ "Aswerus? Bardzo mi³y cz³owiek. Bardzo siê stara byæ mi³y. Szacun - ja nawet nie próbuje.", 15.0, 0, + false, false ], "szeherezada": [ "Szeherezada? BDSMowa Mata Hari. Tylko na tyle cwana ¿e zdecydowanie lepiej skoñczy.", 15.0, 0, + false, false ], "szehera": [ "Szeherezada? BDSMowa Mata Hari. Tylko na tyle cwana ¿e zdecydowanie lepiej skoñczy.", 15.0, 0, + false, false ], "szechera": [ "Szeherezada? BDSMowa Mata Hari. Tylko na tyle cwana ¿e zdecydowanie lepiej skoñczy.", 15.0, 0, + false, false ], "orja": [ "Orja? Nie wkurzamy goreañskich kajir. Mo¿na batem dostaæ:D", 15.0, 0, + false, false ], "khattaw": [ "*Zza œciany zaczyna dobywaæ siê przyt³umione dobre techenko. Conjurer blednie* Nie.... znowu ona.... Khaaaa *Mijaj¹ 72 godziny nieustaj¹cego raveu. Khat nadal tañczy. Elemental le¿y na pod³odze pod kroplówk¹*", 15.0, 0, + false, false ], "kwadraty": [ "Kwadraty? Przemi³y cz³owiek. Myœla³em ¿e jak go trzy napalone laski napad³y to bêdê musia³ wkroczyæ... Tak go la³y... A on potem ca³y w skowronkach. Szacun dla goœcia. Zajebisty ziomek.", 15.0, 0, + false, false ], "jules": [ "Jules? Œwietna diablica. Zaraz... Jak to sz³o... All is dust? Krew dla boga krwi? Poka¿ cycki dla Slaanesh? Iron within, iron without? Coœ w ten deseñ.", 15.0, 0, + false, false ], "elokwentna": [ "O matko. Znowu ta dyskusja? Hammer ma stary s³ownik gdzie elokwentna i pyskata s¹ praktycznie synonimami. Jedyna ró¿nica ¿e swojej uleg³ej mo¿na za bycie pyskatym daæ po dupie.", 15.0, 0, + false, false ], "elokwencja": [ "O matko. Znowu ta dyskusja? Hammer ma stary s³ownik gdzie elokwentna i pyskata s¹ praktycznie synonimami. Jedyna ró¿nica ¿e swojej uleg³ej mo¿na za bycie pyskatym daæ po dupie.", 15.0, 0, + false, false ], "pyskata": [ "Pyskata? A teraz mówisz o Tatarku czy o tej drugiej? *Zauwa¿a spojrzenie Hammera* Taaaak o Tatarku.", 15.0, 0, + false, false ], "kld": [ "KLD? Burdel wiêkszy ni¿ u Hammera.... Chocia¿ ten ju¿ sprz¹ta.... Burdel wiêkszy ni¿ u Hammera zanim zacz¹³ sprz¹taæ.", 15.0, 0, + false, false ], "burdel": [ "Oooo... Widzê ¿e Hammera odwiedzi³eœ. Tylko mu nie mów ¿eby firanki zmieni³.", 15.0, 0, + false, false ], "xo": [ "Xo? Jejku. Taka ma³a dziewczynka. A laæ j¹ mo¿na godzinami. Sam siê bym zasapa³. No i sznurki ³adne wi¹¿e.", 15.0, 0, + false, false ], "ziemniaczek": [ "Ma³y. POD£Y!!! PSYCHOPAT!!!!! Ahahahahaha..... *Strzela z karabinu maszynowego w sufit*", 15.0, 0, + false, false ], "toæka": [ "Toæka? Csiii... Nie tak g³oœno. Wpada, pokazuje cycki - po³owa facetów mdleje z powodu niedokrwienia g³owy, druga po³owa z powodu przekrwienia g³ówki - a trzecia siê bije kto siê na ni¹ pierwszy rzuci", 15.0, 0, + false, false ], "istoriya": [ "*Z g³oœników zaczyna lecieæ muzyka podejrzanie przypominaj¹ca Disco Polo* Istoriya? Legenda memiarstwa, rêka chodzi najebana. Nakurwia memami Na Na Na Na NA NA! *Conjurer rapuje niezbornie wymachuj¹c ³apkami*", 15.0, 0, + false, false ], "panifretka": [ "*Œwiat³o gaœnie. Z kranów zaczynaj¹ lecieæ iskry, a woda z wentylatora. Conjurer g³oœno wzdycha.* No tak ONA. WiedŸma znowu korki wyjeba³a bawi¹c siê w loszku jakimœ szczêœliwym biedactwem", 15.0, 0, + false, false ], "frecia": [ "Kochana pluszowa delikatna sadystka *patrzy na Fretkê, orientuje siê co ta robi* - Nie Frecia proszê nie wpinaj tego penisa do kontaktu, z³a Fretka, niedobra Sadystka *Nastêpuje wybuch snopu iskier i zapada ciemnoœæ*", 15.0, 0, + false, false ], "wolfram": [ "A kolega po fachu. Karczmarz ze wsi pod £odzi¹. Warszyce? Warszomin? Coœ takiego. Spoko ziomek. Coprawda nasza stacja kosmiczna jest wiêksza od jego karczmy - ale nie wszystko da siê zrobiæ w polu grawitacyjnym planety. Je¿u mówi ¿e jest bardem z naczelnych... No Homo Sapiens w koñcu.", 15.0, 0, + false, false ], "fallain": [ "Kochana Krówka. Sk³onnoœæ do dramatów i zakrwawiania œcian. Jedna z kilku NAPRAWDE srogich masochistek... Ach ta krew :drool:", 15.0, 0, + false, false ], "roar": [ "Kici, kici....", 15.0, 0, + false, false ], "conjurer": [ "Kto mnie wo³a³? Czego chcia³? Jak chcesz pogadaæ to napisz **Conjurer:** i powiedz co masz na myœli. Jak chcesz ¿ebym Ci coœ narysowa³ napisz **imaginuje sobie** i tak samo. Moge te¿ Ci opowiedzieæ o fabryczce", - 15.0, + 1200.0, 0, + false, false ], "œwiêta": [ "Œwiêta Ladacznica? Przyjazna osóbka. Bardzo. Jak wilk. Z wielkimi k³ami. Strasznie j¹ lubiê. Tak samo nie lubi przemocowców jak ja i Hammer", 15.0, 0, + false, false ], "ladacznica": [ "Œwiêta Ladacznica? Oj tak. Jeœli okazuje siê ¿e ktoœ jej naciœnie na odcisk zostaje po¿arty w ca³oœci... Dobra jest.", 15.0, 0, + false, false ], "saint": [ "Saint Harlot? Ten jej uœmiech. Potrafi nim wyraziæ wszystko. Zarówno ¿eby rozmówca siê ucieszy³ i wyluzowa³... Jak i zesra³ ze strachu.", 15.0, 0, + false, false ], "harlot": [ "Saint Harlot? Wielkie k³y. Wielkie pazury. Puchate miêciutkie futerko. Nie nale¿y irytowaæ. No i przemocowcy znikaj¹ w jej okolicy 'przypadkiem'.", 15.0, 0, + false, false + ], + "(oj\\.*\\s*){3,}": [ + "Widzê ¿e szykuje siê wpierdol. *Prze³adowuje rêczn¹ rewolwerow¹ wyrzutniê granatów termoj¹drowych i szeroko siê uœmiecha w kierunku Hammera i Wykidaj³ów*", + 15.0, + 0, + false, + true ] - - - } -} +} \ No newline at end of file